Summary
With CURLMOPT_NOTIFYFUNCTION + curl_multi_notify_enable(CURLMNOTIFY_EASY_DONE),
a perform round that queues 129 or more notifications silently stops delivering
notifications for the remaining lifetime of the multi handle, and the queued
chunks are leaked. curl_multi_cleanup() cannot reclaim them.
Root cause
mnfty_chunk_reset() (lib/multi_ntfy.c) does memset(chunk, 0, sizeof(*chunk)),
which clears chunk->next after the head chunk is drained. The parent loop in
Curl_mntfy_dispatch_all() then reads the already-cleared pointer:
multi->ntfy.head = chunk->next; /* NULL instead of the live second chunk */
head becomes NULL while tail still points at the queued second chunk, so the
dispatch loop never runs again and Curl_mntfy_cleanup() (which walks from
head) never frees the orphaned chunks.
Reproduction
Build curl from master (cdbe4eb) with AddressSanitizer and run:
./poc # n = 200 concurrent completions, leak detection on
./poc 128 # control: exactly one chunk, no leak
with ASAN_OPTIONS=detect_leaks=1.
Observed (n = 200):
completions=200 notifications_delivered=129
Direct leak of 1048 byte(s) in 1 object(s) allocated from:
#1 mnfty_chunk_create lib/multi_ntfy.c:54
Control (n = 128): 129 notifications delivered, no leak report.
Impact
The notification facility stops working silently for that handle (the
application never learns about it) and the orphaned chunk plus any later
appended chunks are never freed.
Suggested fix
Only reset the cursors, not the link pointer:
static void mnfty_chunk_reset(struct mntfy_chunk *chunk)
{
- memset(chunk, 0, sizeof(*chunk));
+ chunk->r_offset = 0;
+ chunk->w_offset = 0;
}
(Equivalently, capture chunk->next before resetting it.)
Materials
poc.c (harness), reproduce.sh (clones and builds master with ASan) and
build.md are attached as curl_04_ntfy_chunk_leak_lsan_poc.zip.
Version
Affected release: curl 8.22.0 (2026-09-02, tag curl-8_22_0) - reproduced
on a fresh ASan build of the release tag. Also verified on current master
cdbe4eb (2026-09-15). lib/multi_ntfy.c is unchanged since 8.22.0.
Summary
With
CURLMOPT_NOTIFYFUNCTION+curl_multi_notify_enable(CURLMNOTIFY_EASY_DONE),a perform round that queues 129 or more notifications silently stops delivering
notifications for the remaining lifetime of the multi handle, and the queued
chunks are leaked.
curl_multi_cleanup()cannot reclaim them.Root cause
mnfty_chunk_reset()(lib/multi_ntfy.c) doesmemset(chunk, 0, sizeof(*chunk)),which clears
chunk->nextafter the head chunk is drained. The parent loop inCurl_mntfy_dispatch_all()then reads the already-cleared pointer:headbecomes NULL whiletailstill points at the queued second chunk, so thedispatch loop never runs again and
Curl_mntfy_cleanup()(which walks fromhead) never frees the orphaned chunks.Reproduction
Build curl from master (
cdbe4eb) with AddressSanitizer and run:with
ASAN_OPTIONS=detect_leaks=1.Observed (n = 200):
Control (n = 128): 129 notifications delivered, no leak report.
Impact
The notification facility stops working silently for that handle (the
application never learns about it) and the orphaned chunk plus any later
appended chunks are never freed.
Suggested fix
Only reset the cursors, not the link pointer:
(Equivalently, capture
chunk->nextbefore resetting it.)Materials
poc.c(harness),reproduce.sh(clones and builds master with ASan) andbuild.mdare attached ascurl_04_ntfy_chunk_leak_lsan_poc.zip.Version
Affected release: curl 8.22.0 (2026-09-02, tag
curl-8_22_0) - reproducedon a fresh ASan build of the release tag. Also verified on current master
cdbe4eb(2026-09-15).lib/multi_ntfy.cis unchanged since 8.22.0.