CVE-2018-14359: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: An issue was discovered in Mutt before 1.10.1 and NeoMutt before 2018-07-16. They have a buffer overflow via base64 data.
CWE description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
CVSS v3 base score: 9.8
Published: 2018-07-17T17:29Z
Repository: https://gitlab.com/muttmua/mutt
Language: C
Files changed: base64.c, imap/auth_cram.c, imap/auth_gss.c, protos.h
Commit date: 2018-07-15 08:21:44 -0700
Commit message: Check outbuf length in mutt_from_base64()

The obuf can be overflowed in auth_cram.c, and possibly auth_gss.c.

Thanks to Jeriko One for the bug report.

Diff: diff --git a/base64.c b/base64.c
index fd3ffb88a..120d4baa8 100644
--- a/base64.c
+++ b/base64.c
@@ -79,11 +79,11 @@ void mutt_to_base64 (unsigned char *out, const unsigned char *in, size_t len,
   *out = '\0';
 }
 
 /* Convert '\0'-terminated base 64 string to raw bytes.
  * Returns length of returned buffer, or -1 on error */
-int mutt_from_base64 (char *out, const char *in)
+int mutt_from_base64 (char *out, const char *in, size_t olen)
 {
   int len = 0;
   register unsigned char digit1, digit2, digit3, digit4;
 
   do
@@ -101,18 +101,24 @@ int mutt_from_base64 (char *out, const char *in)
     if (digit4 > 127 || ((digit4 != '=') && (base64val (digit4) == BAD)))
       return -1;
     in += 4;
 
     /* digits are already sanity-checked */
+    if (len == olen)
+      return len;
     *out++ = (base64val(digit1) << 2) | (base64val(digit2) >> 4);
     len++;
     if (digit3 != '=')
     {
+      if (len == olen)
+        return len;
       *out++ = ((base64val(digit2) << 4) & 0xf0) | (base64val(digit3) >> 2);
       len++;
       if (digit4 != '=')
       {
+        if (len == olen)
+          return len;
 	*out++ = ((base64val(digit3) << 6) & 0xc0) | base64val(digit4);
 	len++;
       }
     }
   }
diff --git a/imap/auth_cram.c b/imap/auth_cram.c
index 9b6db9af6..876172156 100644
--- a/imap/auth_cram.c
+++ b/imap/auth_cram.c
@@ -69,11 +69,11 @@ imap_auth_res_t imap_auth_cram_md5 (IMAP_DATA* idata, const char* method)
   {
     dprint (1, (debugfile, "Invalid response from server: %s\n", ibuf));
     goto bail;
   }
 
-  if ((len = mutt_from_base64 (obuf, idata->buf + 2)) == -1)
+  if ((len = mutt_from_base64 (obuf, idata->buf + 2, sizeof(obuf) - 1)) == -1)
   {
     dprint (1, (debugfile, "Error decoding base64 response.\n"));
     goto bail;
   }
 
diff --git a/imap/auth_gss.c b/imap/auth_gss.c
index a08e7c205..e14f4aac6 100644
--- a/imap/auth_gss.c
+++ b/imap/auth_gss.c
@@ -195,11 +195,11 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
       dprint (1, (debugfile, "Error receiving server response.\n"));
       gss_release_name (&min_stat, &target_name);
       goto bail;
     }
 
-    request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
+    request_buf.length = mutt_from_base64 (buf2, idata->buf + 2, sizeof(buf2));
     request_buf.value = buf2;
     sec_token = &request_buf;
 
     /* Write client data */
     maj_stat = gss_init_sec_context (&min_stat, GSS_C_NO_CREDENTIAL, &context,
@@ -231,11 +231,11 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
   if (rc != IMAP_CMD_RESPOND)
   {
     dprint (1, (debugfile, "Error receiving server response.\n"));
     goto bail;
   }
-  request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
+  request_buf.length = mutt_from_base64 (buf2, idata->buf + 2, sizeof(buf2));
   request_buf.value = buf2;
 
   maj_stat = gss_unwrap (&min_stat, context, &request_buf, &send_token,
     &cflags, &quality);
   if (maj_stat != GSS_S_COMPLETE)
diff --git a/protos.h b/protos.h
index 8bcda67fb..f933e9255 100644
--- a/protos.h
+++ b/protos.h
@@ -395,11 +395,11 @@ pid_t mutt_create_filter_fd (const char *, FILE **, FILE **, FILE **, int, int,
 
 ADDRESS *alias_reverse_lookup (ADDRESS *);
 
 /* base64.c */
 void mutt_to_base64 (unsigned char*, const unsigned char*, size_t, size_t);
-int mutt_from_base64 (char*, const char*);
+int mutt_from_base64 (char*, const char*, size_t);
 
 /* utf8.c */
 int mutt_wctoutf8 (char *s, unsigned int c, size_t buflen);
 
 #ifdef LOCALES_HACK

Vulnerable code: int mutt_from_base64 (char *out, const char *in)
  if ((len = mutt_from_base64 (obuf, idata->buf + 2)) == -1)
    request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
  request_buf.length = mutt_from_base64 (buf2, idata->buf + 2);
int mutt_from_base64 (char*, const char*);
Fixed code: int mutt_from_base64 (char *out, const char *in, size_t olen)
    if (len == olen)
      return len;
      if (len == olen)
        return len;
        if (len == olen)
          return len;
  if ((len = mutt_from_base64 (obuf, idata->buf + 2, sizeof(obuf) - 1)) == -1)
    request_buf.length = mutt_from_base64 (buf2, idata->buf + 2, sizeof(buf2));
  request_buf.length = mutt_from_base64 (buf2, idata->buf + 2, sizeof(buf2));
int mutt_from_base64 (char*, const char*, size_t);