docs(fuzz): document transaction_deserialize_fuzz target and link wrapper difference
The README only covered script_fuzz. Add the second target, the build
invocation (CC=clang CXX=clang++ is required — gcc doesn't support
-fsanitize=fuzzer-no-link), and explain why transaction_deserialize_fuzz
needs its own link wrapper (link_txdeser.sh keeps script.cpp.o because
wallet.cpp.o references ExtractDestination/SignSignature/Solver/IsMine).
Matches the committed CMakeLists.txt wiring at 3a4f271.
This commit is contained in:
+75
-35
@@ -1,54 +1,94 @@
|
||||
# Triangles Script Fuzzer
|
||||
# Triangles Fuzz Targets
|
||||
|
||||
libFuzzer-based harness for `EvalScript` in `src/script.cpp`. Mutations
|
||||
find bugs in opcode dispatch, stack handling, push-data edge cases, and
|
||||
the multisig stack walk.
|
||||
Two libFuzzer-based harnesses, both gated on `-DBUILD_FUZZ=ON` so default
|
||||
builds (and CI) don't pull in libFuzzer.
|
||||
|
||||
## Build
|
||||
|
||||
The fuzz target is gated on `-DBUILD_FUZZ=ON` so default builds (and CI)
|
||||
don't pull in libFuzzer. To build:
|
||||
|
||||
```bash
|
||||
cd build
|
||||
cmake -G Ninja -DBUILD_TESTS=ON -DBUILD_FUZZ=ON ..
|
||||
ninja script_fuzz
|
||||
CC=clang CXX=clang++ cmake -G Ninja -DBUILD_TESTS=ON -DBUILD_FUZZ=ON ..
|
||||
ninja
|
||||
```
|
||||
|
||||
Both targets (`fuzz_script` and `transaction_deserialize_fuzz`) are part
|
||||
of the default `ALL` target once `BUILD_FUZZ=ON`.
|
||||
|
||||
Requires `clang++` (libFuzzer is built in since clang-6; clang-18 is
|
||||
current on DNS2).
|
||||
current on DNS2). `gcc` does NOT support `-fsanitize=fuzzer-no-link`, so
|
||||
the entire `triangles_common` and `trianglesd_objects` libraries must be
|
||||
compiled with clang under `BUILD_FUZZ=ON`.
|
||||
|
||||
## Run
|
||||
## Targets
|
||||
|
||||
### `fuzz_script` — script interpreter
|
||||
|
||||
libFuzzer harness for `EvalScript` in `src/script.cpp`. Mutations find
|
||||
bugs in opcode dispatch, stack handling, push-data edge cases, and the
|
||||
multisig stack walk.
|
||||
|
||||
```bash
|
||||
# Fuzz for 5 minutes
|
||||
./bin/script_fuzz -max_total_time=300 -max_len=10000 corpus/
|
||||
|
||||
# Reproduce a crash
|
||||
./bin/script_fuzz crash-deadbeef.bin
|
||||
|
||||
# Run a single corpus file (when built without -fsanitize=fuzzer)
|
||||
./bin/script_fuzz corpus/script_001.bin
|
||||
./bin/fuzz_script -max_total_time=300 -max_len=10000 corpus/
|
||||
./bin/fuzz_script crash-deadbeef.bin # reproduce a crash
|
||||
```
|
||||
|
||||
## Seed corpus
|
||||
Seed corpus: start with `src/test/data/script_valid.json` and
|
||||
`script_invalid.json` — extract the `scriptPubKey` fields and prefix
|
||||
each with `uint8_t(scriptLen)`. A small starter set lives in `corpus/`
|
||||
(generated by `scripts/seed-from-tests.sh`).
|
||||
|
||||
Start with the existing `src/test/data/script_valid.json` and
|
||||
`script_invalid.json` — extract the scriptPubKey fields and prefix
|
||||
each with `uint8_t(scriptLen)`. A small starter set lives in
|
||||
`corpus/` (generated by `scripts/seed-from-tests.sh`).
|
||||
What it finds: every historical script interpreter bug has been in this
|
||||
surface (sigcache asymmetry, CHECKMULTISIG stack walk ordering,
|
||||
combineSigs size trap, push-data encoding, numeric overflow).
|
||||
|
||||
## What it finds
|
||||
### `transaction_deserialize_fuzz` — P2P tx parser
|
||||
|
||||
Every historical script interpreter bug has been in this surface:
|
||||
libFuzzer harness for `CTransaction` deserialization. Reads raw
|
||||
attacker-controlled bytes into a `CDataStream` and calls `Unserialize`
|
||||
on a `CTransaction`, then exercises hash determinism, round-trip
|
||||
serialize/parse, and `CheckTransaction` bounds. Mirrors the Bitcoin Core
|
||||
`deserialize-fuzz` pattern.
|
||||
|
||||
- sigcache Set/Get asymmetry (silent no-op)
|
||||
- CHECKMULTISIG stack walk ordering
|
||||
- combineSigs size trap
|
||||
- PushData encoding edge cases
|
||||
- Numeric overflow on the stack
|
||||
```bash
|
||||
./bin/transaction_deserialize_fuzz -max_total_time=300 -max_len=200000 corpus/
|
||||
./bin/transaction_deserialize_fuzz crash-deadbeef.bin
|
||||
```
|
||||
|
||||
The harness is intentionally minimal — it calls `EvalScript` against
|
||||
a default-constructed `CTransaction`, so signature verification is
|
||||
not exercised. That surface is covered by BOOST tests in
|
||||
`src/test/script_tests.cpp`. The fuzz target is for everything else.
|
||||
What it covers:
|
||||
|
||||
- `ReadCompactSize` varint decoder — every overflow / truncation /
|
||||
non-canonical encoding path.
|
||||
- `Vector<T> Unserialize_impl` — recursive expansion when `T` is
|
||||
itself a structured type (`CTxIn`, `CTxOut`). Known to do unbounded
|
||||
`std::vector::resize(nSize)` before reading; historical DoS surface
|
||||
for "send a tx claiming nSize=0xFFFFFFFF".
|
||||
- `CScript` deserialization (downstream `EvalScript` is covered by
|
||||
`fuzz_script`).
|
||||
- `CTransaction::CheckTransaction` bounds — max size, negative value,
|
||||
out-of-range totals.
|
||||
- Hash determinism — `GetHash()` must produce the same `uint256` for
|
||||
the same bytes, regardless of intermediate state mutations.
|
||||
|
||||
What it does NOT cover: signature verification (covered by
|
||||
`script_tests.cpp` / `keystore_tests.cpp`), block-level validation,
|
||||
P2P message framing (the fuzz input is the raw tx payload, not the wire
|
||||
envelope).
|
||||
|
||||
## Link wrappers
|
||||
|
||||
Both targets use a wrapper script (`fuzz_objs/link.sh` and
|
||||
`fuzz_objs/link_txdeser.sh`) that discovers `.o` files at link time.
|
||||
The difference:
|
||||
|
||||
- `link.sh` excludes `script.cpp.o` from `triangles_common` because
|
||||
`fuzz_script` provides its own clang-instrumented copy.
|
||||
- `link_txdeser.sh` keeps `script.cpp.o` (needed by `wallet.cpp.o`
|
||||
symbols like `ExtractDestination`, `SignSignature`, `Solver`,
|
||||
`IsMine`) and excludes only `init.cpp.o` (daemon `main()` would
|
||||
conflict with libFuzzer's).
|
||||
|
||||
## CI integration
|
||||
|
||||
Both jobs run under the `Build All Platforms` workflow on every PR.
|
||||
The CI script build script lives in
|
||||
`.github/workflows/build-all.yml` under the `fuzz` job.
|
||||
Reference in New Issue
Block a user