feat(fuzz): add SharpFuzz-based fuzzing for untrusted-input parsers
New tests/PalladiumWallet.Fuzz project: one target per parser that consumes untrusted input (block headers, Merkle proofs, peer lists, wallet files, user-pasted mnemonics/keys/addresses/amounts), each encoding the parser's documented error contract - any exception beyond that contract is a finding, reported as a crash. Three ways to run: the seed corpus (with regression inputs for every crash found so far, including the two fixed in prior commits) replays automatically inside dotnet test via FuzzCorpusTests, so a fixed contract violation can't silently return; a built-in random-mutation mode needs no external tooling (dotnet run -- <target> --random N); and fuzz.sh drives real coverage-guided campaigns via afl++ + SharpFuzz instrumentation for pre-release or post-parser-change runs.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Fuzzing
|
||||
|
||||
Fuzz targets over every parser that consumes **untrusted input**: server
|
||||
responses (block headers, Merkle proofs, peer lists), wallet files (plaintext
|
||||
document and encrypted container), and user-pasted text (mnemonics, SLIP-132
|
||||
keys, addresses, amounts).
|
||||
|
||||
Each target encodes the parser's **error contract**: the exception types
|
||||
documented as its failure mode are swallowed, anything else escaping is a
|
||||
finding. Targets: `header` `merkle` `slip132` `bip39` `address` `coinamount`
|
||||
`walletdoc` `encfile` `peers` (see `FuzzTargets.cs` for each contract).
|
||||
|
||||
## Three ways to run
|
||||
|
||||
**1. Corpus replay — automatic.** The seed corpus (including regression inputs
|
||||
for every crash found so far) replays through all targets on every
|
||||
`dotnet test` run via `FuzzCorpusTests`, so fixed findings cannot come back.
|
||||
|
||||
**2. Built-in random smoke — no tooling.** Not coverage-guided, but catches
|
||||
shallow contract violations in seconds:
|
||||
|
||||
```bash
|
||||
dotnet run -- bip39 --random 50000 # target, iterations [, seed]
|
||||
```
|
||||
|
||||
**3. Coverage-guided campaign — afl++.** The real thing; run it before a
|
||||
release or after touching any parser:
|
||||
|
||||
```bash
|
||||
apt install afl++
|
||||
dotnet tool install --global SharpFuzz.CommandLine
|
||||
./fuzz.sh header # one target per campaign
|
||||
./fuzz.sh peers -V 3600 # extra args go to afl-fuzz
|
||||
```
|
||||
|
||||
`fuzz.sh` builds Release, instruments `PalladiumWallet.Core.dll` (and NBitcoin)
|
||||
with SharpFuzz, and launches afl-fuzz with the target's seed corpus. Findings
|
||||
land in `findings/<target>/crashes/`.
|
||||
|
||||
## Triage workflow
|
||||
|
||||
```bash
|
||||
dotnet run -- <target> findings/<target>/crashes/<file> # replay: full stack trace
|
||||
```
|
||||
|
||||
Fix the parser (typed exception or graceful rejection — see the hardening
|
||||
commits for `Bip39.TryParse`, `ElectrumApi.ParsePeers`, `EncryptedFile.Decrypt`
|
||||
as examples), then add the input to `SeedCorpus` in `Program.cs` as a
|
||||
`regression-*` seed and regenerate with `dotnet run -- --make-seeds Corpus`.
|
||||
|
||||
## Findings so far
|
||||
|
||||
The first smoke run found two real bugs, both reachable from untrusted input:
|
||||
|
||||
- `Bip39.TryParse` crashed with `NotSupportedException` on restore text
|
||||
resembling no wordlist (NBitcoin's `Wordlist.AutoDetect` throws for its
|
||||
internal "Unknown" language).
|
||||
- `ElectrumApi.ParsePeers` crashed with `InvalidOperationException` on a peer
|
||||
list containing a JSON string with invalid UTF-8 bytes (parses fine,
|
||||
fails at `GetString()` transcoding) — attacker-controlled server data.
|
||||
|
||||
Plus two hardening changes made so the `encfile`/`peers` contracts could be
|
||||
strict at all: `EncryptedFile.Decrypt` maps every malformed-container failure
|
||||
to `InvalidDataException` and bounds the PBKDF2 iteration count (a tampered
|
||||
file could previously demand 2^31 iterations, hanging the wallet at open), and
|
||||
`ParsePeers` tolerates any JSON shape.
|
||||
Reference in New Issue
Block a user