1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
| | ## Fuzzing passt with AFL++
### Prerequisites
- AFL++ (afl-clang-fast, afl-fuzz)
- Linux kernel with `CONFIG_USER_NS=y` (default on Fedora, Debian, Ubuntu)
### Build
```
make fuzz # AFL++-instrumented binary (produces passt.fuzz)
```
This also builds `fuzz-server` (the host-side test peer) as a prerequisite.
This produces:
- `passt.fuzz` -- instrumented with AFL++ and AddressSanitizer
- `fuzz-server` -- host-side test peer (built automatically)
To use a specific AFL++ installation:
```
make FUZZ_CC=/path/to/afl-clang-fast fuzz
```
### Network setup
The fuzzer runs in a rootless user + network namespace with AnyIP
routing so the test server intercepts all outbound IPv4 and IPv6
TCP from passt, regardless of destination IP or port. No root
access is needed -- `fuzz-setup.sh` uses `unshare --user --net`
to create the namespace pair, matching how pasta itself operates.
The test server listens on ports 1 through 55535 (both IPv4 and
IPv6), leaving the top 10000 ports free for passt's own
`connect()` and `bind()` calls.
The namespace exists only while the command runs; no cleanup step
is required.
### Run
passt automatically fork+execs `fuzz-server` before the AFL++
forkserver starts, then sleeps 3 seconds to let it finish binding
all ports. There is no need to launch it separately.
Basic run:
```
fuzzing/fuzz-setup.sh -- afl-fuzz -i fuzzing/testcase_dir \
-o fuzzing/sync_dir -- ./passt.fuzz --foreground
```
Multi-core (secondary instances share the corpus):
```
# Terminal 1 -- main instance:
fuzzing/fuzz-setup.sh -- afl-fuzz -M main \
-i fuzzing/testcase_dir -o fuzzing/sync_dir \
-- ./passt.fuzz --foreground
# Terminal 2 -- secondary with different power schedule:
fuzzing/fuzz-setup.sh -- afl-fuzz -S variant1 -p rare \
-i fuzzing/testcase_dir -o fuzzing/sync_dir \
-- ./passt.fuzz --foreground
```
### Architecture
AFL++ controls four regions of the testcase buffer:
- **(a) `ev`** -- array of epoll events injected into passt's
main loop alongside real kernel events
- **(b) `buf`** -- raw tap-side packets (full L2 frames, headers
included). AFL++ controls everything: Ethernet, IP, TCP/UDP
headers, destination addresses, payload
- **(c) `test_buf`** -- payload the test server sends to passt
on accepted connections
- **(d) `sockopt_buf`** -- TCP_INFO data returned to passt by the
`getsockopt()` wrapper in fuzz.c
The testcase header carries explicit lengths for each region:
`n_events` (u32), `tap_len` (u16), `testbuf_len` (u16), and
`sockopt_len` (u16). Both passt and `fuzz-server` call
`fuzz_parse_layout()` on the same header, which clamps each
declared length to the available space and to per-region maximums,
so the two sides always agree on offsets. AFL++ controls the
split directly through mutation of these header fields.
Example flow for a single event:
1. AFL++ writes an EPOLLIN event with type EPOLL_TYPE_TAP_PASST
in `ev`, raw packet data in `buf`, and payload in `test_buf`
2. passt reads the event from `ev`, reads data from `buf`, and
hands it to tap_handler()
3. The data happens to have Ethernet, IP, and TCP headers with
the SYN flag set (AFL++ discovered this format). passt calls
connect() to the destination in the packet
4. AnyIP routing makes the destination local and the test server,
which listens on ports 1-55535 (IPv4+IPv6), accepts the connection
5. The test server sends the contents of `test_buf` to passt
6. A real epoll_wait() fires EPOLLOUT for passt (not from `ev`)
7. passt marks the connection established and inserts it in the
flow table
8. passt reads data from the test server and generates TCP data
back to the "guest"
### Seed inputs
`testcase_dir/empty.bin` provides a minimal starting point.
AFL++ discovers packet formats through mutation.
### Reproducing crashes
Replay a crash input (`fuzz-server` is fork+exec'd by passt
automatically):
```
fuzzing/fuzz-setup.sh -- ./passt.fuzz --foreground < \
fuzzing/sync_dir/default/crashes/id:000000,...
```
Minimize a crash input:
```
fuzzing/fuzz-setup.sh -- afl-tmin \
-i fuzzing/sync_dir/default/crashes/id:000000,... \
-o crash_minimized -- ./passt.fuzz --foreground
```
|