ADR-0382: Y4M header parser — reject non-positive width or height before allocation¶
- Status: Accepted
- Date: 2026-05-10
- Deciders: lusoris, Claude (Anthropic)
- Tags:
security,fuzz,parser,fork-local
Context¶
The nightly fuzz.yml fuzz_y4m_input job reported an AddressSanitizer SEGV on address 0x000000000000 inside fread, called from y4m_input_fetch_frame (core/tools/y4m_input.c), for every scheduled run from 2026-05-05 through 2026-05-08 (T-FUZZ-Y4M-NEG-WIDTH-SEGV). The reproducer header YUV4MPEG2 W-8 H4 F30:1 Ip A1:1 C422 passes the tag scanner (y4m_parse_tags) because sscanf(p+1, "%d", &_y4m->pic_w) accepts negative decimal literals. The subsequent size arithmetic in y4m_input_open_impl wraps or yields a non-positive product, so malloc either receives a huge wrapped size and returns NULL, or receives zero and returns a non-NULL stub that is too small. Either way _y4m->dst_buf is unusable, yet y4m_input_fetch_frame calls fread(_y4m->dst_buf, 1, _y4m->dst_buf_read_sz, _fin) unconditionally — producing the NULL-deref SEGV.
This crash is distinct from the Y4M-411-OOB heap-buffer-overflow fixed by PR #357 / commit 05ba29a6 at line 507 of y4m_convert_411_422jpeg. That fix added bounds checks inside a conversion routine; this fix gates the entire open path on valid dimensions before any allocation occurs.
Decision¶
Add a dimension validation guard in y4m_input_open_impl, immediately after y4m_parse_tags returns successfully and before the chroma-type dispatch block. If pic_w <= 0 or pic_h <= 0, print a diagnostic that includes the rejected values and return -1. No allocation is attempted; the caller (y4m_input_open) propagates the error and frees the outer struct.
The guard is placed at the earliest safe point: after the tags are parsed (so the values are available) and before any arithmetic that uses them (so no wrap-around can reach malloc). This satisfies NASA/JPL Power of 10 rule 7 (check the return value / validity of every data-dependent computation before acting on it) and SEI CERT C INT32-C (avoid signed integer overflow in size arithmetic).
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Add guard in y4m_parse_tags directly at the case 'W' / case 'H' sites | Fails early, inside the scanner | Splits the "semantic validation" concern from "syntactic parsing"; sscanf may legitimately return a negative value for signed tag fields in other contexts | Rejected — cleaner to keep the scanner as a pure lexer and validate semantics at the open-impl level |
Guard malloc result and skip fread when dst_buf == NULL | Avoids the SEGV without touching the parser | Masks the root cause; a NULL dst_buf with non-zero dst_buf_read_sz is never valid; any future code path could re-introduce the dereference | Rejected — defensive allocation-return checks do not substitute for input validation |
| Catch SIGSEGV with a signal handler in the fuzz harness | Prevents the fuzzer from crashing | Signal handlers are unsafe in ASAN mode; silences a real bug rather than fixing it | Rejected explicitly per task constraint |
Consequences¶
- Positive: The fuzz harness no longer crashes on negative-dimension inputs; the nightly
fuzz_y4m_inputCI leg returns to green. The error message prints the rejected W/H values so operators can identify malformed sources in production logs. - Positive: The reproducer byte sequence is promoted to
core/test/fuzz/y4m_input_known_crashes/y4m_neg_width_null_deref.y4m— a permanent regression seed that libFuzzer replays on every run. - Negative: Callers that previously received a SEGV now receive a clean
-1error return and astderrmessage. This is a strict improvement; no valid Y4M source has W=0 or W<0. - Neutral: The fix touches only
y4m_input_open_impl(an internal static function) — no public API change, no ffmpeg-patches impact, no rebase-notes entry needed.
References¶
- T-FUZZ-Y4M-NEG-WIDTH-SEGV row in
docs/state.md. - ADR-0332 (
docs/adr/0332-nightly-fuzz-triage-keep-gates.md) — policy to keep the fuzz workflow on while bugs are open. - Prior Y4M parser fix: PR #357 / commit
05ba29a6(y4m_convert_411_422jpeg1-byte heap-buffer-overflow, ADR-0228). - Crash artefact:
gh run download 25538384046 --repo VMAFx/vmafx --name fuzz_y4m_input-crashes-25538384046; shacrash-645a8f241b71d80ff496c10984d9b493d03dbfe1. - req: "Fix T-FUZZ-Y4M-NEG-WIDTH-SEGV: real ASan-detected SEGV in the Y4M parser ... reject W <= 0 / H <= 0 in the YUV4MPEG header parser before any allocation."