From: David Gibson <david@gibson.dropbear.id.au>
To: Stefano Brivio <sbrivio@redhat.com>
Cc: Anshu Kumari <anskuma@redhat.com>,
passt-dev@passt.top, aerosound161@gmail.com, abdobngad@gmail.com,
lvivier@redhat.com
Subject: Re: [PATCH 5/5] fuzz: Add test server for bidirectional protocol fuzzing
Date: Fri, 14 Aug 2026 20:02:46 +1000 [thread overview]
Message-ID: <an7ntzKrZx-DZGWP@zatzit> (raw)
In-Reply-To: <20260814093558.41626ef3@elisabeth>
[-- Attachment #1: Type: text/plain, Size: 13620 bytes --]
On Fri, Aug 14, 2026 at 09:35:59AM +0200, Stefano Brivio wrote:
> On Fri, 14 Aug 2026 15:40:35 +1000
> David Gibson <david@gibson.dropbear.id.au> wrote:
>
> > On Thu, Aug 13, 2026 at 09:53:24AM +0200, Stefano Brivio wrote:
> > > On Wed, 12 Aug 2026 12:56:28 +0530
> > > Anshu Kumari <anskuma@redhat.com> wrote:
> > >
> > > > 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.
> >
> > It does.
> >
> > > 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).
> >
> > So, I agree that to meaningfully fuzz things, we want the fuzzer to be
> > able to control data on both the guest and host side. I can see two
> > basic approaches:
> >
> > A) Alter passt/pasta so that instead of directly communicating with
> > external entities (either guest or host side) we use mocked
> > versions which retrieve data from AFL. We can do that either at
> > the system call level, or at a higher helper function level, the
> > lower level we go, the more of the "normal" passt code we're
> > exercising, but doing at a slightly higher level might be easier
> > to implement
> >
> > B) Run passt/pasta in an environment where we can intercept the
> > external transfers to a test server / test peer / test guest which
> > in turn responds based on data from AFL.
> >
> > Both the current draft and the sketch diagram Stefano has provided are
> > a hybrid of both approaches, so far I'm not seeing a clear advantage
> > to that over going all one way or the other.
>
> Of course, B) would be cleaner (and not that complicated, see below),
> but we don't want to do that guest-side (sending data from a test
> guest) because we would lose the speed advantage of having shared
> memory on the path that _really_ matters for fuzzing (the guest is
> untrusted, the kernel isn't).
I agree we lose the speed advantage, but I don't really see why that
matters more on the guest side than the host. Yes, fuzzing the guest
side matters more, but most guest side operations will induce passt to
perform a host side operation, so the speed of the host side handling
matters even if the guest side is what we care about fuzzing.
> That's something we already established a while ago when AbdAlRahman
> was working on it. We hadn't really looked into the host side yet, back
> then.
>
> So, host side: we can't do it (and it's much less important) because we
> need to use those sockets in the same way passt uses them.
I'm not sure what you mean. I outlined a way to do this below.
> The guest side interface is a trivial recv(), the host side is something
> complicated with iovecs and everything.
I'm now not sure if you're saying this in relation to approach B, or
approach A.
> > (B) is quite easy to do guest side - we just connect a "test guest" to
> > passt's socket. Approach B is much harder for host side. The current
> > draft has a test server listening on a single address.
>
> This is just to get something up and running though, it obviously needs
> to be changed later.
I don't see how we are "up and running" if fuzzed packets from the
guest induce passt to forward them to random host side addresses that
we're not controlling - we won't generate reproducible results.
> > But that means
> > the fuzzing is fundamentally incapable of finding bugs involving
> > talking to multiple peers at once. Worse, we have to constrain the
> > construction of guest side data so that we talk to the test server not
> > something else, and that's one of the things we most want to fuzz.
> >
> > To really take approach B for the host side we'd need to intercept
> > *all* host side network traffic regardless of address. Probably
> > easiest way to do that would be put the whole thing inside another
> > netns. That outside netns would have a default route to a tap device,
> > and on the other side of the tap device would be a test server serving
> > up frames built from the fuzzer output.
>
> As I was mentioning, this could be done in a network namespace without
> any interface, by making the test server listen to all ports and all
> addresses, with a non-local bind and a so-called AnyIP route. Tested:
>
> $ pasta -- sh -c 'ip route add local default dev lo; nc -l 1 & { sleep 1; echo x | nc -N 1.2.3.4 1; }'
> x
True, but having the fuzzer synthesize L2 frames seems easier to me
than having it directly synthesize the various socket operations the
host side peer might perform.
> > It's probably easier to co-ordinate if the host side and guest side
> > test server is the same, so we'd have:
> >
> > ,-------. ,-------------.
> > | AFL++ |-- shares memory with ---| test peer |
> > '-------' '--v------v---'
> > | |
> > /-tap device-/ |
> > | |
> > /- test netns ----------^-------------------|-------------\
> > | | |
> > | ,--------. | |
> > | | passt >-----------<unix socket>-----/ |
> > | '--------' |
> > \---------------------------------------------------------/
> >
> > The test peer generates host side frames via the tap device, and guest
> > side frames via the Unix socket.
>
> The UNIX socket is something we want to avoid, it's really much slower
> compared to shared memory (we tried something like that) on the path
> where AFL++ is trying to mutate data fast (because it can hit a lot of
> different code paths with small changes, compared to changing
> socket-side payload).
Right. That's why I conclude approch A is probably better further
down. But even if we only care about fuzzing guest side, we'll
usually incur the cost of operations on both sides, so I don't see
that using shared memory is more important for guest side than host
side interposition.
> > It could also be done with pasta, like this:
> >
> > ,-------. ,-------------.
> > | AFL++ |-- shares memory with ---| test peer |
> > '-------' '--v------v---'
> > | |
> > /-tap device-/ packet
> > | socket
> > /- test netns ----------^-------------------|-------------\
> > | | |
> > | ,--------. ,----------------|---. |
> > | | pasta >-tap device-< guest netns * | |
> > | '--------' '--------------------' |
> > \---------------------------------------------------------/
> >
> >
> > The order it generates host vs. guest frames should also come from the
> > fuzzer, not be fixed. At least theoretically, this is non-invasive:
> > it could run with an unmodified passt/pasta. Except that
> > - AFL would still need coverage feedback from passt/pasta
> > - It wouldn't allow us to simulate odd timings (except by actually
> > expending real time)
> > - The various interposing layers will probably slow down fuzzing.
> >
> > So, I rather suspect it will work better to go fully to approach A: no
> > test peer at all, instead passt itself is modified to use mocked
> > versions of all the external syscalls to slurp data from AFL. The
> > draft series already does this for epoll_wait() and recv*(), but we'd
> > need to also do that for recv*() on the tap socket, connect(),
> > accept(), TCP_INFO and probably others. We'd also need to mock
> > "sending" calls, send(), write() and shutdown() at least - but those
> > could probably be no-ops.
>
> ...except that by mocking all those we lose a lot of complexity where
> historically we had a ton of bugs. If we just mock recv() it's much
> less (well yes we had bugs there as well but it was like 3 or 4 over the
> entire project history).
If we mock as close as possible to the syscall level, I don't see that
bypass much of our complexity. To be clear, I'm suggesting mocks
where both returned data and error codes are derived from the fuzzer,
not just no-op stubs.
> > This is more invasive, of course. It also means we need to deal with
> > the case where AFL generates a syscall results that should be
> > impossible - that should move onto the next case ASAP, but not be
> > flagged as a passt bug. On the other hand, this approach should be
> > fast, and since we can also mock clock_gettime(), the fuzzer can
> > potentially find timer logic bugs that would only occur after hours or
> > days in real time.
> >
> > > 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
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2026-08-14 10:03 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-12 7:26 [PATCH 0/5] Add AFL++ fuzzing support for passt Anshu Kumari
2026-08-12 7:26 ` [PATCH 1/5] fuzz: Add deterministic wrappers for system calls Anshu Kumari
2026-08-13 3:46 ` David Gibson
2026-08-12 7:26 ` [PATCH 2/5] fuzz: Add flow type guards for fuzzing stability Anshu Kumari
2026-08-13 4:45 ` David Gibson
2026-08-12 7:26 ` [PATCH 3/5] fuzz: Bypass isolation and adapt sockets for AFL++ Anshu Kumari
2026-08-13 5:04 ` David Gibson
2026-08-12 7:26 ` [PATCH 4/5] fuzz: Add AFL++ persistent mode fuzz loop Anshu Kumari
2026-08-13 6:34 ` Stefano Brivio
2026-08-14 1:55 ` David Gibson
2026-08-14 10:32 ` Anshu Kumari
2026-08-12 7:26 ` [PATCH 5/5] fuzz: Add test server for bidirectional protocol fuzzing Anshu Kumari
2026-08-13 7:53 ` Stefano Brivio
2026-08-14 5:40 ` David Gibson
2026-08-14 7:35 ` Stefano Brivio
2026-08-14 10:02 ` David Gibson [this message]
2026-08-14 11:55 ` Stefano Brivio
2026-08-14 5:50 ` David Gibson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=an7ntzKrZx-DZGWP@zatzit \
--to=david@gibson.dropbear.id.au \
--cc=abdobngad@gmail.com \
--cc=aerosound161@gmail.com \
--cc=anskuma@redhat.com \
--cc=lvivier@redhat.com \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).