Repository navigation
test(segment): fuzz FindBreaksAround on forged iterators - #282
Merged
Merged
Conversation
siracozmen01
commented
Nov 5, 2026
siracozmen01
left a comment
Contributor
Author
There was a problem hiding this comment.
Review at 0ac5231: ready. No findings. Read, not run.
- The entry is the caller's own.
Segmentmakes the iterator through the public init calls, optionally adds overrides, a strictness and a breaker, and steps it. ThenOverwritesets input-chosen bytes of themuniSegmenteroutside the kept ranges, and the fuzzer calls the publicmuniSegmentIterator_FindBreaksAround(&iterator, ...). So the forged state reaches the function through its own load check, as a caller's corrupted state would. Nothing is pre-validated or reached through an internal entry. - The kept ranges are the ones the file's header names as outside the contract: the cursor's text and length, the breaker, its context and SA run, and the overrides pointer, plus the kind and count when there are overrides. Every other byte the function reads, the line settings and cursor position among them, is forgeable.
- Offsets inside the text:
s_textis 52 bytes, sosizeof(s_text)is 53 andsteps * 7 % 53takes 0 to 52, the end included. 7 and 53 are coprime, so the 256 values ofstepsreach every residue, including offsets inside a multi-byte sequence (the Thai, the emoji ZWJ sequence, αβ, 人). The second call pins the end, 52. - Offsets outside the text: never. As the body says, the past-the-end refusal stays at 0 here. That check compares the caller's offset with the cursor's length, which is kept, so no forged byte can change its outcome, and test_segment.c already tests it. Fuzzing it would add nothing.
- Each call writes
beforeandafteror refuses, and both are discarded, so a refusal cannot leave the loop that follows on uninitialized values.
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.
Problem
test/fuzz_iterators.cgives every call that takes an iterator back a forged state, so that no bit pattern makes one read outside the state, the text or the overrides.muniSegmentIterator_FindBreaksAround(#278) was not among those calls. It loads the iterator through the same check, then reads the text the cursor holds with the iterator's kind, line settings, class overrides and breaker, and runs segmenters of its own over it. No fuzzer gave it a forged state:test/fuzz_segment.ccalls it only on iterators an init call set up.Change
After the getters,
fuzz_iteratorscallsmuniSegmentIterator_FindBreaksAroundon each forged segment iterator twice: at an offset the input's step byte picks, from 0 to the end of the text, and at the end. Two calls, not one per offset, keep the target's speed: with a call every few bytes it ran 2.7M inputs in 300 seconds instead of 7.0M; with these two it runs 5.6M.No library code changes.
Verification
fuzz_iterators(clang-20,-fprofile-instr-generate) after 300 seconds from the committed seeds (5.6M runs, nothing found): forged states reach the call's backward search and segmenter run 1.24M times, and its busy (6.7k) and state (3.15M) refusals, as well as the refusal of an iterator no init call set up (3.03M). The committed seeds alone reach the search too. The NULL argument and offset past the end refusals stay at 0: the fuzzer passes neither, andtest/test_segment.ctests both.tools/check.sh changedpasses: the rules with the nodiscard check, format; no test suite is reached by the change.Closes #281