Skip to content

Harness: zero-pad the C input so libinjection's over-reads are defined - #4

Merged
ndreno merged 1 commit into
mainfrom
fix/harness-pad-c-overread
Sep 12, 2026
Merged

ndreno merged 1 commit into
mainfrom
fix/harness-pad-c-overread

Conversation

@ndreno

@ndreno ndreno commented Sep 12, 2026

Copy link
Copy Markdown

The differential fuzzer's XSS campaign reported a divergence (Rust: false, C: true) that would not reproduce locally (C: false) on byte-identical input.

Root cause: libinjection reads past the end of the input buffer. ASan
confirms a stack-buffer-overflow (out-of-bounds read) in htmlencode_startswith,
reached from libinjection_is_xss, on a URL-attribute value ending in an
incomplete HTML entity (e.g. …&#X). Past the buffer the bytes are
indeterminate, so C's verdict depends on the build and stack layout: true on
the CI Linux build, false on macOS. The memory-safe port reads within bounds
and returns a deterministic answer.

Fix

The FFI harness now copies the input into a zeroed, over-allocated buffer before
calling libinjection (both detectors), so any over-read lands on defined zero
bytes. The differential compares against a deterministic C answer, and the fuzz
gate reports genuine parse differences rather than C reading garbage. A
dedicated test pins the input that surfaced this.

Result

  • Full corpus differential stays at 0 (verdicts and fingerprints).
  • The reported input now agrees (false / false).
  • README and CHANGELOG updated, including the char-semantics classes the fuzzer
    found earlier (NUL handling across the scans and whitespace checks,
    sp_password/collate over raw bytes, and the @-stripped variable value).

The out-of-bounds read is a bug in upstream libinjection; a separate report to
that project is worthwhile.

libinjection reads a few bytes past the end of the input on some
adversarial inputs: an ASan-confirmed out-of-bounds read in
htmlencode_startswith, reached from libinjection_is_xss, on a
URL-attribute value ending in an incomplete HTML entity. Past the buffer
the bytes are indeterminate, so the C verdict is not reproducible across
builds (the differential fuzzer saw "C: true" on the CI Linux build and
"C: false" locally on byte-identical input).

Copy the input into a zeroed, over-allocated buffer before handing it to
libinjection, for both detectors, so any such read lands on defined zero
bytes. The differential then compares against a stable C answer rather
than stack garbage, and the memory-safe port (which never reads past the
input) matches it. A dedicated test pins the input that surfaced this.

Full corpus differential stays at 0. README and CHANGELOG updated,
including the further char-semantics fixes the fuzzer surfaced.
@ndreno
ndreno merged commit 70ab33c into main Sep 12, 2026
4 checks passed
@ndreno
ndreno deleted the fix/harness-pad-c-overread branch September 12, 2026 09:53
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