Skip to content

timer: pad Data so the ngx_event_ident debug read stays in bounds#31

Open
climagabriel wants to merge 1 commit into
mainfrom
fastedge-timer-data-ident-overread
Open

timer: pad Data so the ngx_event_ident debug read stays in bounds#31
climagabriel wants to merge 1 commit into
mainfrom
fastedge-timer-data-ident-overread

Conversation

@climagabriel

Copy link
Copy Markdown
Collaborator

Bug

Timer::start sets ev->data to a Box<Data<HandlerFn>> (observed 24 bytes; can be as small as 8 with a zero-sized handler). nginx's timer machinery assumes ev->data points at an ngx_connection_t for debug logging: ngx_event_expire_timers (and ngx_event_add_timer/ngx_event_del_timer) evaluate ngx_event_ident(ev->data) = ((ngx_connection_t *) data)->fd — a 4-byte read at offset 24.

On a --with-debug binary with any debug-level log sink configured (gcore edges all carry error_log memory:32m debug), that read executes on every timer expiry and lands past the Data allocation:

  • plain builds: harmless out-of-bounds read, garbage fd in a debug line nobody sees
  • ASan builds: fatal heap-buffer-overflow, worker aborts on the first fastedge timer expiry, master respawns, repeat — a continuous worker crash loop
==28187==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 4
    #0 ngx_event_expire_timers src/event/ngx_event_timer.c:78
    #1 ngx_process_events_and_timers src/event/ngx_event.c:263
0x...ad8 is located 0 bytes to the right of 24-byte region
allocated by ... nginx_module::timer::Timer<HandlerFn>::start nginx-rust/nginx_module/src/timer.rs:48

Found by the EPX-151 ASan package variant (1.30.2-9.epx151.asan.1) on the am3 loadtest edge: ~2500 worker aborts in 4 minutes, all identical.

Fix

Pad Data so bytes 24..28 of the allocation are always in bounds. Minimal-diff alternative to restructuring the event+data into one #[repr(C)] box; nginx's own non-connection ev->data users (resolver, cache manager) are safe only because their structs happen to be large enough — this makes fastedge's explicitly so.

nginx debug logging evaluates ngx_event_ident(ev->data) on every timer
expiry, reading ((ngx_connection_t *) data)->fd — 4 bytes at offset 24.
Timer sets ev->data to a Box<Data<HandlerFn>> that can be as small as
8 bytes (24 observed), so the read lands past the allocation: benign
garbage in a debug line on plain builds, fatal heap-buffer-overflow
under AddressSanitizer (worker abort on first expiry on any node with
'error_log memory:32m debug' and a --with-debug binary). Pad Data to
keep offset 24..28 readable.
@climagabriel climagabriel requested a review from ruslanti July 10, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant