Skip to content

fix(static): skip magic-number pread under directio (O_DIRECT EINVAL)#75

Merged
eilandert merged 1 commit into
masterfrom
fix/directio-probe
Jul 7, 2026
Merged

fix(static): skip magic-number pread under directio (O_DIRECT EINVAL)#75
eilandert merged 1 commit into
masterfrom
fix/directio-probe

Conversation

@eilandert

Copy link
Copy Markdown
Member

MAJOR from module audit.

Problem

With directio <size> configured, ngx_open_cached_file() opens the .zst with O_DIRECT once the file meets the threshold. The static handler then runs its 4-byte magic-number sanity probe with an unaligned pread() — but an O_DIRECT read requires buffer, offset and length all aligned to the device block size, so the probe fails EINVAL on every request. The handler treated that as a failed read → declined every .zst above the directio threshold and spammed NGX_LOG_CRIT once per request.

Fix

Skip the probe when of.is_directio. It is only a best-effort corruption guard; the precompressed file is still served, just without the magic check (which O_DIRECT alignment makes impossible to run cheaply here anyway).

Test

t/01-static.t TEST 26 — zstd_static on; directio 1; serves the .zst with 200 + Content-Encoding: zstd, no [error]/CRIT. O_DIRECT is filesystem-dependent, so the test deterministically catches the declined+CRIT regression where O_DIRECT engages and still asserts correct serving elsewhere. Verified to fail on pre-fix code.

Local: prove t/01-static.t = 303/303 PASS.

When "directio <size>" is configured, ngx_open_cached_file() opens the
.zst with O_DIRECT once the file meets the threshold. The static
handler's 4-byte magic-number sanity probe then issues a deliberately
tiny, unaligned pread() — but an O_DIRECT read requires buffer, offset
and length all aligned to the device block size, so the probe fails
EINVAL on every request. The handler treated that as a failed read and
declined, wrongly refusing to serve every .zst above the directio
threshold and spamming NGX_LOG_CRIT once per request.

The probe is only a best-effort corruption guard, so skip it when
of.is_directio: the precompressed file is still served, just without the
magic sanity check (which O_DIRECT alignment makes impossible to run
cheaply here anyway).

Test (t/01-static.t TEST 26): zstd_static on + directio 1 serves the
.zst with 200 + Content-Encoding: zstd and no [error]/CRIT. O_DIRECT is
filesystem-dependent, so the test deterministically catches the
declined+CRIT regression where O_DIRECT engages and still asserts
correct serving elsewhere. Verified to fail on pre-fix code.
@eilandert
eilandert merged commit 84fefce into master Jul 7, 2026
12 checks passed
@eilandert
eilandert deleted the fix/directio-probe branch July 7, 2026 19:46
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