From: Anshu Kumari <anskuma@redhat.com>
To: sbrivio@redhat.com, passt-dev@passt.top
Cc: lvivier@redhat.com, anskuma@redhat.com, abdobngad@gmail.com
Subject: [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop
Date: Mon, 28 Sep 2026 10:47:25 +0530 [thread overview]
Message-ID: <20260928051727.2251281-6-anskuma@redhat.com> (raw)
In-Reply-To: <20260928051727.2251281-1-anskuma@redhat.com>
Integrate AFL++ persistent mode into main(). Each iteration
resets clock, flow table and epoll state, then parses the
testcase buffer into epoll events, tap frames and TCP_INFO
data. Real and fuzz-injected events are interleaved so
protocol handshakes can complete. fuzz-server is fork+exec'd
once before the forkserver starts.
Add fuzz_flow_cleanup() in flow.c to close sockets and
timerfds leaked between iterations.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
flow.c | 52 +++++++++++++++
fuzz.h | 1 +
passt.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 251 insertions(+)
diff --git a/flow.c b/flow.c
index 71918b77..2e5ecc9f 100644
--- a/flow.c
+++ b/flow.c
@@ -1277,3 +1277,55 @@ void flow_init(void)
for (b = 0; b < FLOW_HASH_SIZE; b++)
flow_hashtab[b] = FLOW_SIDX_NONE;
}
+
+#ifdef FUZZING
+/**
+ * fuzz_flow_cleanup() - Close leaked fds from the previous AFL++ iteration
+ */
+void fuzz_flow_cleanup(void)
+{
+ union flow *flow;
+
+ flow_new_entry = NULL;
+ flow_first_free = 0;
+
+ flow_foreach_slot(flow) {
+ unsigned sidei;
+
+ if (flow->f.state < FLOW_STATE_TYPED)
+ continue;
+
+ switch (flow->f.type) {
+ case FLOW_TCP:
+ if (flow->tcp.sock >= 0)
+ close(flow->tcp.sock);
+ if (flow->tcp.timer >= 0)
+ close(flow->tcp.timer);
+ break;
+ case FLOW_TCP_SPLICE:
+ flow_foreach_sidei(sidei) {
+ if (flow->tcp_splice.s[sidei] >= 0)
+ close(flow->tcp_splice.s[sidei]);
+ if (flow->tcp_splice.pipe[sidei][0] >= 0) {
+ close(flow->tcp_splice.pipe[sidei][0]);
+ close(flow->tcp_splice.pipe[sidei][1]);
+ }
+ }
+ break;
+ case FLOW_PING4:
+ case FLOW_PING6:
+ if (flow->ping.sock >= 0)
+ close(flow->ping.sock);
+ break;
+ case FLOW_UDP:
+ flow_foreach_sidei(sidei) {
+ if (flow->udp.s[sidei] >= 0)
+ close(flow->udp.s[sidei]);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+#endif
diff --git a/fuzz.h b/fuzz.h
index 311d7e79..16327506 100644
--- a/fuzz.h
+++ b/fuzz.h
@@ -20,6 +20,7 @@ int fuzz_clock_gettime(clockid_t clk, struct timespec *tp);
void fuzz_clock_reset(void);
int fuzz_getsockopt(int fd, int level, int optname, void *optval,
socklen_t *optlen);
+void fuzz_flow_cleanup(void);
extern const unsigned char *fuzz_sockopt_data;
extern int fuzz_sockopt_data_len;
diff --git a/passt.c b/passt.c
index 50545511..6c45ea7b 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,37 @@
#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
+
+const unsigned char *fuzz_sockopt_data;
+int fuzz_sockopt_data_len;
+
+#endif
+
char pkt_buf[PKT_BUF_BYTES] __attribute__ ((aligned(PAGE_SIZE)));
struct ctx passt_ctx = {
@@ -282,9 +308,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:
@@ -315,6 +349,46 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events)
migrate_handler(c, &now);
}
+#ifdef FUZZING
+/**
+ * fuzz_inject_tap_frame() - Inject one AFL++-controlled raw L2 frame
+ * @c: Execution context
+ * @buf: AFL++ testcase buffer
+ * @cur: In/out cursor into the tap-frame region of @buf
+ * @end: End offset of the tap-frame region in @buf
+ * @now: Current timestamp
+ *
+ * Reads one length-prefixed frame (u16 length + bytes) from the tap
+ * region and hands it to the tap handlers.
+ */
+static void fuzz_inject_tap_frame(struct ctx *c, unsigned char *buf,
+ uint32_t *cur, int end,
+ const struct timespec *now)
+{
+ struct iov_tail data;
+ uint16_t flen = 0;
+
+ if ((int)(*cur + sizeof(flen)) > end)
+ return;
+
+ memcpy(&flen, buf + *cur, sizeof(flen));
+ *cur += sizeof(flen);
+
+ if ((int)(*cur + flen) > end)
+ flen = end - *cur;
+
+ if (flen > 0) {
+ tap_flush_pools();
+ memcpy(pkt_buf, buf + *cur, flen);
+ data = IOV_TAIL_FROM_BUF(pkt_buf, flen, 0);
+ tap_add_packet(c, &data, now);
+ tap_handler(c, now);
+ }
+
+ *cur += flen;
+}
+#endif
+
/**
* main() - Entry point and main loop
* @argc: Argument count
@@ -450,6 +524,129 @@ int main(int argc, char **argv)
timer_init(c, &now);
+#ifdef FUZZING
+
+#define FUZZ_LOOP_ITERATIONS 10000
+
+ /* Start fuzz-server before __AFL_INIT() and wait for it to
+ * bind all ports, otherwise early iterations race its
+ * bind()/listen() and every connect() gets ECONNREFUSED.
+ */
+ {
+ pid_t srv = fork();
+
+ if (srv < 0)
+ err_perror("fuzz: fork() for fuzz-server failed");
+ else if (srv == 0) {
+ execl("./fuzz-server", "fuzz-server", NULL);
+ _exit(1);
+ }
+
+ sleep(3);
+ }
+
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ __AFL_INIT();
+#endif
+ {
+ unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
+
+ while (__AFL_LOOP(FUZZ_LOOP_ITERATIONS)) {
+ int len = __AFL_FUZZ_TESTCASE_LEN;
+ struct epoll_event ev;
+ struct fuzz_layout fl;
+ union epoll_ref ref;
+ uint32_t tap_cur, i;
+ int tap_end, round;
+
+ if (len < (int)FUZZ_HDR_SIZE)
+ continue;
+
+ /* Close sockets and timerfds leaked by the
+ * previous iteration before resetting the table.
+ */
+ fuzz_flow_cleanup();
+ memset(flowtab, 0, FLOW_MAX * sizeof(*flowtab));
+
+ /* Reset clock, protocol state and
+ * epoll for each AFL++ iteration.
+ */
+ fuzz_clock_reset();
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timer_init(c, &now);
+
+ flow_init();
+
+ close(c->epollfd);
+ c->epollfd = epoll_create1(EPOLL_CLOEXEC);
+ flow_epollid_register(EPOLLFD_ID_DEFAULT, c->epollfd);
+
+ fl = fuzz_parse_layout(buf, len);
+ tap_cur = fl.tap_off;
+ tap_end = (int)(fl.tap_off + fl.tap_len);
+
+ fuzz_sockopt_data_len = (int)fl.sockopt_len;
+ if (fuzz_sockopt_data_len > 0)
+ fuzz_sockopt_data = buf + fl.sockopt_off;
+ else
+ fuzz_sockopt_data = NULL;
+
+ /* Mix real epoll events with fuzz events
+ * (one per iteration) so protocol handshakes
+ * complete between fuzzed inputs. Capped by
+ * FUZZ_MAX_ITER; FUZZ_DRAIN_ROUNDS extra
+ * iterations after fuzz events are exhausted.
+ */
+#define FUZZ_DRAIN_ROUNDS 16
+#define FUZZ_MAX_ITER 256
+
+ i = 0;
+ round = 0;
+ while (round < FUZZ_MAX_ITER) {
+ nfds = epoll_wait(c->epollfd, events,
+ NUM_EPOLL_EVENTS - 1, 0);
+ if (nfds < 0)
+ nfds = 0;
+
+ if (i < fl.n_events) {
+ memcpy(&ev, buf + FUZZ_HDR_SIZE
+ + i * sizeof(ev), sizeof(ev));
+ ref = *((union epoll_ref *)
+ &ev.data.u64);
+
+ i++;
+
+ if (ref.type >= EPOLL_NUM_TYPES)
+ continue;
+
+ if (ref.type == EPOLL_TYPE_TAP_PASST ||
+ ref.type == EPOLL_TYPE_TAP_PASTA) {
+ fuzz_inject_tap_frame(c, buf,
+ &tap_cur,
+ tap_end,
+ &now);
+ }
+
+ events[nfds] = ev;
+ nfds++;
+ }
+
+ if (nfds == 0 ||
+ (i >= fl.n_events &&
+ round >= (int)fl.n_events +
+ FUZZ_DRAIN_ROUNDS)) {
+ break;
+ }
+
+ round++;
+ 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 +658,5 @@ loop:
passt_worker(c, nfds, events);
goto loop;
+#endif /* FUZZING */
}
--
2.55.0
next prev parent reply other threads:[~2026-09-28 5:17 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 1/7] fuzz: Add AFL++ shared memory testcase buffer layout Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 2/7] fuzz: Add deterministic wrappers for assert, clock and getsockopt Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 3/7] fuzz: Guard protocol handlers against invalid fuzz-injected state Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 4/7] fuzz: Bypass sandboxing for fuzzing builds Anshu Kumari
2026-09-28 5:17 ` Anshu Kumari [this message]
2026-09-28 5:17 ` [PATCH v2 6/7] fuzz: Add host-side test server for bidirectional fuzzing Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 7/7] fuzz: Add build targets, namespace setup and documentation 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=20260928051727.2251281-6-anskuma@redhat.com \
--to=anskuma@redhat.com \
--cc=abdobngad@gmail.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).