Skip to content

test(segment): fuzz FindBreaksAround on forged iterators - #282

Merged
siracozmen01 merged 1 commit into
mainfrom
test/fuzz-iterators-around
Nov 5, 2026
Merged

siracozmen01 merged 1 commit into
mainfrom
test/fuzz-iterators-around

Conversation

@siracozmen01

Copy link
Copy Markdown
Contributor

Problem

test/fuzz_iterators.c gives 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.c calls it only on iterators an init call set up.

Change

After the getters, fuzz_iterators calls muniSegmentIterator_FindBreaksAround on 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

  • Source coverage of 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, and test/test_segment.c tests both.
  • tools/check.sh changed passes: the rules with the nodiscard check, format; no test suite is reached by the change.

Closes #281

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 0ac5231: ready. No findings. Read, not run.

  • The entry is the caller's own. Segment makes the iterator through the public init calls, optionally adds overrides, a strictness and a breaker, and steps it. Then Overwrite sets input-chosen bytes of the muniSegmenter outside the kept ranges, and the fuzzer calls the public muniSegmentIterator_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_text is 52 bytes, so sizeof(s_text) is 53 and steps * 7 % 53 takes 0 to 52, the end included. 7 and 53 are coprime, so the 256 values of steps reach 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 before and after or refuses, and both are discarded, so a refusal cannot leave the loop that follows on uninitialized values.

@siracozmen01
siracozmen01 merged commit 0c2c23a into main Nov 5, 2026
24 checks passed
@siracozmen01
siracozmen01 deleted the test/fuzz-iterators-around branch November 5, 2026 12:08
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.

The iterator fuzzer never calls muniSegmentIterator_FindBreaksAround

1 participant