Fix NULL deref in usUntilEarliestTimer when all time events are deleted (#15391)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run

Fixes #13992

## The problem

`usUntilEarliestTimer()` in `src/ae.c` scans the time event list for the
nearest non-deleted timer. It already guards the empty-list case (`if
(te == NULL) return -1;`), but when the list is non-empty yet every
event is marked `AE_DELETED_EVENT_ID`, the loop finishes with `earliest
== NULL` and the subsequent `earliest->when` access is a NULL pointer
dereference.

## The fix

Add `if (earliest == NULL) return -1;` after the scan, mirroring the
existing empty-list guard.

`-1` is the correct value here rather than `0`: the caller in
`aeProcessEvents()` treats a negative result as "no timer to wait for"
and leaves the poll timeout unset (blocking on I/O), whereas `0` would
make it poll with a zero timeout and busy-loop until the deleted events
are reclaimed.

Note: Harmless in practice — Redis always has the self-rearming
serverCron timer, so earliest is never NULL. This is defensive hardening
for the generic ae.c loop rather than a real crash fix.
This commit is contained in:
Vismay 2026-06-30 06:11:44 +05:30 committed by GitHub
parent 5b5c32663b
commit 0e618fafdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -271,6 +271,11 @@ static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
te = te->next;
}
/* The list may hold only events marked AE_DELETED_EVENT_ID, leaving no
* earliest timer. Mirror the empty-list case above and report "no timer"
* instead of dereferencing a NULL earliest. */
if (earliest == NULL) return -1;
monotime now = getMonotonicUs();
return (now >= earliest->when) ? 0 : earliest->when - now;
}