Keep a valid address in pjuri fuzz mutations - #1828
Conversation
A bitcoin address is checksummed, so libFuzzer cannot construct one. Unlike a character-set check, which it learns from the comparisons it traces, a checksum gives it no way to turn the constant it needs into input bytes, so every input was rejected before the payjoin parameters were read and the body of the target never ran. The custom mutator keeps a parseable address, substituting a canonical one when the input carries none, and spends the mutation budget on the query instead. A quarter of mutations still go through untouched to keep the reject paths covered. Over 60s from an empty corpus, coverage rises from 293 to 1433 edges. Drop the TypeId assertions along the way: they compare compile-time constants, so they cannot fail, and their early returns would have silently disabled the rest of the target.
c54fd09 to
292cc46
Compare
caarloshenriq
left a comment
There was a problem hiding this comment.
cACK 292cc46
One question: split_uri falls back to DEFAULT_QUERY when the input has no ?, which means the first mutation from an empty corpus always starts with a full payjoin query. Is that intentional as a bootstrap, or would starting with an empty query expose more of the parameter-parsing edge cases (missing keys, partial query strings)?
It's intentional. When the |
Coverage Report for CI Build 32403746166Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage remained the same at 86.718%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
caarloshenriq
left a comment
There was a problem hiding this comment.
tACK 292cc46
Ran uri_deserialize_pjuri for 60s from an empty corpus on x86_64 Linux, 6 forks:
| Metric | Value |
|---|---|
| Coverage counters reached | 1353 |
| Feature edges | 3146 |
| Corpus size | 612 inputs |
| Crashes / OOM / Timeouts | 0 / 0 / 0 |
Coverage was still growing at 60s, which confirms the custom mutator is keeping the fuzzer out of the address checksum wall and spending budget on the payjoin parameters where it matters.
A bitcoin address is checksummed, so libFuzzer cannot construct one. Unlike a character-set check, which it learns from the comparisons it traces, a checksum gives it no way to turn the constant it needs into input bytes, so every input was rejected before the payjoin parameters were read and the body of the target never ran.
The custom mutator keeps a parseable address, substituting a canonical one when the input carries none, and spends the mutation budget on the query instead. A quarter of mutations still go through untouched to keep the reject paths covered. Over 60s from an empty corpus, coverage rises from 293 to 1433 edges.
Drop the
TypeIdassertions along the way: they compare compile-time constants, so they cannot fail, and their early returns would have silently disabled the rest of the target.More context at: #1826