public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: Anshu Kumari <anskuma@redhat.com>
Cc: david@gibson.dropbear.id.au, passt-dev@passt.top,
	aerosound161@gmail.com, abdobngad@gmail.com, lvivier@redhat.com
Subject: Re: [PATCH 4/5] fuzz: Add AFL++ persistent mode fuzz loop
Date: Thu, 13 Aug 2026 08:34:08 +0200 (CEST)	[thread overview]
Message-ID: <20260813083352.2cbf2af3@elisabeth> (raw)
In-Reply-To: <20260812072630.3235261-5-anskuma@redhat.com>

Not a complete review, just a few notes, mostly about the general
concept:

On Wed, 12 Aug 2026 12:56:27 +0530
Anshu Kumari <anskuma@redhat.com> wrote:

> Add the AFL++ persistent mode fuzz loop to passt.c main().
> The loop uses __AFL_LOOP() for in-process iteration and
> __AFL_FUZZ_TESTCASE_BUF for shared memory fuzzing.
> 
> Each iteration:
>   - Resets deterministic clock, flow table, and epoll instance.
>   - Drains stale data from the TAP socket.
>   - Reads an epoll event from the AFL++ buffer.
>   - For TAP events: constructs a packet with fixed L2/L3/L4
>     headers and injects it via tap_add_packet() + tap_handler().
>   - Exchanges a turn flag with the test server for
>     bidirectional flow over the UNIX socket.

This complexity could probably be avoided if you switch to a model where
the test server is just operating on the host side of things (accepting
TCP connections and replying). More on that in a bit as a comment to
5/5.

>   - Calls passt_worker() to process the event.
>   - Polls for host-side TCP events via epoll_wait().
>   - Runs post_handler() for deferred work.
> 
> Added the 'make fuzz' target which builds passt with
> afl-clang-fast, -DFUZZING, -DNDEBUG, and AddressSanitizer.
> 
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
>  Makefile |   8 +++
>  passt.c  | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 197 insertions(+)
> 
> diff --git a/Makefile b/Makefile
> index fe1df58..8e4121e 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -123,6 +123,14 @@ valgrind: BASE_CPPFLAGS += -DVALGRIND
>  valgrind: BASE_CFLAGS += -g
>  valgrind: all
>  
> +FUZZ_CC ?= afl-clang-fast
> +
> +.PHONY: fuzz
> +
> +fuzz:
> +	$(MAKE) clean
> +	$(MAKE) CC="$(FUZZ_CC)" CPPFLAGS="-DFUZZING -DNDEBUG" CFLAGS="-g -fsanitize=address" passt
> +
>  .PHONY: clean
>  clean:
>  	$(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h seccomp_pesto.h pasta.1 \
> diff --git a/passt.c b/passt.c
> index 5054551..e026eb2 100644
> --- a/passt.c
> +++ b/passt.c
> @@ -35,6 +35,7 @@
>  #include <sys/prctl.h>
>  #include <netinet/if_ether.h>
>  #include <libgen.h>
> +#include <netinet/tcp.h>
>  
>  #include "util.h"
>  #include "passt.h"
> @@ -54,12 +55,56 @@
>  #include "repair.h"
>  #include "netlink.h"
>  #include "epoll_ctl.h"
> +#include "flow_table.h"
> +#include "fuzz.h"
>  
>  #define NUM_EPOLL_EVENTS	8
>  
>  #define TIMER_INTERVAL_		MIN(TCP_TIMER_INTERVAL, FWD_PORT_SCAN_INTERVAL)
>  #define TIMER_INTERVAL		MIN(TIMER_INTERVAL_, FLOW_TIMER_INTERVAL)
>  
> +#ifdef FUZZING
> +
> +/* AFL++ persistent mode / shared memory fuzzing compatibility macros. */
> +#ifndef __AFL_FUZZ_TESTCASE_LEN
> +  ssize_t		fuzz_len;
> +  unsigned char		fuzz_buf[1024 * 1024];
> +# define __AFL_FUZZ_TESTCASE_LEN	fuzz_len
> +# define __AFL_FUZZ_TESTCASE_BUF	fuzz_buf
> +# define __AFL_FUZZ_INIT()		void sync(void)
> +# define __AFL_LOOP(x)		\
> +	((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
> +# define __AFL_INIT()		sync()
> +#endif
> +
> +#ifdef __AFL_HAVE_MANUAL_CONTROL
> +  __AFL_FUZZ_INIT();
> +#endif
> +
> +static struct fuzz_turn *fuzz_turn_ptr;
> +
> +/**
> + * fuzz_turn_connect() - Map the turn flag shared memory
> + *
> + * Return: pointer to mapped turn flag, or NULL on failure
> + */
> +static struct fuzz_turn *fuzz_turn_connect(void)
> +{
> +	struct fuzz_turn *t;
> +	int fd;
> +
> +	fd = open(FUZZ_TURN_PATH, O_RDWR);
> +	if (fd < 0)
> +		return NULL;
> +
> +	t = mmap(NULL, sizeof(*t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
> +	close(fd);
> +
> +	return (t == MAP_FAILED) ? NULL : t;
> +}
> +
> +#endif
> +
>  char pkt_buf[PKT_BUF_BYTES]	__attribute__ ((aligned(PAGE_SIZE)));
>  
>  struct ctx passt_ctx = {
> @@ -282,9 +327,17 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events)
>  			icmp_sock_handler(c, ref, &now);
>  			break;
>  		case EPOLL_TYPE_VHOST_CMD:
> +#ifdef FUZZING
> +			if (!c->vdev)
> +				break;
> +#endif
>  			vu_control_handler(c->vdev, c->fd_tap, eventmask);
>  			break;
>  		case EPOLL_TYPE_VHOST_KICK:
> +#ifdef FUZZING
> +			if (!c->vdev)
> +				break;
> +#endif
>  			vu_kick_cb(c->vdev, ref, &now);
>  			break;
>  		case EPOLL_TYPE_REPAIR_LISTEN:
> @@ -450,6 +503,141 @@ int main(int argc, char **argv)
>  
>  	timer_init(c, &now);
>  
> +#ifdef FUZZING
> +	fuzz_turn_ptr = fuzz_turn_connect();
> +
> +#define FUZZ_LOOP_ITERATIONS	10000
> +#define FUZZ_DRAIN_BUF_SIZE	1600
> +
> +#ifdef __AFL_HAVE_MANUAL_CONTROL
> +	__AFL_INIT();
> +#endif
> +	{
> +		unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
> +
> +		while (__AFL_LOOP(FUZZ_LOOP_ITERATIONS)) {

I think this loop is a useful implementation, as far as I understand
the purpose is to avoid that AFL++ needs to restarts us at every new
attempt. So I think it makes sense that you reset the state below.

But, inside this loop, we need to allow AFL++ to send us arbitrary
sequences of packets, not just inject a single one. Not much will
happen with a single packet.

> +			int len = __AFL_FUZZ_TESTCASE_LEN;
> +			int injected = 0;
> +			int pkt_len, round;
> +			struct epoll_event ev;
> +			union epoll_ref ref;
> +			int min_pkt = sizeof(struct ethhdr) +
> +				      sizeof(struct iphdr) +
> +				      sizeof(struct tcphdr);
> +
> +			if (len < (int)sizeof(ev))
> +				continue;
> +
> +			/* Reset clock, flow table and epoll for each
> +			 * AFL++ iteration.
> +			 */
> +			fuzz_clock_reset();
> +			clock_gettime(CLOCK_MONOTONIC, &now);
> +			timer_init(c, &now);
> +
> +			flow_init();
> +
> +			/* Recreate epoll instance */
> +			close(c->epollfd);
> +			c->epollfd = epoll_create1(EPOLL_CLOEXEC);
> +			flow_epollid_register(EPOLLFD_ID_DEFAULT, c->epollfd);
> +
> +			if (c->fd_tap >= 0) {
> +				union epoll_ref tref = {
> +					.type = EPOLL_TYPE_TAP_PASST,
> +					.fd = c->fd_tap
> +				};
> +				epoll_add(c->epollfd,
> +					  EPOLLIN | EPOLLRDHUP, tref);
> +
> +				/* Drain stale socket data */
> +				char drain[FUZZ_DRAIN_BUF_SIZE];
> +				while (recv(c->fd_tap, drain, sizeof(drain),
> +					    MSG_DONTWAIT) > 0);
> +			}
> +
> +			/* Read epoll event from AFL++ buffer */
> +			memcpy(&ev, buf, sizeof(ev));
> +			ref = *((union epoll_ref *)&ev.data.u64);

This is needed to let AFL++ generate events. But if you call
epoll_wait() below, with 'events' (which is not set from 'ev'), we
won't actually use those events generated by AFL++. I guess you're only
getting events from the test server. But I don't think that hardcoding
a sequence of:

- single packet from AFL++ (tap side)

- four packets from the test server (all host side I guess?)

will actually result in any meaningful exchange (including a TCP
connection).

By the way, in the approach I was suggesting, where AFL++ would act as
guest and feeding data to us directly, while the test server would act
as host / internet side (with data fed from AFL++), AFL++ would only
generate tap-side events, so we would probably need to *add* those to
'ev' while also reacting to host-side events (for example the test
server accepting a connection, or sending data over an accepted
connection).

> +
> +			/* Set recv payload in AFL++ shared memory */
> +			fuzz_recv_data = buf + FUZZ_RECV_OFF;
> +			fuzz_recv_data_len =
> +				(len > FUZZ_RECV_OFF + FUZZ_RECV_MAX)
> +					     ? FUZZ_RECV_MAX
> +					     : ((len > FUZZ_RECV_OFF)
> +						? len - FUZZ_RECV_OFF : 0);
> +
> +			/* Inject fuzz packet for TAP events */
> +			if (ref.type == EPOLL_TYPE_TAP_PASST ||
> +			    ref.type == EPOLL_TYPE_TAP_PASTA) {
> +				struct iov_tail data;
> +				struct ethhdr *eh;
> +				struct iphdr *iph;
> +				struct tcphdr *th;
> +
> +				tap_flush_pools();
> +				memset(pkt_buf, 0, min_pkt);
> +
> +				pkt_len = len - (int)sizeof(ev);
> +				if (pkt_len > 0)
> +					memcpy(pkt_buf, buf + sizeof(ev),
> +					       pkt_len);
> +				if (pkt_len < min_pkt)
> +					pkt_len = min_pkt;
> +
> +				/* construct ethernet header */

I guess this whole path is needed to quickly get something working,
but, eventually, we shouldn't need this. We need to give the
possibility to AFL++ to give us multiple packets, and possibly (or
especially) malformed ones.

If it just generates payload, that looks relatively "safe" and is
relatively unlikely to discover issues.

> +				eh = (struct ethhdr *)pkt_buf;
> +				memcpy(eh->h_dest, c->our_tap_mac, ETH_ALEN);
> +				memcpy(eh->h_source, c->guest_mac, ETH_ALEN);
> +				eh->h_proto = htons(ETH_P_IP);
> +
> +				/* construct IPv4 header */
> +				iph = (struct iphdr *)(pkt_buf + sizeof(*eh));
> +				iph->version = 4;
> +				iph->ihl = 5;
> +				iph->protocol = IPPROTO_TCP;
> +				iph->saddr = c->ip4.addr.s_addr;
> +				iph->daddr = c->ip4.guest_gw.s_addr;
> +				iph->tot_len = htons(pkt_len - sizeof(*eh));
> +
> +				/* Fix TCP Header */
> +				th = (struct tcphdr *)(pkt_buf + sizeof(*eh) +
> +								 sizeof(*iph));
> +				th->dest = htons(9999);
> +				if (th->doff < 5)
> +					th->doff = 5;
> +
> +				data = IOV_TAIL_FROM_BUF(pkt_buf, pkt_len, 0);
> +				tap_add_packet(c, &data, &now);
> +				tap_handler(c, &now);
> +				injected = 1;
> +			}
> +
> +			/* Turn exchange -- only if data was sent */
> +			if (injected && fuzz_turn_ptr) {
> +				__atomic_store_n(&fuzz_turn_ptr->turn, 1,
> +						 __ATOMIC_RELEASE);
> +				while (__atomic_load_n(&fuzz_turn_ptr->turn,
> +						      __ATOMIC_ACQUIRE) != 0);
> +			}
> +
> +			passt_worker(c, 1, &ev);
> +
> +			/* Process host-side TCP events */
> +			for (round = 0; round < 4; round++) {
> +				nfds = epoll_wait(c->epollfd, events,
> +						  NUM_EPOLL_EVENTS, 0);
> +				if (nfds <= 0)
> +					break;
> +				passt_worker(c, nfds, events);
> +			}
> +
> +			post_handler(c, &now);
> +		}
> +	}
> +	return 0;
> +#else
>  loop:
>  	/* NOLINTBEGIN(bugprone-branch-clone): intervals can be the same */
>  	/* cppcheck-suppress [duplicateValueTernary, unmatchedSuppression] */
> @@ -461,4 +649,5 @@ loop:
>  	passt_worker(c, nfds, events);
>  
>  	goto loop;
> +#endif /* FUZZING */
>  }

-- 
Stefano


  reply	other threads:[~2026-08-13  6:34 UTC|newest]

Thread overview: 10+ 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 [this message]
2026-08-12  7:26 ` [PATCH 5/5] fuzz: Add test server for bidirectional protocol fuzzing Anshu Kumari

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=20260813083352.2cbf2af3@elisabeth \
    --to=sbrivio@redhat.com \
    --cc=abdobngad@gmail.com \
    --cc=aerosound161@gmail.com \
    --cc=anskuma@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=lvivier@redhat.com \
    --cc=passt-dev@passt.top \
    /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).