----- Forwarded message from Acts1631 <[email protected]> -----
Date: Wed, 17 Jun 2026 20:16:18 +0000 From: Acts1631 <[email protected]> To: "[email protected]" <[email protected]> Subject: report 3/3: latent unterminated buffer in imap_wordcasecmp() X-Spam-score: 0.0 X-Delivered-to: [email protected] Message-ID: <ZJud85dInL0tNywwnZ6vrX_6gMApaSGaf5NjsqED3dGpZ90dK9EcV-n0dDrJp6GSOaRAE-OxwG_cXHhjKs6MJfTHncpVUwoyTMGzd_ZPbDY=@proton.me> imap_wordcasecmp() copies one word from an IMAP response into a stack buffer and compares it case-insensitively: char tmp[SHORT_STRING]; ... tmp[SHORT_STRING-1] = 0; for (i=0;i < SHORT_STRING-2;i++,s++) { if (!*s || IS_ASCII_WS(*s)) { tmp[i] = 0; break; } tmp[i] = *s; } tmp[i+1] = 0; With SHORT_STRING == 128, an input word of at least 126 non-whitespace characters fills tmp[0] through tmp[125]. The loop exits with i == 126 and then writes tmp[127] = 0. This leaves tmp[126] uninitialized, meaning tmp is not properly terminated. The only current caller in the tree is cmd_parse_capability(): if (imap_wordcasecmp(Capabilities[x], s) == 0) Since Capabilities[x] elements are short fixed strings (e.g., "IMAP4rev1", "CONDSTORE"), ascii_strcasecmp() will return before reading the uninitialized tmp[126] byte. However, the helper is buggy in isolation. Proposed fix: Zero-initialize tmp and simplify the copy loop: memset(tmp, 0, sizeof(tmp)); for (i = 0; i < SHORT_STRING - 2 && *s && !IS_ASCII_WS(*s); i++, s++) tmp[i] = *s; This ensures tmp is always fully initialized and correctly NUL-terminated. ----- End forwarded message ----- -- Kevin J. McCarthy GPG Fingerprint: 8975 A9B3 3AA3 7910 385C 5308 ADEF 7684 8031 6BDA
diff --git a/imap/util.c b/imap/util.c
index 9b6ef7b4..44cdfca9 100644
--- a/imap/util.c
+++ b/imap/util.c
@@ -887,17 +887,9 @@ int imap_wordcasecmp(const char *a, const char *b)
const char *s = b;
int i;
- tmp[SHORT_STRING-1] = 0;
- for (i=0;i < SHORT_STRING-2;i++,s++)
- {
- if (!*s || IS_ASCII_WS(*s))
- {
- tmp[i] = 0;
- break;
- }
+ memset(tmp, 0, sizeof(tmp));
+ for (i = 0; i < SHORT_STRING - 2 && *s && !IS_ASCII_WS(*s); i++, s++)
tmp[i] = *s;
- }
- tmp[i+1] = 0;
return ascii_strcasecmp(a, tmp);
}
#!/usr/bin/env python3
"""
Standalone demonstrator for Bug 3's latent imap_wordcasecmp() termination
bug.
The earlier network reproducer was not valid: in the real mutt capability
parser, imap_wordcasecmp() is only called with short fixed capability names
as its first argument, so a malicious unknown 126-byte capability word does
not force ascii_strcasecmp() to read tmp[126].
This script writes a small C harness to /tmp that recreates the vulnerable
helper and compares it against a 126-byte string, which is the condition
needed to force a read of the uninitialized tmp[126] byte. Detecting the
read requires a tool that tracks uninitialized memory, such as Clang
MemorySanitizer or Valgrind Memcheck.
Usage:
python3 reproducer_imap_wordcase.py
clang -fsanitize=memory -g -O1 /tmp/imap_wordcasecmp_repro.c -o /tmp/imap_wordcasecmp_repro
/tmp/imap_wordcasecmp_repro
"""
from pathlib import Path
SOURCE = r'''
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#define SHORT_STRING 128
#define IS_ASCII_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n')
static int ascii_tolower_local(int c)
{
if (c >= 'A' && c <= 'Z')
return c | 32;
return c;
}
static int ascii_strcasecmp_local(const char *a, const char *b)
{
int i;
for (;; a++, b++)
{
if ((i = ascii_tolower_local((unsigned char)*a) -
ascii_tolower_local((unsigned char)*b)))
return i;
if (!*a)
break;
}
return 0;
}
static int vulnerable_imap_wordcasecmp(const char *a, const char *b)
{
char tmp[SHORT_STRING];
const char *s = b;
int i;
tmp[SHORT_STRING-1] = 0;
for (i=0;i < SHORT_STRING-2;i++,s++)
{
if (!*s || IS_ASCII_WS(*s))
{
tmp[i] = 0;
break;
}
tmp[i] = *s;
}
tmp[i+1] = 0;
return ascii_strcasecmp_local(a, tmp);
}
int main(void)
{
char a[SHORT_STRING];
char b[SHORT_STRING];
memset(a, 'A', SHORT_STRING - 2);
a[SHORT_STRING - 2] = '\0';
a[SHORT_STRING - 1] = '\0';
memset(b, 'A', SHORT_STRING - 2);
b[SHORT_STRING - 2] = '\0';
b[SHORT_STRING - 1] = '\0';
printf("result=%d\n", vulnerable_imap_wordcasecmp(a, b));
return 0;
}
'''
def main():
path = Path("/tmp/imap_wordcasecmp_repro.c")
path.write_text(SOURCE)
print(f"Wrote {path}")
print("Compile with: clang -fsanitize=memory -g -O1 /tmp/imap_wordcasecmp_repro.c -o /tmp/imap_wordcasecmp_repro")
if __name__ == "__main__":
main()
signature.asc
Description: PGP signature
