Repository navigation
✨ feat(fuzz): drive Atheris with a failure header - #1234
Merged
Merged
Conversation
The Atheris gap check compared owners against a hand-kept module list, so a new public module would get no fuzz target and the check would still pass. Walking the package picks up each module with no underscore in its path that declares __all__, and the owner check then fails until a target claims its exports.
The Atheris driver passed raw bytes to each target, so no run could fail an allocation, and coverage feedback did not reach the cleanup paths after a failed PyMem allocation. The fuzz build already has a hook that fails the Nth one. Each input now starts with libxml2's [opts][failure_pos][max_chunk] header. The driver bounds failure_pos by the input size plus 100, as xml.c does, and opens the injection window around the target. When the hook fires, the target must raise MemoryError; a MemoryError without a fired hook counts as a finding too. The bridge's custom mutator follows xmlFuzzMutateChunks and picks one header field or the payload per call, so mutations keep the fields in place. Seeds get libxml2's seed header, and the atheris env builds with -Dfuzzing=true to get the hook.
IncrementalParser, Tokenizer, EncodingDetector and the stdlib HTMLParser shim ran one unit per feed, so coverage feedback could not steer chunk boundaries, and the detector and HTMLParser saw a fixed hex envelope in place of the fuzz bytes. Targets with an incremental API now feed the payload in max_chunk pieces with an empty feed around each, capped at libxml2's 50 + len/100 pieces, and compare the result with a one-shot call. Bytes APIs split inside UTF-8 sequences whenever a chunk edge lands there. The opts bits move the IncrementalParser and Tokenizer options off their defaults, so the mutator reaches the option combinations too.
Seven round-trip oracles ran only in fuzz.py's generational round-trip mode, out of reach of coverage-guided inputs. They cover the HTML and XML fixpoints, source spans, StyleDeclaration re-reads, XPath and CSS entry-point agreement, and URL split and recompose. The document, schema, CSS object model and selector targets now run those oracles on the input they already decode, and the URL check adds the reparse oracle. An out-of-scope case skips only that oracle, so the input keeps its other checks and its corpus entry.
Merging this PR will regress 1 benchmark
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | test_feature[select-relative-sibling] |
42.5 µs | 44.8 µs | -5.08% |
| ⚡ | test_feature[shadow-slot-comments] |
137.9 µs | 83.3 µs | +65.59% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing gaborbernat:feat/atheris-driver-1014 (6726cb0) with main (af31f20)
Footnotes
-
32 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Atheris driver handed raw bytes to each target, so a run could not fail an allocation, and the incremental APIs ran one unit per feed. Each input now starts with libxml2's
[opts][failure_pos][max_chunk]header (xml.c), and the bridge's custom mutator changes one header field or the payload per call, asxmlFuzzMutateChunksdoes (fuzz.c).failure_posdrives the_fuzz_inject_failurehook through the call patternfuzz.py's self-test uses, bounded by the input size plus 100 as libxml2 bounds it. When the hook fires, the target must raiseMemoryErrorand nothing else. Theatherisenv builds with-Dfuzzing=trueso the hook exists, and the Linux test fails the first allocation of a document seed and requires the hook to report it.IncrementalParser,Tokenizer,EncodingDetectorand the stdlibHTMLParsershim feed the payload inmax_chunkpieces with empty feeds around each, capped at libxml2's 50 + len/100 pieces (xml.c), and compare against one call. Atheris's own custom mutator example keeps a zlib frame intact the same way this mutator keeps the header.The public-module list now comes from walking the package, so a new module fails the owner check until a target claims it. Seven round-trip oracles that ran only in
fuzz.py's generational mode now run inside the document, schema, CSS object model, selector and URL targets. closes #1014