CVE-2018-14359 (published 2018-07-17T17:29Z)

An issue was discovered in Mutt before 1.10.1 and NeoMutt before 2018-07-16. They have a buffer overflow via base64 data.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
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.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://gitlab.com/muttmua/mutt
commit_details: repo_url: https://gitlab.com/muttmua/mutt
author: Kevin McCarthy
author_date: 2018-07-13 14:25:28-07:00
msg: 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.
repository: repo_url: https://gitlab.com/muttmua/mutt
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: muttmua
file_changes: filename: base64.c
change_type: ModificationType.MODIFY
diff: @@ -81,7 +81,7 @@ void mutt_to_base64 (unsigned char *out, const unsigned char *in, size_t len,
 
 /* 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;
@@ -103,14 +103,20 @@ int mutt_from_base64 (char *out, const char *in)
     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++;
       }

programming_language: C
filename: auth_cram.c
change_type: ModificationType.MODIFY
diff: @@ -71,7 +71,7 @@ imap_auth_res_t imap_auth_cram_md5 (IMAP_DATA* idata, const char* method)
     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;

programming_language: C
filename: auth_gss.c
change_type: ModificationType.MODIFY
diff: @@ -197,7 +197,7 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
       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;
 
@@ -233,7 +233,7 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
     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,

programming_language: C
filename: protos.h
change_type: ModificationType.MODIFY
diff: @@ -397,7 +397,7 @@ 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);

programming_language: C
repo_url: https://github.com/neomutt/neomutt
commit_details: repo_url: https://github.com/neomutt/neomutt
author: Kevin McCarthy
author_date: 2018-07-13 14:25:28-07:00
msg: 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.
repository: repo_url: https://github.com/neomutt/neomutt
repo_name: neomutt/neomutt
description: ✉️ Teaching an Old Dog New Tricks -- IRC: #neomutt on irc.libera.chat
repo_language: C
owner: neomutt
file_changes: filename: auth_cram.c
change_type: ModificationType.MODIFY
diff: @@ -134,7 +134,7 @@ enum ImapAuthRes imap_auth_cram_md5(struct ImapData *idata, const char *method)
     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");

programming_language: C
filename: auth_gss.c
change_type: ModificationType.MODIFY
diff: @@ -203,7 +203,7 @@ enum ImapAuthRes imap_auth_gss(struct ImapData *idata, const char *method)
       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;
 
@@ -238,7 +238,7 @@ enum ImapAuthRes imap_auth_gss(struct ImapData *idata, const char *method)
     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);

programming_language: C
filename: base64.c
change_type: ModificationType.MODIFY
diff: @@ -117,8 +117,9 @@ size_t mutt_b64_encode(char *out, const char *cin, size_t len, size_t olen)
 
 /**
  * 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
  *
@@ -126,7 +127,7 @@ size_t mutt_b64_encode(char *out, const char *cin, size_t len, size_t olen)
  * 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;
@@ -148,14 +149,20 @@ int mutt_b64_decode(char *out, const char *in)
     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++;
       }

programming_language: JavaScript
filename: base64.h
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,6 @@ 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 */

programming_language: C
filename: rfc2047.c
change_type: ModificationType.MODIFY
diff: @@ -399,8 +399,9 @@ static char *rfc2047_decode_word(const char *s, size_t len, enum ContentEncoding
   }
   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);

programming_language: C
filename: base64.c
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,7 @@ 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);
@@ -59,7 +59,7 @@ void test_base64_lengths(void)
 
   /* 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);
@@ -76,7 +76,7 @@ void test_base64_lengths(void)
       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);

programming_language: C++