* [PATCH v2 0/7] Add AFL++ fuzzing support for passt
@ 2026-09-28 5:17 Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 1/7] fuzz: Add AFL++ shared memory testcase buffer layout Anshu Kumari
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
This series adds integrated AFL++ fuzzing support for passt,
extending the earlier work by AbdAlRahman Gad with
persistent mode, bidirectional protocol fuzzing, and real
TCP connection coverage via a companion test server.
Each testcase is a flat buffer split into four regions:
epoll events, raw L2 tap frames, test-server payloads,
and getsockopt(TCP_INFO) overrides. AFL++ controls all
four through mutation of a 10-byte header.
Fuzz-injected epoll events are interleaved with real
kernel events so protocol handshakes can complete. A
standalone test server (fuzz-server) attaches to AFL++'s
shared memory and sends fuzzer-controlled payload on
every accepted TCP connection. AnyIP routing in a
rootless user+network namespace makes every destination
local, so the test server intercepts all outbound traffic
from passt without mocking recv().
Deterministic wrappers replace clock_gettime(),
getsockopt() and assert() to eliminate non-determinism
from kernel state. Sandboxing (seccomp, namespaces,
close_range, capabilities) is bypassed under FUZZING
builds since AFL++ needs its own fds and syscalls.
*** BLURB HERE ***
Anshu Kumari (7):
fuzz: Add AFL++ shared memory testcase buffer layout
fuzz: Add deterministic wrappers for assert, clock and getsockopt
fuzz: Guard protocol handlers against invalid fuzz-injected state
fuzz: Bypass sandboxing for fuzzing builds
fuzz: Add AFL++ persistent mode fuzz loop
fuzz: Add host-side test server for bidirectional fuzzing
fuzz: Add build targets, namespace setup and documentation
Makefile | 35 +++-
flow.c | 52 +++++
fuzz-server.c | 343 +++++++++++++++++++++++++++++++++
fuzz-testbuf.h | 135 +++++++++++++
fuzz.c | 102 ++++++++++
fuzz.h | 38 ++++
fuzzing/README.fuzzing.md | 129 +++++++++++++
fuzzing/fuzz-setup.sh | 24 +++
fuzzing/testcase_dir/empty.bin | Bin 0 -> 12 bytes
icmp.c | 8 +-
isolation.c | 23 +++
passt.c | 198 +++++++++++++++++++
tap.c | 13 ++
tcp.c | 18 +-
tcp_buf.c | 1 +
tcp_splice.c | 4 +-
udp.c | 29 ++-
udp_flow.c | 3 +-
util.c | 1 +
19 files changed, 1131 insertions(+), 25 deletions(-)
create mode 100644 fuzz-server.c
create mode 100644 fuzz-testbuf.h
create mode 100644 fuzz.c
create mode 100644 fuzz.h
create mode 100644 fuzzing/README.fuzzing.md
create mode 100755 fuzzing/fuzz-setup.sh
create mode 100644 fuzzing/testcase_dir/empty.bin
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/7] fuzz: Add AFL++ shared memory testcase buffer layout
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
@ 2026-09-28 5:17 ` Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 2/7] fuzz: Add deterministic wrappers for assert, clock and getsockopt Anshu Kumari
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Define the shared memory layout that both passt and the test
server use to interpret AFL++ testcase data. Each testcase
starts with a 10-byte header carrying explicit lengths:
[0..3] u32 n_events epoll events to inject
[4..5] u16 tap_len L2 tap frame data
[6..7] u16 testbuf_len test-server payload
[8..9] u16 sockopt_len getsockopt() overrides
followed by four variable-length payload regions:
(a) events: simulated epoll events for passt's main loop
(b) tap: length-prefixed raw L2 frames
(c) testbuf: payload the test server sends on connections
(d) sockopt: fuzzer-controlled TCP_INFO data
fuzz_parse_layout() reads the header and computes each
region's offset and length.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
Makefile | 13 ++---
fuzz-testbuf.h | 135 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 142 insertions(+), 6 deletions(-)
create mode 100644 fuzz-testbuf.h
diff --git a/Makefile b/Makefile
index b3152425..97c27f7c 100644
--- a/Makefile
+++ b/Makefile
@@ -47,12 +47,13 @@ SRCS = $(PASST_SRCS) $(PASST_REPAIR_SRCS) $(PESTO_SRCS)
MANPAGES = passt.1 pasta.1 pesto.1 passt-repair.1
PASST_HEADERS = arch.h arp.h bitmap.h checksum.h conf.h dhcp.h dhcpv6.h \
- epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h icmp.h icmp_flow.h \
- inany.h iov.h ip.h isolation.h lineread.h linux_dep.h log.h migrate.h \
- ndp.h netlink.h packet.h parse.h passt.h pasta.h pcap.h pif.h repair.h \
- serialise.h siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h \
- tcp_splice.h tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h \
- vhost_user.h virtio.h vu_common.h
+ epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h fuzz-testbuf.h \
+ icmp.h icmp_flow.h inany.h iov.h ip.h isolation.h lineread.h \
+ linux_dep.h log.h migrate.h ndp.h netlink.h packet.h parse.h \
+ passt.h pasta.h pcap.h pif.h repair.h serialise.h siphash.h tap.h \
+ tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h tcp_vu.h \
+ udp.h udp_flow.h udp_internal.h udp_vu.h util.h vhost_user.h \
+ virtio.h vu_common.h
PASST_REPAIR_HEADERS = linux_dep.h
PESTO_HEADERS = bitmap.h common.h fwd_rule.h inany.h ip.h log.h parse.h \
pesto.h serialise.h
diff --git a/fuzz-testbuf.h b/fuzz-testbuf.h
new file mode 100644
index 00000000..6366528f
--- /dev/null
+++ b/fuzz-testbuf.h
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* fuzz-testbuf.h - AFL++ testcase buffer layout shared between passt
+ * and the test server.
+ *
+ * Copyright Red Hat
+ * Author: Anshu Kumari <anskuma@redhat.com>
+ */
+
+#ifndef FUZZ_TESTBUF_H
+#define FUZZ_TESTBUF_H
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+/*
+ * AFL++ testcase buffer layout
+ *
+ * Each fuzz iteration receives a single flat buffer split into a
+ * fixed 10-byte header followed by four variable-length regions:
+ *
+ * Header (FUZZ_HDR_SIZE = 10 bytes):
+ * [0..3] u32 n_events number of epoll events
+ * [4..5] u16 tap_len bytes of tap (L2 frame) data
+ * [6..7] u16 testbuf_len bytes of test-server payload
+ * [8..9] u16 sockopt_len bytes of getsockopt() overrides
+ *
+ * Payload (starts at offset FUZZ_HDR_SIZE):
+ * (a) events: n_events * sizeof(struct epoll_event)
+ * Simulated epoll events consumed by passt.
+ * (b) tap: tap_len bytes of length-prefixed L2 frames
+ * injected into passt as tap input.
+ * (c) testbuf: testbuf_len bytes consumed only by the
+ * test server to send load to passt.
+ * (d) sockopt: sockopt_len bytes of fuzzer-controlled
+ * TCP_INFO data returned by getsockopt().
+ *
+ */
+#define FUZZ_EV_COUNT_OFF 0
+#define FUZZ_TAP_LEN_OFF 4
+#define FUZZ_TESTBUF_LEN_OFF 6
+#define FUZZ_SOCKOPT_LEN_OFF 8
+#define FUZZ_HDR_SIZE 10
+
+#define FUZZ_TAP_MAX (64 * 1024)
+#define FUZZ_TESTBUF_MAX (64 * 1024)
+#define FUZZ_SOCKOPT_MAX 256
+
+/**
+ * struct fuzz_layout - testcase layout
+ * @n_events: Number of epoll events in region (a)
+ * @tap_off: Byte offset of region (b) in the testcase
+ * @tap_len: Byte length of region (b)
+ * @testbuf_off: Byte offset of region (c) in the testcase
+ * @testbuf_len: Byte length of region (c)
+ * @sockopt_off: Byte offset of region (d) in the testcase
+ * @sockopt_len: Byte length of region (d)
+ */
+struct fuzz_layout {
+ uint32_t n_events;
+ uint32_t tap_off;
+ uint32_t tap_len;
+ uint32_t testbuf_off;
+ uint32_t testbuf_len;
+ uint32_t sockopt_off;
+ uint32_t sockopt_len;
+};
+
+/**
+ * fuzz_parse_layout() - Split a testcase into non-overlapping regions
+ * @buf: Raw AFL++ testcase buffer
+ * @total_len: Total byte length of @buf
+ *
+ * Each AFL++ testcase starts with a 10-byte header that says how
+ * large each payload region should be. This function reads that
+ * header and figures out where each region actually starts and
+ * ends, making sure nothing runs past the end of the buffer and
+ * no two regions overlap.
+ *
+ * Return: a fuzz_layout with offsets and lengths for every region,
+ * or all zeros if the buffer is too small for the header
+ */
+static inline struct fuzz_layout fuzz_parse_layout(const uint8_t *buf,
+ uint32_t total_len)
+{
+ struct fuzz_layout l = { 0 };
+ uint32_t raw_n_events;
+ uint32_t remaining;
+ uint16_t raw16;
+
+ if (total_len < FUZZ_HDR_SIZE)
+ return l;
+
+ memcpy(&raw_n_events, buf + FUZZ_EV_COUNT_OFF, sizeof(raw_n_events));
+
+ if (total_len > FUZZ_HDR_SIZE) {
+ uint32_t ev_space = total_len - FUZZ_HDR_SIZE;
+ uint32_t max_ev = ev_space / sizeof(struct epoll_event);
+
+ l.n_events = raw_n_events > max_ev ? max_ev : raw_n_events;
+ }
+
+ l.tap_off = FUZZ_HDR_SIZE + l.n_events * sizeof(struct epoll_event);
+ remaining = total_len > l.tap_off ? total_len - l.tap_off : 0;
+
+ memcpy(&raw16, buf + FUZZ_TAP_LEN_OFF, sizeof(raw16));
+ l.tap_len = raw16;
+ if (l.tap_len > FUZZ_TAP_MAX)
+ l.tap_len = FUZZ_TAP_MAX;
+ if (l.tap_len > remaining)
+ l.tap_len = remaining;
+ remaining -= l.tap_len;
+
+ l.testbuf_off = l.tap_off + l.tap_len;
+ memcpy(&raw16, buf + FUZZ_TESTBUF_LEN_OFF, sizeof(raw16));
+ l.testbuf_len = raw16;
+ if (l.testbuf_len > FUZZ_TESTBUF_MAX)
+ l.testbuf_len = FUZZ_TESTBUF_MAX;
+ if (l.testbuf_len > remaining)
+ l.testbuf_len = remaining;
+ remaining -= l.testbuf_len;
+
+ l.sockopt_off = l.testbuf_off + l.testbuf_len;
+ memcpy(&raw16, buf + FUZZ_SOCKOPT_LEN_OFF, sizeof(raw16));
+ l.sockopt_len = raw16;
+ if (l.sockopt_len > FUZZ_SOCKOPT_MAX)
+ l.sockopt_len = FUZZ_SOCKOPT_MAX;
+ if (l.sockopt_len > remaining)
+ l.sockopt_len = remaining;
+
+ return l;
+}
+
+#endif /* FUZZ_TESTBUF_H */
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 2/7] fuzz: Add deterministic wrappers for assert, clock and getsockopt
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 ` Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 3/7] fuzz: Guard protocol handlers against invalid fuzz-injected state Anshu Kumari
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Add fuzz_assert() macro that calls _exit(0) instead of abort()
so AFL++ treats assertion failures as normal exits rather than
crashes.
Add deterministic replacements for clock_gettime() and
getsockopt() that eliminate non-determinism from kernel state:
- fuzz_clock_gettime(): returns a monotonically incrementing
timestamp from a fixed baseline, reset each AFL++ iteration
- fuzz_getsockopt(): returns fuzzer-controlled TCP_INFO from
AFL++ shared memory (region d), and fixed values for
SO_ERROR/SO_RCVBUF/SO_SNDBUF
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
Makefile | 17 ++++++----
fuzz.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
fuzz.h | 37 ++++++++++++++++++++
3 files changed, 149 insertions(+), 7 deletions(-)
create mode 100644 fuzz.c
create mode 100644 fuzz.h
diff --git a/Makefile b/Makefile
index 97c27f7c..cce9ffd0 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,9 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \
parse.c passt.c pasta.c pcap.c pif.c repair.c serialise.c tap.c tcp.c \
tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \
vhost_user.c virtio.c vu_common.c
+ifneq ($(findstring FUZZING,$(CPPFLAGS)),)
+PASST_SRCS += fuzz.c
+endif
PASST_REPAIR_SRCS = passt-repair.c
PESTO_SRCS = pesto.c bitmap.c fwd_rule.c inany.c ip.c lineread.c parse.c \
serialise.c
@@ -47,13 +50,13 @@ SRCS = $(PASST_SRCS) $(PASST_REPAIR_SRCS) $(PESTO_SRCS)
MANPAGES = passt.1 pasta.1 pesto.1 passt-repair.1
PASST_HEADERS = arch.h arp.h bitmap.h checksum.h conf.h dhcp.h dhcpv6.h \
- epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h fuzz-testbuf.h \
- icmp.h icmp_flow.h inany.h iov.h ip.h isolation.h lineread.h \
- linux_dep.h log.h migrate.h ndp.h netlink.h packet.h parse.h \
- passt.h pasta.h pcap.h pif.h repair.h serialise.h siphash.h tap.h \
- tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h tcp_vu.h \
- udp.h udp_flow.h udp_internal.h udp_vu.h util.h vhost_user.h \
- virtio.h vu_common.h
+ epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h fuzz.h \
+ fuzz-testbuf.h icmp.h icmp_flow.h inany.h iov.h ip.h isolation.h \
+ lineread.h linux_dep.h log.h migrate.h ndp.h netlink.h packet.h \
+ parse.h passt.h pasta.h pcap.h pif.h repair.h serialise.h siphash.h \
+ tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h \
+ tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h \
+ vhost_user.h virtio.h vu_common.h
PASST_REPAIR_HEADERS = linux_dep.h
PESTO_HEADERS = bitmap.h common.h fwd_rule.h inany.h ip.h log.h parse.h \
pesto.h serialise.h
diff --git a/fuzz.c b/fuzz.c
new file mode 100644
index 00000000..89a47d76
--- /dev/null
+++ b/fuzz.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* fuzz.c - AFL++ fuzzing support: deterministic wrappers for
+ * clock_gettime() and getsockopt()
+ *
+ * Copyright Red Hat
+ * Author: Anshu Kumari <anskuma@redhat.com>
+ */
+
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <netinet/tcp.h>
+#include "passt.h"
+#include "fuzz.h"
+
+/* Undo macros so definitions here call the real syscalls */
+#undef clock_gettime
+#undef getsockopt
+
+#define FUZZ_CLOCK_BASE_SEC 10000
+
+static struct timespec fuzz_clock;
+
+/**
+ * fuzz_clock_reset() - Reset clock to fixed baseline
+ *
+ * Called at the start of every __AFL_LOOP iteration so
+ * the clock is identical regardless of iteration number.
+ */
+void fuzz_clock_reset(void)
+{
+ fuzz_clock.tv_sec = FUZZ_CLOCK_BASE_SEC;
+ fuzz_clock.tv_nsec = 0;
+}
+
+/**
+ * fuzz_clock_gettime() - Return deterministic time
+ * @clk: Clock ID
+ * @tp: Output timespec
+ *
+ * Return: 0 (always succeeds)
+ */
+int fuzz_clock_gettime(clockid_t clk, struct timespec *tp)
+{
+ (void)clk;
+ *tp = fuzz_clock;
+
+ /* increment the timestamp by 1 micro sec monotonically */
+ fuzz_clock.tv_nsec += 1000;
+ if (fuzz_clock.tv_nsec >= 1000000000) {
+ fuzz_clock.tv_sec++;
+ fuzz_clock.tv_nsec -= 1000000000;
+ }
+ return 0;
+}
+
+/**
+ * fuzz_getsockopt() - Deterministic getsockopt wrapper
+ * @fd: Socket file descriptor
+ * @level: Protocol level
+ * @optname: Option name
+ * @optval: Output buffer
+ * @optlen: In/out option length
+ *
+ * For TCP_INFO: populates optval from the AFL++ shared memory buffer.
+ * For SO_RCVBUF, SO_SNDBUF: returns deterministic values.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int fuzz_getsockopt(int fd, int level, int optname, void *optval,
+ socklen_t *optlen)
+{
+ if (level == SOL_SOCKET) {
+ if (optname == SO_RCVBUF || optname == SO_SNDBUF) {
+ *(int *)optval = 212992; /* default linux buff size */
+ *optlen = sizeof(int);
+ return 0;
+ }
+ }
+
+ if (level == SOL_TCP && optname == TCP_INFO) {
+ size_t fill = *optlen;
+ size_t copy_len;
+
+ memset(optval, 0, fill);
+
+ if (fuzz_sockopt_data && fuzz_sockopt_data_len > 0) {
+ copy_len = MIN(fill, (size_t)fuzz_sockopt_data_len);
+ memcpy(optval, fuzz_sockopt_data, copy_len);
+ fuzz_sockopt_data += copy_len;
+ fuzz_sockopt_data_len -= copy_len;
+ *optlen = copy_len;
+ } else {
+ *optlen = 0;
+ }
+
+ return 0;
+ }
+
+ return getsockopt(fd, level, optname, optval, optlen);
+}
diff --git a/fuzz.h b/fuzz.h
new file mode 100644
index 00000000..311d7e79
--- /dev/null
+++ b/fuzz.h
@@ -0,0 +1,37 @@
+//SPDX-License-Identifier: GPL-2.0-or-later
+
+/* fuzz.h - AFL++ fuzzing support for passt
+ *
+ * Copyright Red Hat
+ * Author: Anshu Kumari <anskuma@redhat.com>
+ */
+
+#ifndef FUZZ_H
+#define FUZZ_H
+
+#ifdef FUZZING
+
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include "fuzz-testbuf.h"
+
+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);
+
+extern const unsigned char *fuzz_sockopt_data;
+extern int fuzz_sockopt_data_len;
+
+#define clock_gettime(clk, tp) fuzz_clock_gettime(clk, tp)
+#define getsockopt(fd, level, name, val, len) \
+ fuzz_getsockopt(fd, level, name, val, len)
+
+#define fuzz_assert(expr) do { if (!(expr)) _exit(0); } while (0)
+#else /* !FUZZING */
+#include <assert.h>
+#define fuzz_assert(expr) assert(expr)
+#endif /* FUZZING */
+
+#endif /* FUZZ_H */
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 3/7] fuzz: Guard protocol handlers against invalid fuzz-injected state
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 ` Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 4/7] fuzz: Bypass sandboxing for fuzzing builds Anshu Kumari
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Use fuzz_assert() instead of assert() for flow lookups, add NULL
checks after conn_at_sidx()/udp_at_sidx(), and validate timer
references — all no-ops in non-fuzzing builds.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
icmp.c | 8 +++++---
tap.c | 13 +++++++++++++
tcp.c | 18 ++++++++++++------
tcp_buf.c | 1 +
tcp_splice.c | 4 +++-
udp.c | 29 +++++++++++++++++++++--------
udp_flow.c | 3 ++-
util.c | 1 +
8 files changed, 58 insertions(+), 19 deletions(-)
diff --git a/icmp.c b/icmp.c
index 0fe23667..539f8e77 100644
--- a/icmp.c
+++ b/icmp.c
@@ -39,6 +39,7 @@
#include "icmp.h"
#include "flow_table.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
#define ICMP_ECHO_TIMEOUT 60 /* s, timeout for ICMP socket activity */
#define ICMP_NUM_IDS (1U << 16)
@@ -58,7 +59,7 @@ static struct icmp_ping_flow *ping_at_sidx(flow_sidx_t sidx)
if (!flow)
return NULL;
- assert(flow->f.type == FLOW_PING4 || flow->f.type == FLOW_PING6);
+ fuzz_assert(flow->f.type == FLOW_PING4 || flow->f.type == FLOW_PING6);
return &flow->ping;
}
@@ -72,7 +73,7 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref,
const struct timespec *now)
{
struct icmp_ping_flow *pingf = ping_at_sidx(ref.flowside);
- const struct flowside *ini = &pingf->f.side[INISIDE];
+ const struct flowside *ini;
union sockaddr_inany sr;
socklen_t sl = sizeof(sr);
char buf[USHRT_MAX];
@@ -82,7 +83,8 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref,
if (c->no_icmp)
return;
- assert(pingf);
+ fuzz_assert(pingf);
+ ini = &pingf->f.side[INISIDE];
n = recvfrom(ref.fd, buf, sizeof(buf), 0, &sr.sa, &sl);
if (n < 0) {
diff --git a/tap.c b/tap.c
index dfa66c71..b8e6b61f 100644
--- a/tap.c
+++ b/tap.c
@@ -14,6 +14,7 @@
*/
#include <sched.h>
+#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
@@ -61,6 +62,7 @@
#include "vhost_user.h"
#include "vu_common.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
/* Maximum allowed frame lengths (including L2 header) */
@@ -512,6 +514,9 @@ size_t tap_send_frames(const struct ctx *c, const struct iovec *iov,
{
size_t m;
+#ifdef FUZZING
+ return nframes;
+#endif
if (c->fd_tap == -1)
return nframes;
@@ -1229,6 +1234,10 @@ static void tap_passt_input(struct ctx *c, const struct timespec *now)
ssize_t n;
char *p;
+#ifdef FUZZING
+ partial_frame = NULL;
+ partial_len = 0;
+#endif
tap_flush_pools();
if (partial_len) {
@@ -1412,6 +1421,10 @@ static void tap_sock_unix_init(const struct ctx *c)
*/
bool tap_is_ready(const struct ctx *c)
{
+#ifdef FUZZING
+ (void)c;
+ return true;
+#endif
if (c->fd_tap < 0)
return false;
diff --git a/tcp.c b/tcp.c
index 3b78d2ed..f491264d 100644
--- a/tcp.c
+++ b/tcp.c
@@ -316,6 +316,7 @@
#include "tcp_buf.h"
#include "tcp_vu.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
/*
* The size of TCP header (including options) is given by doff (Data Offset)
@@ -456,7 +457,7 @@ static struct tcp_tap_conn *conn_at_sidx(flow_sidx_t sidx)
if (!flow)
return NULL;
- assert(flow->f.type == FLOW_TCP);
+ fuzz_assert(flow->f.type == FLOW_TCP);
return &flow->tcp;
}
@@ -2606,7 +2607,7 @@ void tcp_listen_handler(const struct ctx *c, union epoll_ref ref,
union flow *flow;
int s;
- assert(!c->no_tcp);
+ fuzz_assert(!c->no_tcp);
if (!(flow = flow_alloc()))
return;
@@ -2681,7 +2682,11 @@ void tcp_timer_handler(const struct ctx *c, union epoll_ref ref,
const struct timespec *now)
{
struct itimerspec check_armed = { { 0 }, { 0 } };
- struct tcp_tap_conn *conn = &FLOW(ref.flow)->tcp;
+ struct tcp_tap_conn *conn;
+
+ fuzz_assert(ref.flow < FLOW_MAX);
+ fuzz_assert(FLOW(ref.flow)->f.type == FLOW_TCP);
+ conn = &FLOW(ref.flow)->tcp;
assert(!c->no_tcp);
assert(conn->f.type == FLOW_TCP);
@@ -2752,8 +2757,9 @@ void tcp_sock_handler(const struct ctx *c, union epoll_ref ref,
{
struct tcp_tap_conn *conn = conn_at_sidx(ref.flowside);
- assert(!c->no_tcp);
- assert(pif_at_sidx(ref.flowside) != PIF_TAP);
+ fuzz_assert(!c->no_tcp);
+ fuzz_assert(conn);
+ fuzz_assert(pif_at_sidx(ref.flowside) != PIF_TAP);
if (conn->events == CLOSED)
return;
@@ -2939,7 +2945,7 @@ static void tcp_get_rto_params(struct ctx *c)
*/
int tcp_init(struct ctx *c)
{
- assert(!c->no_tcp);
+ fuzz_assert(!c->no_tcp);
tcp_get_rto_params(c);
diff --git a/tcp_buf.c b/tcp_buf.c
index 72c45412..eb28abeb 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -32,6 +32,7 @@
#include "tcp_conn.h"
#include "tcp_internal.h"
#include "tcp_buf.h"
+#include "fuzz.h"
#define TCP_FRAMES_MEM 128
#define TCP_FRAMES \
diff --git a/tcp_splice.c b/tcp_splice.c
index 4b01f1aa..f688dbdf 100644
--- a/tcp_splice.c
+++ b/tcp_splice.c
@@ -56,6 +56,7 @@
#include "inany.h"
#include "flow.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
#include "flow_table.h"
@@ -105,7 +106,7 @@ static struct tcp_splice_conn *conn_at_sidx(flow_sidx_t sidx)
if (!flow)
return NULL;
- assert(flow->f.type == FLOW_TCP_SPLICE);
+ fuzz_assert(flow->f.type == FLOW_TCP_SPLICE);
return &flow->tcp_splice;
}
@@ -594,6 +595,7 @@ void tcp_splice_sock_handler(struct ctx *c, union epoll_ref ref,
struct tcp_splice_conn *conn = conn_at_sidx(ref.flowside);
unsigned evsidei = ref.flowside.sidei;
+ fuzz_assert(conn);
assert(conn->f.type == FLOW_TCP_SPLICE);
if (conn->events == SPLICE_CLOSED)
diff --git a/udp.c b/udp.c
index 505e5540..198ec4da 100644
--- a/udp.c
+++ b/udp.c
@@ -118,6 +118,7 @@
#include "udp_internal.h"
#include "udp_vu.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
#define UDP_MAX_FRAMES 32 /* max # of frames to receive at once */
@@ -629,7 +630,7 @@ static int udp_sock_recverr(const struct ctx *c, int s, flow_sidx_t sidx,
}
uflow = udp_at_sidx(sidx);
- assert(uflow);
+ fuzz_assert(uflow);
fromside = &uflow->f.side[sidx.sidei];
toside = &uflow->f.side[!sidx.sidei];
topif = uflow->f.pif[!sidx.sidei];
@@ -698,7 +699,8 @@ static int udp_sock_errs(const struct ctx *c, int s, flow_sidx_t sidx,
socklen_t errlen;
int rc, err;
- assert(!c->no_udp);
+ fuzz_assert(!c->no_udp);
+ fuzz_assert(uflow);
/* Empty the error queue */
while ((rc = udp_sock_recverr(c, s, sidx, pif, port, now)) > 0)
@@ -780,7 +782,7 @@ static int udp_peek_addr(int s, union sockaddr_inany *src,
*/
static int udp_sock_recv(const struct ctx *c, int s, struct mmsghdr *mmh, int n)
{
- assert(!c->no_udp);
+ fuzz_assert(!c->no_udp);
n = recvmmsg(s, mmh, n, 0, NULL);
if (n < 0) {
@@ -807,9 +809,12 @@ static void udp_sock_to_sock(const struct ctx *c, int from_s, int n,
const struct flowside *toside = flowside_at_sidx(tosidx);
const struct udp_flow *uflow = udp_at_sidx(tosidx);
uint8_t topif = pif_at_sidx(tosidx);
- int to_s = uflow->s[tosidx.sidei];
+ int to_s;
int i;
+ fuzz_assert(toside && uflow);
+ to_s = uflow->s[tosidx.sidei];
+
if ((n = udp_sock_recv(c, from_s, udp_mh_recv, n)) <= 0)
return;
@@ -836,9 +841,12 @@ static void udp_buf_sock_to_tap(const struct ctx *c, int s, int n,
{
const struct flowside *toside = flowside_at_sidx(tosidx);
struct udp_flow *uflow = udp_at_sidx(tosidx);
- uint8_t *omac = uflow->f.tap_omac;
+ uint8_t *omac;
int i;
+ fuzz_assert(toside && uflow);
+ omac = uflow->f.tap_omac;
+
if ((n = udp_sock_recv(c, s, udp_mh_recv, n)) <= 0)
return;
@@ -884,6 +892,9 @@ void udp_sock_fwd(const struct ctx *c, int s, int rule_hint,
pif_name(frompif), port);
/* FIXME: what now? close/re-open socket? */
}
+#ifdef FUZZING
+ break;
+#endif
continue;
}
@@ -901,10 +912,12 @@ void udp_sock_fwd(const struct ctx *c, int s, int rule_hint,
} else if (flow_sidx_valid(tosidx)) {
struct udp_flow *uflow = udp_at_sidx(tosidx);
+ fuzz_assert(uflow);
flow_err_ratelimit(
uflow, now,
"No support for forwarding UDP from %s to %s",
pif_name(frompif), pif_name(topif));
+
discard = true;
} else {
warn_ratelimit(now, "Discarding datagram without flow");
@@ -949,7 +962,7 @@ void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
{
struct udp_flow *uflow = udp_at_sidx(ref.flowside);
- assert(!c->no_udp && uflow);
+ fuzz_assert(!c->no_udp && uflow);
if (events & EPOLLERR) {
if (udp_sock_errs(c, ref.fd, ref.flowside,
@@ -1034,7 +1047,7 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
in_port_t src, dst;
uint8_t topif;
- assert(!c->no_udp);
+ fuzz_assert(!c->no_udp);
if (!packet_get(p, idx, &data))
return 1;
@@ -1183,7 +1196,7 @@ static void udp_get_timeout_params(struct ctx *c)
*/
int udp_init(struct ctx *c)
{
- assert(!c->no_udp);
+ fuzz_assert(!c->no_udp);
udp_get_timeout_params(c);
diff --git a/udp_flow.c b/udp_flow.c
index f59649f6..7500be14 100644
--- a/udp_flow.c
+++ b/udp_flow.c
@@ -16,6 +16,7 @@
#include "flow_table.h"
#include "udp_internal.h"
#include "epoll_ctl.h"
+#include "fuzz.h"
/**
* udp_at_sidx() - Get UDP specific flow at given sidx
@@ -31,7 +32,7 @@ struct udp_flow *udp_at_sidx(flow_sidx_t sidx)
if (!flow)
return NULL;
- assert(flow->f.type == FLOW_UDP);
+ fuzz_assert(flow->f.type == FLOW_UDP);
return &flow->udp;
}
diff --git a/util.c b/util.c
index 28c32e43..a5e23299 100644
--- a/util.c
+++ b/util.c
@@ -36,6 +36,7 @@
#include "epoll_ctl.h"
#include "pasta.h"
#include "serialise.h"
+#include "fuzz.h"
#ifdef HAS_GETRANDOM
#include <sys/random.h>
#endif
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 4/7] fuzz: Bypass sandboxing for fuzzing builds
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
` (2 preceding siblings ...)
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 ` Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop Anshu Kumari
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Skip isolation steps that prevent AFL++ from operating:
- close_range(): AFL++ needs its shared memory fds open
- User namespace and setuid/setgid: the fuzzer runs in a
separate rootless namespace, not passt's own
- Capability dropping: not needed without real isolation
- Seccomp BPF filters: the fuzz loop uses syscalls
(epoll_create1, fork, execl).
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
isolation.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/isolation.c b/isolation.c
index a30b329f..0d1e6423 100644
--- a/isolation.c
+++ b/isolation.c
@@ -275,6 +275,7 @@ int isolate_fds(int argc, char **argv)
fd = close_from++;
}
+#ifndef FUZZING
if (close_range(close_from, ~0U, CLOSE_RANGE_UNSHARE)) {
if (errno == ENOSYS || errno == EINVAL) {
/* This probably means close_range() or the
@@ -288,6 +289,9 @@ int isolate_fds(int argc, char **argv)
die_perror("Failed to close files leaked by parent");
}
}
+#else
+ (void)close_from;
+#endif /* !FUZZING */
return fd;
}
@@ -311,6 +315,7 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
{
uint64_t ns_caps = 0;
+#ifndef FUZZING
/* First set our UID & GID in the original namespace */
if (setgroups(0, NULL)) {
/* If we don't have CAP_SETGID, this will EPERM */
@@ -340,6 +345,10 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
if (unshare(CLONE_NEWUSER) != 0)
die_perror("Couldn't create user namespace");
}
+#else
+ (void)uid;
+ (void)gid;
+#endif /* !FUZZING */
/* Joining a new userns gives us full capabilities; drop the
* ones we don't need. With --netns-only we haven't changed
@@ -371,7 +380,11 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
ns_caps |= BIT(CAP_SYS_PTRACE);
}
+#ifndef FUZZING
drop_caps_ep_except(ns_caps);
+#else
+ (void)ns_caps;
+#endif /* !FUZZING */
}
/**
@@ -389,6 +402,7 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
*/
int isolate_prefork(const struct ctx *c)
{
+#ifndef FUZZING
int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
uint64_t ns_caps = 0;
@@ -452,6 +466,9 @@ int isolate_prefork(const struct ctx *c)
clamp_caps();
drop_caps_ep_except(ns_caps);
+#else
+ (void)c;
+#endif /* !FUZZING */
return 0;
}
@@ -466,10 +483,13 @@ int isolate_prefork(const struct ctx *c)
*/
void isolate_postfork(const struct ctx *c)
{
+#ifndef FUZZING
struct sock_fprog prog;
+#endif /* !FUZZING */
prctl(PR_SET_DUMPABLE, 0);
+#ifndef FUZZING
switch (c->mode) {
case MODE_PASST:
prog.len = (unsigned short)ARRAY_SIZE(filter_passt);
@@ -490,4 +510,7 @@ void isolate_postfork(const struct ctx *c)
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
die_perror("Failed to apply seccomp filter");
+#else
+ (void)c;
+#endif /* !FUZZING */
}
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
` (3 preceding siblings ...)
2026-09-28 5:17 ` [PATCH v2 4/7] fuzz: Bypass sandboxing for fuzzing builds Anshu Kumari
@ 2026-09-28 5:17 ` Anshu Kumari
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
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
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
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 6/7] fuzz: Add host-side test server for bidirectional fuzzing
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
` (4 preceding siblings ...)
2026-09-28 5:17 ` [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop Anshu Kumari
@ 2026-09-28 5:17 ` Anshu Kumari
2026-09-28 5:17 ` [PATCH v2 7/7] fuzz: Add build targets, namespace setup and documentation Anshu Kumari
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Add fuzz-server, a standalone program that acts as a host-side
peer for TCP connections from passt during fuzzing.
It attaches to AFL++'s shared memory segment (via the inherited
__AFL_SHM_FUZZ_ID env var) and sends region (c) payload on
every accepted connection and after each read, enabling
bidirectional protocol fuzzing without mocking recv().
Combined with AnyIP routing in the fuzzing namespace, fuzz-server
listens on TCP ports (1-55535) on 0.0.0.0 to intercept every
outbound connection from passt regardless of destination IP or
port.
Fork+exec'd by passt before __AFL_INIT(), so it starts once
in the forkserver parent and persists across iterations.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
fuzz-server.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 343 insertions(+)
create mode 100644 fuzz-server.c
diff --git a/fuzz-server.c b/fuzz-server.c
new file mode 100644
index 00000000..15f4348d
--- /dev/null
+++ b/fuzz-server.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* fuzz-server.c - Host-side test peer for AFL++ fuzzing of passt
+ *
+ * Accepts TCP connections from passt and sends AFL++-controlled
+ * payload read directly from AFL++'s shared memory (region c of
+ * the testcase buffer).
+ *
+ * Started by passt (fork+exec before __AFL_INIT), inherits the
+ * __AFL_SHM_FUZZ_ID env var and attaches directly.
+ *
+ * Runs in a network namespace with AnyIP routing, listening on
+ * TCP ports 1 through FUZZ_LISTEN_MAX on both IPv4 and IPv6.
+ * The top FUZZ_RESERVED_PORTS ports are left free for passt's
+ * own connect() and bind() calls.
+ *
+ * Build: make fuzz-server
+ * Run: started automatically by passt when built with FUZZING
+ *
+ * Copyright Red Hat
+ * Author: Anshu Kumari <anskuma@redhat.com>
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/epoll.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+
+#include "fuzz-testbuf.h"
+
+#define FUZZ_MAX_PORT 65535
+#define FUZZ_RESERVED_PORTS 10000
+#define FUZZ_LISTEN_MAX (FUZZ_MAX_PORT - FUZZ_RESERVED_PORTS)
+#define MAX_EVENTS 64
+
+#define TYPE_LISTENER 1
+#define TYPE_CONNECTION 2
+
+static uint8_t *afl_shmem;
+static int epfd = -1;
+
+/**
+ * map_afl_shmem() - Attach to AFL++'s shared memory segment
+ *
+ * Reads __AFL_SHM_FUZZ_ID env var (inherited when passt fork+execs).
+ *
+ * Return: 0 on success, -1 on failure
+ */
+static int map_afl_shmem(void)
+{
+ const char *env;
+ char *endptr;
+ void *ptr;
+ long id;
+
+ env = getenv("__AFL_SHM_FUZZ_ID");
+ if (!env)
+ return -1;
+
+ errno = 0;
+ id = strtol(env, &endptr, 10);
+ if (errno || *endptr || endptr == env)
+ return -1;
+
+ ptr = shmat((int)id, NULL, SHM_RDONLY);
+ if (ptr == (void *)-1)
+ return -1;
+
+ afl_shmem = ptr;
+ return 0;
+}
+
+/**
+ * listen_on() - Create one non-blocking listening socket
+ * @af: Address family, AF_INET or AF_INET6
+ * @port: TCP port to bind
+ *
+ * Return: listening socket, or -1 if it can't be created or bound
+ */
+static int listen_on(int af, int port)
+{
+ struct sockaddr_in6 sa6 = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = in6addr_any,
+ .sin6_port = htons(port),
+ };
+ struct sockaddr_in sa4 = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ .sin_port = htons(port),
+ };
+ const struct sockaddr *sa;
+ int fd, opt = 1;
+ socklen_t sl;
+
+ fd = socket(af, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -1;
+
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
+
+ if (af == AF_INET6) {
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
+ sa = (const struct sockaddr *)&sa6;
+ sl = sizeof(sa6);
+ } else {
+ sa = (const struct sockaddr *)&sa4;
+ sl = sizeof(sa4);
+ }
+
+ if (bind(fd, sa, sl) || listen(fd, 128)) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+/**
+ * epoll_add() - Register @fd in the event loop, tagged with @type
+ * @fd: File descriptor to watch
+ * @type: TYPE_LISTENER or TYPE_CONNECTION
+ * @events: epoll event mask
+ */
+static void epoll_add(int fd, uint32_t type, uint32_t events)
+{
+ struct epoll_event ev = {
+ .events = events,
+ .data.u64 = ((uint64_t)type << 32) | (uint32_t)fd,
+ };
+
+ epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
+}
+
+/**
+ * server_init() - Create TCP listeners on every port, IPv4 and IPv6
+ *
+ * Binds to 0.0.0.0 and [::] on ports 1 through FUZZ_LISTEN_MAX.
+ * Ports that fail to bind are skipped.
+ *
+ * Return: 0 on success, -1 on failure
+ */
+static int server_init(void)
+{
+ struct rlimit rl;
+ int port;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl))
+ return -1;
+
+ rl.rlim_cur = rl.rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, &rl))
+ return -1;
+
+ epfd = epoll_create1(EPOLL_CLOEXEC);
+ if (epfd < 0)
+ return -1;
+
+ for (port = 1; port <= FUZZ_LISTEN_MAX; port++) {
+ int fd;
+
+ if ((fd = listen_on(AF_INET, port)) >= 0)
+ epoll_add(fd, TYPE_LISTENER, EPOLLIN);
+
+ if ((fd = listen_on(AF_INET6, port)) >= 0)
+ epoll_add(fd, TYPE_LISTENER, EPOLLIN);
+ }
+
+ return 0;
+}
+
+/**
+ * send_fuzz_payload() - Send region (c) from AFL++ shared memory
+ * @fd: Connected non-blocking socket
+ *
+ * Reads the testcase length and event count from AFL++'s shared
+ * memory, computes the region (c) offset and length, and sends
+ * the payload to @fd, tolerating short writes.
+ */
+static void send_fuzz_payload(int fd)
+{
+ const uint8_t *testcase, *data;
+ struct fuzz_layout fl;
+ uint32_t total_len;
+ size_t off = 0;
+ size_t len;
+
+ if (!afl_shmem)
+ return;
+
+ memcpy(&total_len, afl_shmem, sizeof(total_len));
+ testcase = afl_shmem + sizeof(uint32_t);
+ fl = fuzz_parse_layout(testcase, total_len);
+
+ if (!fl.testbuf_len)
+ return;
+
+ data = testcase + fl.testbuf_off;
+ len = (size_t)fl.testbuf_len;
+
+ while (off < len) {
+ ssize_t n = send(fd, data + off, len - off, MSG_NOSIGNAL);
+
+ if (n > 0) {
+ off += (size_t)n;
+ continue;
+ }
+
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ struct pollfd pfd = { .fd = fd, .events = POLLOUT };
+
+ if (poll(&pfd, 1, 50) <= 0)
+ return;
+ continue;
+ }
+
+ if (n < 0 && errno == EINTR)
+ continue;
+
+ return;
+ }
+}
+
+/**
+ * handle_accept() - Accept connections and send test payload
+ * @lfd: Listening socket file descriptor
+ *
+ * For each accepted connection, sends the current region (c) data
+ * from AFL++'s shared memory and registers the connection for
+ * further I/O events.
+ */
+static void handle_accept(int lfd)
+{
+ int fd;
+
+ while ((fd = accept4(lfd, NULL, NULL,
+ SOCK_NONBLOCK | SOCK_CLOEXEC)) >= 0) {
+ send_fuzz_payload(fd);
+ epoll_add(fd, TYPE_CONNECTION,
+ EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR);
+ }
+}
+
+/**
+ * handle_data() - Read data from passt and reply with fuzz payload
+ * @fd: Connected TCP socket
+ */
+static void handle_data(int fd)
+{
+ uint8_t buf[4096];
+ ssize_t n;
+
+ n = read(fd, buf, sizeof(buf));
+ if (n <= 0) {
+ epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
+ close(fd);
+ return;
+ }
+
+ send_fuzz_payload(fd);
+}
+
+/**
+ * main() - Server entry point
+ *
+ * Attaches to AFL++ shared memory via env var,
+ * binds all ports, then enters the epoll loop. Dies automatically
+ * when the parent (passt forkserver) exits.
+ *
+ * Return: 0 on success, 1 on initialization failure
+ */
+int main(void)
+{
+ struct epoll_event events[MAX_EVENTS];
+ int nfds, i;
+
+ signal(SIGPIPE, SIG_IGN);
+ prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+ if (map_afl_shmem() < 0) {
+ (void)fprintf(stderr,
+ "fuzz-server: __AFL_SHM_FUZZ_ID not set, "
+ "running without AFL++ shared memory\n");
+ } else {
+ (void)fprintf(stderr,
+ "fuzz-server: attached to AFL++ shared memory\n");
+ }
+
+ if (server_init() < 0) {
+ perror("fuzz-server: server_init");
+ return 1;
+ }
+
+ (void)fprintf(stderr,
+ "fuzz-server: listening on ports 1-%d, "
+ "%d-%d reserved for passt, IPv4+IPv6\n",
+ FUZZ_LISTEN_MAX, FUZZ_LISTEN_MAX + 1, FUZZ_MAX_PORT);
+
+ while (1) {
+ nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
+ if (nfds < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("fuzz-server: epoll_wait");
+ return 1;
+ }
+
+ for (i = 0; i < nfds; i++) {
+ uint32_t type = events[i].data.u64 >> 32;
+ int fd = (int)(events[i].data.u64 & 0xFFFFFFFF);
+
+ if (type == TYPE_LISTENER) {
+ handle_accept(fd);
+ } else if (type == TYPE_CONNECTION) {
+ if (events[i].events &
+ (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) {
+ epoll_ctl(epfd, EPOLL_CTL_DEL,
+ fd, NULL);
+ close(fd);
+ } else if (events[i].events & EPOLLIN) {
+ handle_data(fd);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 7/7] fuzz: Add build targets, namespace setup and documentation
2026-09-28 5:17 [PATCH v2 0/7] Add AFL++ fuzzing support for passt Anshu Kumari
` (5 preceding siblings ...)
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 ` Anshu Kumari
6 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-09-28 5:17 UTC (permalink / raw)
To: sbrivio, passt-dev; +Cc: lvivier, anskuma, abdobngad
Add Makefile targets for building and running AFL++ fuzzing:
- fuzz: AFL++-instrumented binary with AddressSanitizer
- fuzz-server: host-side test server
Add supporting scripts and data:
- fuzzing/fuzz-setup.sh: creates a rootless user+network namespace
with AnyIP routing so fuzz-server intercepts all outbound TCP
from passt, no root access needed
- fuzzing/testcase_dir/empty.bin: minimal seed input
- fuzzing/README.fuzzing.md: prerequisites, build instructions,
namespace setup, single and multi-core fuzzing invocations,
architecture overview, seed inputs, reproducing crashes
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
Makefile | 21 +++++-
fuzzing/README.fuzzing.md | 129 +++++++++++++++++++++++++++++++++
fuzzing/fuzz-setup.sh | 24 ++++++
fuzzing/testcase_dir/empty.bin | Bin 0 -> 12 bytes
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 fuzzing/README.fuzzing.md
create mode 100755 fuzzing/fuzz-setup.sh
create mode 100644 fuzzing/testcase_dir/empty.bin
diff --git a/Makefile b/Makefile
index cce9ffd0..5167929a 100644
--- a/Makefile
+++ b/Makefile
@@ -127,11 +127,30 @@ valgrind: BASE_CPPFLAGS += -DVALGRIND
valgrind: BASE_CFLAGS += -g
valgrind: all
+FUZZ_CC ?= afl-clang-fast
+FUZZ_CPPFLAGS := -DFUZZING -DNDEBUG
+
+.PHONY: fuzz-objs-clean fuzz
+
+fuzz-objs-clean:
+ $(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h seccomp_pesto.h pasta.1
+
+fuzz: fuzz-server
+ $(MAKE) fuzz-objs-clean
+ $(MAKE) CC="$(FUZZ_CC)" CPPFLAGS="$(FUZZ_CPPFLAGS)" \
+ CFLAGS="-g -fsanitize=address" passt
+ mv passt passt.fuzz
+
+
+fuzz-server: fuzz-server.c fuzz-testbuf.h
+ $(CC) -D_GNU_SOURCE -O2 -o $@ $<
+
.PHONY: clean
clean:
$(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h seccomp_pesto.h pasta.1 \
passt.tar passt.tar.gz *.deb *.rpm \
- passt.pid README.plain.md
+ passt.pid README.plain.md \
+ fuzz-server passt.fuzz
install: $(BIN) $(MANPAGES) docs
mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
diff --git a/fuzzing/README.fuzzing.md b/fuzzing/README.fuzzing.md
new file mode 100644
index 00000000..1eba36ed
--- /dev/null
+++ b/fuzzing/README.fuzzing.md
@@ -0,0 +1,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
+```
diff --git a/fuzzing/fuzz-setup.sh b/fuzzing/fuzz-setup.sh
new file mode 100755
index 00000000..130016bc
--- /dev/null
+++ b/fuzzing/fuzz-setup.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# fuzzing/fuzz-setup.sh - Rootless network namespace for AFL++ fuzzing
+#
+# Creates a user + network namespace (no root required) with AnyIP
+# routing so the test server intercepts all outbound TCP connections
+# from passt, regardless of destination IP/port or address family.
+#
+# Usage:
+# fuzzing/fuzz-setup.sh -- afl-fuzz [opts] -- ./passt.fuzz --foreground
+#
+# Copyright Red Hat
+# Author: Anshu Kumari <anskuma@redhat.com>
+
+exec unshare --map-root-user --net -- sh -c '
+ set -e
+ ip link set lo up
+ ip route add local 0.0.0.0/0 dev lo
+ ip -6 route add local ::/0 dev lo
+
+ exec "$@"
+' _ "$@"
diff --git a/fuzzing/testcase_dir/empty.bin b/fuzzing/testcase_dir/empty.bin
new file mode 100644
index 0000000000000000000000000000000000000000..ce58bc9f84b9623e708de4eb8427a57d9f9a160f
GIT binary patch
literal 12
KcmZQzKmY&$3;+QD
literal 0
HcmV?d00001
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-28 5:17 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop Anshu Kumari
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
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).