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://github.com/neomutt/neomutt
Language: C
Files changed: imap/auth_cram.c, imap/auth_gss.c, mutt/base64.c, mutt/base64.h, mutt/rfc2047.c, test/base64.c
Commit date: 2018-07-15 23:33:06 +0100
Commit message: Check outbuf length in mutt_to_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/imap/auth_cram.c b/imap/auth_cram.c
index 218406fdb0..c33b05f8c1 100644
--- a/imap/auth_cram.c
+++ b/imap/auth_cram.c
@@ -132,11 +132,11 @@ enum ImapAuthRes imap_auth_cram_md5(struct ImapData *idata, const char *method)
   {
     mutt_debug(1, "Invalid response from server: %s\n", ibuf);
     goto bail;
   }
 
-  len = mutt_b64_decode(obuf, idata->buf + 2);
+  len = mutt_b64_decode(obuf, idata->buf + 2, sizeof(obuf));
   if (len == -1)
   {
     mutt_debug(1, "Error decoding base64 response.\n");
     goto bail;
   }
diff --git a/imap/auth_gss.c b/imap/auth_gss.c
index 6870eeb67c..d5ff2f122b 100644
--- a/imap/auth_gss.c
+++ b/imap/auth_gss.c
@@ -201,11 +201,11 @@ enum ImapAuthRes imap_auth_gss(struct ImapData *idata, const char *method)
       mutt_debug(1, "#1 Error receiving server response.\n");
       gss_release_name(&min_stat, &target_name);
       goto bail;
     }
 
-    request_buf.length = mutt_b64_decode(buf2, idata->buf + 2);
+    request_buf.length = mutt_b64_decode(buf2, idata->buf + 2, sizeof(buf2));
     request_buf.value = buf2;
     sec_token = &request_buf;
 
     /* Write client data */
     maj_stat = gss_init_sec_context(
@@ -236,11 +236,11 @@ enum ImapAuthRes imap_auth_gss(struct ImapData *idata, const char *method)
   if (rc != IMAP_CMD_RESPOND)
   {
     mutt_debug(1, "#2 Error receiving server response.\n");
     goto bail;
   }
-  request_buf.length = mutt_b64_decode(buf2, idata->buf + 2);
+  request_buf.length = mutt_b64_decode(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/mutt/base64.c b/mutt/base64.c
index d76e5030e9..8ae9a60397 100644
--- a/mutt/base64.c
+++ b/mutt/base64.c
@@ -115,20 +115,21 @@ size_t mutt_b64_encode(char *out, const char *cin, size_t len, size_t olen)
   return (out - (char *) begin);
 }
 
 /**
  * mutt_b64_decode - Convert null-terminated base64 string to raw bytes
- * @param out Output buffer for the raw bytes
- * @param in  Input  buffer for the null-terminated base64-encoded string
+ * @param out  Output buffer for the raw bytes
+ * @param in   Input  buffer for the null-terminated base64-encoded string
+ * @param olen Length of the output buffer
  * @retval num Success, bytes written
  * @retval -1  Error
  *
  * This function performs base64 decoding. The resulting buffer is NOT
  * null-terminated. If the input buffer contains invalid base64 characters,
  * this function returns -1.
  */
-int mutt_b64_decode(char *out, const char *in)
+int mutt_b64_decode(char *out, const char *in, size_t olen)
 {
   int len = 0;
   unsigned char digit4;
 
   do
@@ -146,18 +147,24 @@ int mutt_b64_decode(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++;
       }
     }
   } while (*in && digit4 != '=');
diff --git a/mutt/base64.h b/mutt/base64.h
index b688a79a0a..8e87400d6c 100644
--- a/mutt/base64.h
+++ b/mutt/base64.h
@@ -26,8 +26,8 @@
 extern const int Index64[];
 
 #define base64val(c) Index64[(unsigned int) (c)]
 
 size_t mutt_b64_encode(char *out, const char *cin, size_t len, size_t olen);
-int    mutt_b64_decode(char *out, const char *in);
+int    mutt_b64_decode(char *out, const char *in, size_t olen);
 
 #endif /* _MUTT_BASE64_H */
diff --git a/mutt/rfc2047.c b/mutt/rfc2047.c
index bf248364b5..550823a019 100644
--- a/mutt/rfc2047.c
+++ b/mutt/rfc2047.c
@@ -397,12 +397,13 @@ static char *rfc2047_decode_word(const char *s, size_t len, enum ContentEncoding
     mutt_buffer_addch(&buf, '\0');
     return buf.data;
   }
   else if (enc == ENCBASE64)
   {
-    char *out = mutt_mem_malloc(3 * len / 4 + 1);
-    int dlen = mutt_b64_decode(out, it);
+    const int olen = 3 * len / 4 + 1;
+    char *out = mutt_mem_malloc(olen);
+    int dlen = mutt_b64_decode(out, it, olen);
     if (dlen == -1)
     {
       FREE(&out);
       return NULL;
     }
diff --git a/test/base64.c b/test/base64.c
index 5d6c221d61..f8b77dbc7e 100644
--- a/test/base64.c
+++ b/test/base64.c
@@ -25,11 +25,11 @@ void test_base64_encode(void)
 }
 
 void test_base64_decode(void)
 {
   char buffer[16];
-  int len = mutt_b64_decode(buffer, encoded);
+  int len = mutt_b64_decode(buffer, encoded, sizeof(buffer));
   if (!TEST_CHECK(len == sizeof(clear) - 1))
   {
     TEST_MSG("Expected: %zu", sizeof(clear) - 1);
     TEST_MSG("Actual  : %zu", len);
   }
@@ -57,11 +57,11 @@ void test_base64_lengths(void)
     TEST_MSG("Actual  : %zu", enclen);
   }
 
   /* Decoding a zero-length string should fail, too */
   out1[0] = '\0';
-  declen = mutt_b64_decode(out2, out1);
+  declen = mutt_b64_decode(out2, out1, sizeof(out2));
   if (!TEST_CHECK(declen == -1))
   {
     TEST_MSG("Expected: %zu", -1);
     TEST_MSG("Actual  : %zu", declen);
   }
@@ -74,11 +74,11 @@ void test_base64_lengths(void)
     if (!TEST_CHECK(enclen == exp))
     {
       TEST_MSG("Expected: %zu", exp);
       TEST_MSG("Actual  : %zu", enclen);
     }
-    declen = mutt_b64_decode(out2, out1);
+    declen = mutt_b64_decode(out2, out1, sizeof(out2));
     if (!TEST_CHECK(declen == i))
     {
       TEST_MSG("Expected: %zu", i);
       TEST_MSG("Actual  : %zu", declen);
     }

Vulnerable code:   len = mutt_b64_decode(obuf, idata->buf + 2);
    request_buf.length = mutt_b64_decode(buf2, idata->buf + 2);
  request_buf.length = mutt_b64_decode(buf2, idata->buf + 2);
 * @param out Output buffer for the raw bytes
 * @param in  Input  buffer for the null-terminated base64-encoded string
int mutt_b64_decode(char *out, const char *in)
int    mutt_b64_decode(char *out, const char *in);
    char *out = mutt_mem_malloc(3 * len / 4 + 1);
    int dlen = mutt_b64_decode(out, it);
  int len = mutt_b64_decode(buffer, encoded);
  declen = mutt_b64_decode(out2, out1);
    declen = mutt_b64_decode(out2, out1);
Fixed code:   len = mutt_b64_decode(obuf, idata->buf + 2, sizeof(obuf));
    request_buf.length = mutt_b64_decode(buf2, idata->buf + 2, sizeof(buf2));
  request_buf.length = mutt_b64_decode(buf2, idata->buf + 2, sizeof(buf2));
 * @param out  Output buffer for the raw bytes
 * @param in   Input  buffer for the null-terminated base64-encoded string
 * @param olen Length of the output buffer
int mutt_b64_decode(char *out, const char *in, size_t olen)
    if (len == olen)
      return len;
      if (len == olen)
        return len;
        if (len == olen)
          return len;
int    mutt_b64_decode(char *out, const char *in, size_t olen);
    const int olen = 3 * len / 4 + 1;
    char *out = mutt_mem_malloc(olen);
    int dlen = mutt_b64_decode(out, it, olen);
  int len = mutt_b64_decode(buffer, encoded, sizeof(buffer));
  declen = mutt_b64_decode(out2, out1, sizeof(out2));
    declen = mutt_b64_decode(out2, out1, sizeof(out2));