On Wed, 12 Aug 2026 12:56:28 +0530
Anshu Kumari
Add fuzz-server that acts as passt's network peer during fuzzing.
To me, this part makes sense. But this one:
It connects to passt's UNIX socket
much less, while:
and listens on 127.0.0.1:9999 for TCP connections.
this is the part that I expected instead. Otherwise it's not just passt's network peer, it's the guest as well.
UNIX socket path: responds to ARP requests and TCP SYNs with stateless replies (swapped addresses, fixed ISN). Responses are XOR'd with AFL++ shared memory data so the fuzzer can mutate server behavior.
This looks rather complicated to me. The approach I was suggesting with a test server is the following: ,- exchanges guest-side data with ------------. | ,---------|---------. | ,--| passt | | / '-.---------------^-' ,---|---. / | connect(), | accept(), | AFL++ |-- shares memory with ---| | send data, | reply with '---|---' \ | etc. | data, etc. | \ ,-v---------------'-. | '--| test server | | '---------|---------' '- exchanges host-side data with -------------' ...at least in its basic form. Eventually, the test server should be able to connect to passt itself (and we could call it "test peer" at that point). As far as I understood, it's not trivial to make the same instance of AFL++ share memory with two processes at the same time, so the memory-sharing path might need to take a more complicated turn, for example there could be a wrapper starting both passt and the test server and sharing memory with them, or passt could _additionally_ (using a special out-of-band fuzzing channel) share data from AFL++ with the test server. An example of communication below (but events don't necessarily need to be in this order, this is just an example). For simplicity, let's ignore the fact that AFL++ might not directly share memory with passt and test server, and assume there are three areas of memory that AFL++ directly controls: a. shared with passt: an array of struct epoll_event, 'ev' b. shared with passt: the kind of tap-side buffer you implemented in 4/5, 'buf' c. shared with the test server: a separate buffer, 'test_buf' Example: 1. AFL++ writes an EPOLLIN event in 'ev' with type EPOLL_TYPE_TAP_PASST, of some data in 'buf', and some data in 'test_buf' 2. AFL++ starts passt and the test server 3. passt reads the EPOLL_TYPE_TAP_PASST event from 'ev', reads data from 'buf' and hands it to passt_tap_handler() 4. this happens to be have Ethernet, IP, and TCP headers, with the SYN flag set, and destination address set to the address of the test server (we might want to force all this, at least initially, or give it as a hint to AFL++ somehow), so passt connects to the test server 5. the test server accepts the connection, and sends the contents of 'test_buf' on it (for the test server, this is directly payload, without headers, as they don't make sense there). I'm not sure if we should have a different set of events (maybe we need a "play script" for the server, in case?) 6. this generates an EPOLLOUT event for passt. It's not in 'ev', it's a regular epoll_wait() (I think we could have an epoll_wait() loop where we additionally read one event from 'ev' for every iteration, or something like that) 7. passt marks the connection as established and inserts it in the flow table 8. passt reads the data sent from the test server and generates whatever TCP data packet to the "guest" (it might simply be a sink) ...and this attempt ends here because AFL++ generated a single event for passt, but there could be more (this should also be decided by AFL++). Would something like this make sense? -- Stefano