Skip to content

Fix remote stack overflow and uninitialized read in Content-Type/Content-Encoding parsing - #506

Merged
xroche merged 1 commit into
masterfrom
fix-header-sscanf-overflow
Jul 8, 2026
Merged

Fix remote stack overflow and uninitialized read in Content-Type/Content-Encoding parsing#506
xroche merged 1 commit into
masterfrom
fix-header-sscanf-overflow

Conversation

@xroche

@xroche xroche commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Two memory-safety bugs in the HTTP response-header parser treathead(), both reachable from any crawl that fetches a URL from a hostile or compromised server.

First, a remote stack buffer overflow: Content-Type and Content-Encoding values are read with a width-less sscanf("%s") into an 1100-byte stack scratch buffer, with the length check happening only after the copy. The back_wait receive path hands treathead a line buffer of up to 2000 bytes (htsback.c rcvd[2048], binput capped at 2000), so a header value longer than 1099 non-whitespace bytes smashes the stack. Both scans are now bounded to the buffer (%1099s), matching the %31s/%255s idiom already used in the request-line parsers.

Second, the Content-Encoding branch ignored the sscanf return value, so an empty value (e.g. a bare Content-Encoding: header) left the scratch buffer uninitialized and then ran strlen() over it, an uninitialized read that can also over-read past the buffer. The Content-Type branch already guarded this case; this mirrors the guard.

Found via the P3-3 static-analysis baseline. The new -#test=headerlong self-test plus empty-value cases in 01_engine-header.test build the hostile inputs inside the handler (the CLI caps argument length) and abort on the pre-fix binary under AddressSanitizer (the overflow) and MemorySanitizer (the uninitialized read); both sanitizer CI jobs cover the regression.

@xroche
xroche force-pushed the fix-header-sscanf-overflow branch from b74bd54 to a3db365 Compare July 8, 2026 05:24
…arsing

treathead() parsed the Content-Type and Content-Encoding response-header
values with a width-less sscanf("%s") into a 1100-byte stack scratch,
bounds-checking the result only after the copy. The receive path in
back_wait hands treathead a line buffer of up to 2000 bytes (htsback.c
rcvd[2048], binput cap 2000), so a hostile server sending a header value
longer than 1099 non-whitespace bytes overflowed the stack. Bound both
scans to the buffer (%1099s), matching the existing %31s/%255s idiom in
the request-line parsers.

The Content-Encoding branch also ignored the sscanf return, so an empty
value (e.g. "Content-Encoding:") left tempo uninitialized and then ran
strlen() over it: an uninitialized read that can also over-read past the
buffer. The Content-Type branch already guarded this; mirror it.

Found via the P3-3 static-analysis baseline. New -#test=headerlong plus
empty-value cases in 01_engine-header.test build the hostile inputs in
the handler (the CLI caps argument length); they abort on the pre-fix
binary under ASan (overflow) and MSan (uninitialized read).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche force-pushed the fix-header-sscanf-overflow branch from a3db365 to 95d0fe7 Compare July 8, 2026 06:46
@xroche xroche changed the title Fix remote stack overflow parsing over-long Content-Type/Content-Encoding Fix remote stack overflow and uninitialized read in Content-Type/Content-Encoding parsing Jul 8, 2026
@xroche
xroche merged commit fd2252d into master Jul 8, 2026
18 checks passed
xroche added a commit that referenced this pull request Jul 8, 2026
P3-5 fuzz Tier-2. Two more harnesses over hostile-input parsers that read
structured bytes into fixed buffers: fuzz-header drives treatfirstline plus
treathead on each response-header line (the Content-Type/-Encoding path hardened
in #506, and the cookie/Location/Content-Range fields); fuzz-cachendx drives the
hts-cache/*.ndx length-prefixed scan that cache_readex_new loads on --update.

fuzz-cachendx found the over-advance fixed in the parent commit; its seed corpus
carries the two crash reproducers as replay regressions. The cache harness stops
at the scan rather than the trailing coucal insert, whose murmur hash trips a
separate pointer-overflow the .ndx parser does not own.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
xroche added a commit that referenced this pull request Jul 8, 2026
P3-5 fuzz Tier-2. Two more harnesses over hostile-input parsers that read
structured bytes into fixed buffers: fuzz-header drives treatfirstline plus
treathead on each response-header line (the Content-Type/-Encoding path hardened
in #506, and the cookie/Location/Content-Range fields); fuzz-cachendx drives the
hts-cache/*.ndx length-prefixed scan that cache_readex_new loads on --update.

fuzz-cachendx found the over-advance fixed in the parent commit; its seed corpus
carries the two crash reproducers as replay regressions. The cache harness stops
at the scan rather than the trailing coucal insert, whose murmur hash trips a
separate pointer-overflow the .ndx parser does not own.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
xroche added a commit that referenced this pull request Jul 8, 2026
…508)

P3-5 fuzz Tier-2. Two more harnesses over hostile-input parsers that read
structured bytes into fixed buffers: fuzz-header drives treatfirstline plus
treathead on each response-header line (the Content-Type/-Encoding path hardened
in #506, and the cookie/Location/Content-Range fields); fuzz-cachendx drives the
hts-cache/*.ndx length-prefixed scan that cache_readex_new loads on --update.

fuzz-cachendx found the over-advance fixed in the parent commit; its seed corpus
carries the two crash reproducers as replay regressions. The cache harness stops
at the scan rather than the trailing coucal insert, whose murmur hash trips a
separate pointer-overflow the .ndx parser does not own.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xroche added a commit that referenced this pull request Jul 8, 2026
…reland) (#509)

* Add libFuzzer harnesses for the HTTP header and cache-index parsers

P3-5 fuzz Tier-2. Two more harnesses over hostile-input parsers that read
structured bytes into fixed buffers: fuzz-header drives treatfirstline plus
treathead on each response-header line (the Content-Type/-Encoding path hardened
in #506, and the cookie/Location/Content-Range fields); fuzz-cachendx drives the
hts-cache/*.ndx length-prefixed scan that cache_readex_new loads on --update.

fuzz-cachendx found the over-advance fixed in the parent commit; its seed corpus
carries the two crash reproducers as replay regressions. The cache harness stops
at the scan rather than the trailing coucal insert, whose murmur hash trips a
separate pointer-overflow the .ndx parser does not own.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Correct copyright year on the new fuzz harnesses (2026)

fuzz-header.c and fuzz-cachendx.c are new in the 2026 audit cycle;
match the sibling *_selftest.c files rather than the boilerplate 1998.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@xroche
xroche deleted the fix-header-sscanf-overflow branch July 27, 2026 18:25
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