## Fuzzing passt with AFL++ ### Prerequisites - AFL++ (afl-clang-fast, afl-fuzz) ### Build ``` make fuzz cp passt passt.fuzz make fuzz-server ``` This produces: - `passt.fuzz` -- instrumented with AFL++ and AddressSanitizer - `fuzz-server` -- test server for bidirectional protocol fuzzing To use a specific AFL++ installation: ``` make FUZZ_CC=/path/to/afl-clang-fast fuzz ``` ### Run Start the test server first, then the fuzzer: ``` # Terminal 1 -- test server: ./fuzz-server # Terminal 2 -- fuzzer: afl-fuzz -i fuzzing/testcase_dir -o fuzzing/sync_dir \ -- ./passt.fuzz --foreground ``` Multi-core (secondary instances share the corpus): ``` # Terminal 1 -- main instance: afl-fuzz -M main -i fuzzing/testcase_dir -o fuzzing/sync_dir \ -- ./passt.fuzz --foreground # Terminal 2 -- secondary with different power schedule: afl-fuzz -S variant1 -p rare -i fuzzing/testcase_dir \ -o fuzzing/sync_dir -- ./passt.fuzz --foreground ``` ### Architecture The fuzzer uses AFL++ persistent mode with shared memory fuzzing. Each iteration: 1. Resets deterministic state (clock, flow table, epoll) 2. Reads an epoll event and packet data from AFL++ shared memory 3. For TAP events: injects a packet with fixed L2/L3/L4 headers into the tap pipeline via tap_add_packet() + tap_handler(). The TCP destination port is fixed to 9999 (the test server's listening port). 4. Exchanges a turn flag with fuzz-server for bidirectional flow. 5. Calls passt_worker() to process the epoll event 6. Polls for host-side TCP events (connect completion, server data) The test server connects to passt's UNIX socket (SOCK_SEQPACKET) and listens on 127.0.0.1:9999 for TCP connections. It responds to ARP requests and TCP SYNs on the UNIX socket, and echoes TCP data (XOR'd with AFL++ shared memory) on the loopback side. Deterministic wrappers in fuzz.c replace clock_gettime(), getrandom(), getsockopt(TCP_INFO), and recv/recvmsg/recvfrom/ recvmmsg to eliminate non-determinism from kernel state. The recv wrappers return AFL++ buffer data for non-TAP file descriptors, giving the fuzzer control over what passt "receives" from host sockets. ### Seed inputs `testcase_dir/empty.bin` is a 12-byte zero file (sizeof(struct epoll_event)). AFL++ discovers packet formats through mutation from this minimal seed. ### Reproducing crashes ``` ./passt.fuzz --foreground < \ fuzzing/sync_dir/default/crashes/id:000000,... ``` Minimize a crash input: ``` afl-tmin -i fuzzing/sync_dir/default/crashes/id:000000,... \ -o crash_minimized -- ./passt.fuzz --foreground ```