nx_fuzz.nx
buildroot/runtime/nx_fuzz.nx
about
nx_fuzz.nx -- mutational input fuzzer.
Generates test inputs by MUTATING existing seed inputs rather
than creating them from scratch. Why mutation beats pure random:
Random bytes almost never parse as valid anything. A lexer
fuzz against "0xF8 0x42 0xE7 ..." spends 100% of runtime in
the 'unexpected character' error path -- you never exercise
deeper code.
Mutation starts from a known-valid seed ("123" for a number
parser, "{x: 1}" for a JSON parser) and applies small edits
(bit flip, byte swap, insert, delete). The mutant is
usually still CLOSE to valid, so the parser reaches deeper
code paths. Classic AFL insight.
Based on:
AFL / AFL++ (Zalewski 2013, 2019) -- the mutation operator set
libFuzzer (Serebryany 2015) -- in-process coverage-guided
honggfuzz (Swiecki 2010+) -- persistent mode + dictionaries
Radamsa (Aalto Uni) -- grammar-aware mutations
v0.0.1 scope:
* Seeded xorshift64 PRNG (reproducible across machines)
* Mutation primitives:
- bit flip (1 bit)
- byte flip (single byte)
- byte arith (+-1, +-8, +-64 on a single byte)
- insert (random byte at random pos)
- delete (random byte at random pos)
- splice (copy chunk from one seed into another)
* Corpus -- a small ring of seed inputs
* Per-call `nx_fuzz_mutate(in_buf, in_len, out_buf, out_cap)
-> out_len`
Not yet (staged):
* Coverage instrumentation -- requires nxc2 to emit
__fuzz_edge counters at every branch. Big compiler change.
When shipped, replaces pure-mutation with AFL-style
dependencies 1 imports · 2 importers
imports: syscalls.nx
imported by: fuzz_lex_test.nxfuzz_parse_test.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 60 | struct NxFuzz |
consts
| 56 | const NX_MAGIC_65536: i64 = 65536 |
| 73 | const NX_FUZZ_BYTES: i64 = 96 |
functions
| 77 | func nx_fuzz_rand_i64(f: *NxFuzz) -> i64; |
| 78 | func nx_fuzz_rand_range(f: *NxFuzz, lo: i64, hi: i64) -> i64; |
| 79 | func nx_fuzz_seed_at(f: *NxFuzz, idx: i64, out_len: *i64) -> *u8; |
| 80 | func nx_fuzz_copy_seed(f: *NxFuzz, idx: i64, out: *u8, cap: i64) -> i64; |
| 84 | func nx_fuzz_new(seed: i64) -> *NxFuzz |
| 103 | func nx_fuzz_rand_i64(f: *NxFuzz) -> i64 |
| 112 | func nx_fuzz_rand_range(f: *NxFuzz, lo: i64, hi: i64) -> i64 calls 1: nx_fuzz_rand_i64 |
| 125 | func nx_fuzz_add_seed(f: *NxFuzz, buf: *u8, len: i64) -> i64 |
| 144 | func nx_fuzz_seed_at(f: *NxFuzz, idx: i64, out_len: *i64) -> *u8 |
| 154 | func nx_fuzz_copy_seed(f: *NxFuzz, idx: i64, out: *u8, cap: i64) -> i64 calls 1: nx_fuzz_seed_at |
| 170 | func nx_fuzz_op_bit_flip(f: *NxFuzz, buf: *u8, len: i64) -> i64 |
| 179 | func nx_fuzz_op_byte_set(f: *NxFuzz, buf: *u8, len: i64) -> i64 |
| 187 | func nx_fuzz_op_insert(f: *NxFuzz, buf: *u8, len: i64, cap: i64) -> i64 |
| 201 | func nx_fuzz_op_delete(f: *NxFuzz, buf: *u8, len: i64) -> i64 |
| 214 | func nx_fuzz_op_arith(f: *NxFuzz, buf: *u8, len: i64) -> i64 |
| 234 | func nx_fuzz_mutate(f: *NxFuzz, out_buf: *u8, out_cap: i64) -> i64 |
| 259 | func nx_fuzz_crash(f: *NxFuzz) -> i64 |
| 266 | func main() -> i64 |