From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202608 header.b=Jfsr1hcz; dkim-atps=neutral Received: from mail.ozlabs.org (gandalf.ozlabs.org [150.107.74.76]) by passt.top (Postfix) with ESMTPS id F29F05A0265 for ; Fri, 14 Aug 2026 07:50:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202608; t=1786686632; bh=EbDzz0vVyAzSACay9K1X+0UMUW5g8YQaQ2S8J/d1Wdg=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=Jfsr1hczBYhlLazVefyABnQ/8Bd5WhYz08rlOZnSNTywa6wJfFQI6uwFmbOCbC0VA h0zAAjUtgC5nWqY2GWkLbK7o3rxHkxGeqpFwQYHj2cYkYwc7ihfqMyrbliIIMT+RGV rAp+iILNwEmcuj5FlPkWsWlBEt7wZrNmO8FFhGzfMoytYwADA7ViJrqLdExh3QFciB 10am9oC7pcAbKkKsLDXW1MVWkYi0VSnUyAsURXm5hKlqvV6zm4wY7G5UrBeakXbHSF MMdxx/4My9WjCqM87nsDgVmNFElOPlYqyJOwFeWPO4cT0ZUQgHYVv5cE7EYE2bzc3l VJeBBAREcjFwg== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4hLrtJ3m8Jz4wHf; Fri, 14 Aug 2026 15:50:32 +1000 (AEST) Date: Fri, 14 Aug 2026 15:50:27 +1000 From: David Gibson To: Anshu Kumari Subject: Re: [PATCH 5/5] fuzz: Add test server for bidirectional protocol fuzzing Message-ID: References: <20260812072630.3235261-1-anskuma@redhat.com> <20260812072630.3235261-6-anskuma@redhat.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="FM45jXr1PWqxd9Y/" Content-Disposition: inline In-Reply-To: <20260812072630.3235261-6-anskuma@redhat.com> Message-ID-Hash: L23C24DLHFJXFZFY4JJA6XDECA2NVTR6 X-Message-ID-Hash: L23C24DLHFJXFZFY4JJA6XDECA2NVTR6 X-MailFrom: dgibson@gandalf.ozlabs.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: sbrivio@redhat.com, passt-dev@passt.top, aerosound161@gmail.com, abdobngad@gmail.com, lvivier@redhat.com X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: --FM45jXr1PWqxd9Y/ Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Wed, Aug 12, 2026 at 12:56:28PM +0530, Anshu Kumari wrote: > Add fuzz-server that acts as passt's network peer > during fuzzing. It connects to passt's UNIX socket > and listens on 127.0.0.1:9999 for TCP connections. As discussed in my other reply, I'm not really convinced a test server makes sense at all, at least in this form. So, I won't have much to say on the details here. > 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. What's the reason for having a fixed response that's then mutated, rather than just generating the response directly from AFL's data? > TCP loopback path: accepts connections on port 9999, reads > data, XOR-mutates with AFL++ shared memory, and echoes it > back. Uses SO_LINGER(0) for immediate RST on close to > avoid TIME_WAIT port exhaustion. >=20 > Turn-based synchronization via mmap'd flag in /dev/shm > coordinates frame exchange between passt and the server. >=20 > Also adds: > - fuzzing/testcase_dir/empty.bin > - fuzzing/README.fuzzing.md >=20 > Signed-off-by: Anshu Kumari > --- > Makefile | 8 +- > fuzz-server.c | 490 +++++++++++++++++++++++++++++++++ > fuzzing/README.fuzzing.md | 95 +++++++ > fuzzing/testcase_dir/empty.bin | Bin 0 -> 12 bytes > 4 files changed, 591 insertions(+), 2 deletions(-) > create mode 100644 fuzz-server.c > create mode 100644 fuzzing/README.fuzzing.md > create mode 100644 fuzzing/testcase_dir/empty.bin >=20 > diff --git a/Makefile b/Makefile > index 8e4121e..4517bc7 100644 > --- a/Makefile > +++ b/Makefile > @@ -125,17 +125,21 @@ valgrind: all > =20 > FUZZ_CC ?=3D afl-clang-fast > =20 > -.PHONY: fuzz > +.PHONY: fuzz fuzz-server > =20 > fuzz: > $(MAKE) clean > $(MAKE) CC=3D"$(FUZZ_CC)" CPPFLAGS=3D"-DFUZZING -DNDEBUG" CFLAGS=3D"-g = -fsanitize=3Daddress" passt Oh, sorry, forgot to mention this on the earlier patch: compiling with -DNDEBUG for fuzzing seems undesirable. Generally if fuzzed input can cause an assert() that's a bug, which we'd like to catch. -NDEBUG will suppress the assert()s, so we won't see it. > +fuzz-server: > + $(CC) -D_GNU_SOURCE -O2 -o fuzz-server fuzz-server.c > + > .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 > =20 > install: $(BIN) $(MANPAGES) docs > mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) > diff --git a/fuzz-server.c b/fuzz-server.c > new file mode 100644 > index 0000000..c9b4101 > --- /dev/null > +++ b/fuzz-server.c > @@ -0,0 +1,490 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > + > +/* fuzz-server.c - Test server for bidirectional fuzz testing of passt > + * > + * Connects to passt's UNIX socket as a client. > + * Reads outbound frames, generates protocol responses, XORs them > + * with AFL++ shared memory data, and writes them back. > + * > + * Build: make fuzz-server > + * Run: ./fuzz-server (after passt.fuzz is listening) > + * > + * Copyright Red Hat > + * Author: Anshu Kumari > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define FUZZ_SOCK_PATH "/tmp/passt_fuzz_1.socket" > +#define FUZZ_TURN_PATH "/dev/shm/passt_fuzz_turn" > +#define FUZZ_XOR_OFF (12 + 64 * 1024) > +#define MAX_FRAME 1600 > +#define TCP_LISTEN_PORT 9999 > +#define TCP_MAX_EVENTS 16 > + > +/** > + * struct fuzz_turn - Turn-based synchronization flag (mmap'd shared mem= ory) > + * @turn: 0 =3D passt's turn, 1 =3D server's turn > + */ > +struct fuzz_turn { > + uint32_t turn; > +}; > + > +/** > + * swap_eth_ip() - Swap Ethernet MACs and IPv4 addresses > + * @pkt: Pointer to start of Ethernet frame > + */ > +static void swap_eth_ip(uint8_t *pkt) > +{ > + struct ethhdr *eth =3D (void *)pkt; > + struct iphdr *iph =3D (void *)(eth + 1); > + uint8_t tmp_mac[ETH_ALEN]; > + uint8_t tmp_ip[4]; > + > + memcpy(tmp_mac, eth->h_source, ETH_ALEN); > + memcpy(eth->h_source, eth->h_dest, ETH_ALEN); > + memcpy(eth->h_dest, tmp_mac, ETH_ALEN); > + > + memcpy(tmp_ip, &iph->saddr, 4); > + memcpy(&iph->saddr, &iph->daddr, 4); > + memcpy(&iph->daddr, tmp_ip, 4); > +} > + > +/** > + * respond_arp() - Generate ARP reply for an ARP request > + * @in: Incoming frame > + * @in_len: Incoming frame length > + * @out: Output buffer for response > + * > + * Swaps sender hardware and protocol addresses to form a > + * valid ARP reply. Only responds to ARP requests (op=3D1). > + * > + * Return: response length, or 0 if not an ARP request > + */ > +static int respond_arp(const uint8_t *in, uint32_t in_len, uint8_t *out) > +{ > + /* Min ARP Frame size: eth(14) + ARP Header(8) + Payload(20) */ > + if (in_len < 42) > + return 0; > + > + const struct ethhdr *eth =3D (const void *)in; > + const struct arphdr *arp =3D (const void *)(eth + 1); > + > + if (eth->h_proto !=3D htons(ETH_P_ARP) || > + arp->ar_op !=3D htons(ARPOP_REQUEST)) > + return 0; > + > + memcpy(out, in, in_len); > + > + struct ethhdr *out_eth =3D (void *)out; > + struct arphdr *out_arp =3D (void *)(out_eth + 1); > + > + /* set MAC addresses for output buffer */ > + memcpy(out_eth->h_dest, eth->h_source, ETH_ALEN); > + memcpy(out_eth->h_source, eth->h_dest, ETH_ALEN); > + > + /* set ARP operation to REPLY */ > + out_arp->ar_op =3D htons(ARPOP_REPLY); > + > + /* Swap ARP Sender and Target fields */ > + uint8_t *sender =3D (uint8_t *)(out_arp + 1); > + uint8_t *target =3D sender + 10; > + uint8_t tmp[10]; > + > + memcpy(tmp, sender, 10); > + memcpy(sender, target, 10); > + memcpy(target, tmp, 10); > + > + return in_len; > +} > + > +/** > + * respond_tcp_syn() - Generate SYN-ACK for a TCP SYN > + * @in: Incoming frame > + * @in_len: Incoming frame length > + * @out: Output buffer for response > + * > + * Swaps MAC/IP/port addresses and crafts a stateless SYN-ACK with > + * a fixed ISN of 1000. Only responds to pure SYN (no ACK set). > + * > + * Return: response length, or 0 if not a TCP SYN > + */ > +static int respond_tcp_syn(const uint8_t *in, uint32_t in_len, uint8_t *= out) > +{ > + const struct ethhdr *eth =3D (const void *)in; > + const struct iphdr *iph; > + const struct tcphdr *th; > + struct tcphdr *out_th; > + uint32_t ihl, hdr_len; > + > + /* Combined minimum length check for Eth + IP + TCP */ > + if (in_len < sizeof(*eth) + sizeof(*iph) + sizeof(*th)) > + return 0; > + > + if (eth->h_proto !=3D htons(ETH_P_IP)) > + return 0; > + > + iph =3D (const void *)(eth + 1); > + ihl =3D iph->ihl * 4; > + > + if (iph->protocol !=3D IPPROTO_TCP || > + in_len < sizeof(*eth) + ihl + sizeof(*th)) > + return 0; > + > + th =3D (const void *)((const uint8_t *)iph + ihl); > + > + /* Only respond to pure SYN packets (no ACK set) */ > + if (!th->syn || th->ack) > + return 0; > + > + hdr_len =3D sizeof(*eth) + ihl + sizeof(*th); > + memcpy(out, in, hdr_len); > + swap_eth_ip(out); > + > + /* Craft SYN-ACK response header */ > + out_th =3D (struct tcphdr *)(out + sizeof(*eth) + ihl); > + out_th->source =3D th->dest; > + out_th->dest =3D th->source; > + out_th->seq =3D htonl(1000); > + out_th->ack_seq =3D htonl(ntohl(th->seq) + 1); > + out_th->syn =3D 1; > + out_th->ack =3D 1; > + out_th->doff =3D 5; > + out_th->check =3D 0; > + > + return hdr_len; > +} > + > +/** > + * generate_response() - Try all protocol responders on a frame > + * @in: Incoming frame > + * @in_len: Incoming frame length > + * @out: Output buffer for response > + * > + * Tries ARP then TCP SYN responders in order. Returns the first > + * successful response. > + * > + * Return: response length, or 0 if no responder matched > + */ > +static int generate_response(const uint8_t *in, uint32_t in_len, uint8_t= *out) > +{ > + int len; > + > + if (in_len < sizeof(struct ethhdr)) > + return 0; > + > + if ((len =3D respond_arp(in, in_len, out)) > 0) > + return len; > + if ((len =3D respond_tcp_syn(in, in_len, out)) > 0) > + return len; > + > + return 0; > +} > + > +/** > + * map_afl_shm() - Map AFL++ shared memory for XOR mutation > + * > + * Reads __AFL_SHM_FUZZ_ID from the AFL++ environment > + * variable and attaches the corresponding shared memory > + * segment read-only. > + * > + * Return: pointer to mapped SHM, or NULL if not available > + */ > +static uint8_t *map_afl_shm(void) > +{ > + const char *id_str =3D getenv("__AFL_SHM_FUZZ_ID"); > + int shm_id; > + uint8_t *map; > + > + if (!id_str) > + return NULL; > + > + shm_id =3D atoi(id_str); > + map =3D shmat(shm_id, NULL, SHM_RDONLY); > + if (map =3D=3D (void *)-1) > + return NULL; > + > + return map; > +} > + > +/** > + * apply_xor_mask() - XOR buffer with AFL++ shared memory data > + * @buf: Buffer to mutate in place > + * @len: Buffer length > + * @afl_buf: AFL++ shared memory > + * > + * XORs each byte of @buf with the corresponding byte from the > + * AFL++ buffer starting at offset FUZZ_XOR_OFF (65548). This > + * lets AFL++ mutate server responses. > + */ > +static void apply_xor_mask(uint8_t *buf, int len, const uint8_t *afl_buf) > +{ > + int i; > + > + if (!afl_buf) > + return; > + > + for (i =3D 0; i < len; i++) > + buf[i] ^=3D afl_buf[FUZZ_XOR_OFF + i]; > +} > + > +/* ---- TCP Host Listener (Loopback) ---- */ > + > +static int tcp_listen_fd =3D -1; > +static int tcp_epfd =3D -1; > + > +/** > + * tcp_listener_init() - Set up TCP listener on loopback port 9999 > + * > + * Creates an epoll instance and a non-blocking TCP listener on > + * 127.0.0.1:9999 with SO_REUSEADDR. > + * > + * Return: 0 on success, -1 on failure > + */ > +static int tcp_listener_init(void) > +{ > + struct sockaddr_in sa =3D { > + .sin_family =3D AF_INET, > + .sin_addr.s_addr =3D htonl(INADDR_LOOPBACK), > + .sin_port =3D htons(TCP_LISTEN_PORT), > + }; > + struct epoll_event ev; > + int opt =3D 1; > + > + tcp_epfd =3D epoll_create1(EPOLL_CLOEXEC); > + tcp_listen_fd =3D socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); > + if (tcp_epfd < 0 || tcp_listen_fd < 0) > + return -1; > + > + setsockopt(tcp_listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); > + > + if (bind(tcp_listen_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0 || > + listen(tcp_listen_fd, 128) < 0) > + return -1; > + > + ev.events =3D EPOLLIN; > + ev.data.fd =3D tcp_listen_fd; > + epoll_ctl(tcp_epfd, EPOLL_CTL_ADD, tcp_listen_fd, &ev); > + > + return 0; > +} > + > +/** > + * tcp_handle_accept() - Drain all pending TCP connections > + * > + * Sets SO_LINGER with l_linger=3D0 on every accepted connection, > + * which does the immediate RST on connection close instead of > + * waiting for connection to close on it's own. > + */ > +static void tcp_handle_accept(void) > +{ > + struct linger lg =3D { .l_onoff =3D 1, .l_linger =3D 0 }; > + struct epoll_event ev; > + int fd; > + > + /* Drain ALL pending connections in the backlog */ > + while ((fd =3D accept4(tcp_listen_fd, NULL, NULL, > + SOCK_NONBLOCK)) >=3D 0) { > + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg)); > + > + ev.events =3D EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR; > + ev.data.fd =3D fd; > + epoll_ctl(tcp_epfd, EPOLL_CTL_ADD, fd, &ev); > + } > +} > + > +/** > + * tcp_handle_data() - Read TCP data, XOR-mutate, echo back > + * @fd: Connected TCP socket > + * @afl_buf: AFL++ shared memory for XOR mask > + */ > +static void tcp_handle_data(int fd, const uint8_t *afl_buf) > +{ > + uint8_t buf[MAX_FRAME]; > + ssize_t n, written, ret; > + > + n =3D read(fd, buf, sizeof(buf)); > + if (n <=3D 0) { > + epoll_ctl(tcp_epfd, EPOLL_CTL_DEL, fd, NULL); > + close(fd); > + return; > + } > + > + apply_xor_mask(buf, (uint32_t)n, afl_buf); > + > + /* Ensure all n bytes are written, handling short writes */ > + written =3D 0; > + while (written < n) { > + ret =3D write(fd, buf + written, (size_t)(n - written)); > + if (ret <=3D 0) { > + if (ret < 0 && (errno =3D=3D EAGAIN || > + errno =3D=3D EWOULDBLOCK)) > + continue; > + > + epoll_ctl(tcp_epfd, EPOLL_CTL_DEL, fd, NULL); > + close(fd); > + return; > + } > + written +=3D ret; > + } > +} > + > +/** > + * tcp_process_events() - Non-blocking poll for TCP events > + * @afl_buf: AFL++ shared memory > + */ > +static void tcp_process_events(const uint8_t *afl_buf) > +{ > + struct epoll_event events[TCP_MAX_EVENTS]; > + int nfds, i; > + > + nfds =3D epoll_wait(tcp_epfd, events, TCP_MAX_EVENTS, 0); > + > + for (i =3D 0; i < nfds; i++) { > + int fd =3D events[i].data.fd; > + > + if (fd =3D=3D tcp_listen_fd) { > + tcp_handle_accept(); > + } else if (events[i].events & (EPOLLIN | EPOLLRDHUP)) { > + tcp_handle_data(fd, afl_buf); > + } else if (events[i].events & (EPOLLHUP | EPOLLERR)) { > + epoll_ctl(tcp_epfd, EPOLL_CTL_DEL, fd, NULL); > + close(fd); > + } > + } > +} > + > +/** > + * create_turn_flag() - Create and mmap the turn synchronization flag > + * > + * Creates FUZZ_TURN_PATH in /dev/shm and maps it > + * as shared memory. Both passt and fuzz-server map this file to > + * coordinate turn-based frame exchange via atomic load/store. > + * > + * Return: pointer to mapped turn struct, or NULL on failure > + */ > +static struct fuzz_turn *create_turn_flag(void) > +{ > + struct fuzz_turn *t; > + int fd; > + > + fd =3D open(FUZZ_TURN_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); > + if (fd < 0) > + return NULL; > + > + if (ftruncate(fd, sizeof(struct fuzz_turn)) < 0) { > + close(fd); > + return NULL; > + } > + > + t =3D mmap(NULL, sizeof(*t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); > + close(fd); > + > + if (t =3D=3D MAP_FAILED) > + return NULL; > + > + t->turn =3D 0; > + return t; > +} > + > +/** > + * main() - Server entry point > + * > + * Outer loop reconnects to passt's UNIX socket on each AFL++ fork > + * server restart. Inner loop handles turn-based TAP frame exchange > + * and TCP events concurrently. > + * > + * Return: 0 on success, 1 on initialization failure > + */ > +int main(void) > +{ > + struct sockaddr_un addr =3D { .sun_family =3D AF_UNIX }; > + uint8_t frame[MAX_FRAME], response[MAX_FRAME]; > + struct fuzz_turn *turn; > + uint8_t *afl_buf; > + > + strncpy(addr.sun_path, FUZZ_SOCK_PATH, sizeof(addr.sun_path) - 1); > + > + turn =3D create_turn_flag(); > + if (!turn || tcp_listener_init() < 0) { > + (void)fprintf(stderr, > + "fuzz-server: tcp initialization failed\n"); > + return 1; > + } > + > + afl_buf =3D map_afl_shm(); > + (void)fprintf(stderr, > + "fuzz-server: ready, TCP listening on 127.0.0.1:%d\n", > + TCP_LISTEN_PORT); > + > + /* > + * Reconnect UNIX socket on passt restart =E2=80=94 AFL++'s fork server > + * restarts passt on each iteration. > + */ > + while (1) { > + ssize_t n; > + int sock; > + > + sock =3D socket(AF_UNIX, SOCK_SEQPACKET, 0); > + if (sock < 0) > + return 1; > + > + while (connect(sock, (struct sockaddr *)&addr, > + sizeof(addr)) < 0) > + usleep(10000); > + > + while (1) { > + tcp_process_events(afl_buf); > + > + if (__atomic_load_n(&turn->turn, > + __ATOMIC_ACQUIRE) =3D=3D 1) { > + n =3D recv(sock, frame, MAX_FRAME, > + MSG_DONTWAIT); > + if (n > 0) { > + int resp_len; > + > + resp_len =3D generate_response( > + frame, n, response); > + if (resp_len > 0) { > + apply_xor_mask(response, > + resp_len, > + afl_buf); > + send(sock, response, > + resp_len, MSG_NOSIGNAL); > + } > + } else if (n =3D=3D 0 || > + (n < 0 && errno !=3D EAGAIN && > + errno !=3D EWOULDBLOCK)) { > + __atomic_store_n(&turn->turn, 0, > + __ATOMIC_RELEASE); > + break; > + } > + > + __atomic_store_n(&turn->turn, 0, > + __ATOMIC_RELEASE); > + } > + } > + > + close(sock); > + } > + > + return 0; > +} > diff --git a/fuzzing/README.fuzzing.md b/fuzzing/README.fuzzing.md > new file mode 100644 > index 0000000..f23e265 > --- /dev/null > +++ b/fuzzing/README.fuzzing.md > @@ -0,0 +1,95 @@ > +## Fuzzing passt with AFL++ > + > +### Prerequisites > + > +- AFL++ (afl-clang-fast, afl-fuzz) > + > +### Build > + > +``` > +make fuzz > +cp passt passt.fuzz > +make fuzz-server > +``` > + > +This produces: > +- `passt.fuzz` -- instrumented with AFL++ and AddressSanitizer > +- `fuzz-server` -- test server for bidirectional protocol fuzzing > + > +To use a specific AFL++ installation: > + > +``` > +make FUZZ_CC=3D/path/to/afl-clang-fast fuzz > +``` > + > +### Run > + > +Start the test server first, then the fuzzer: > + > +``` > +# Terminal 1 -- test server: > +./fuzz-server > + > +# Terminal 2 -- fuzzer: > +afl-fuzz -i fuzzing/testcase_dir -o fuzzing/sync_dir \ > + -- ./passt.fuzz --foreground > +``` > + > +Multi-core (secondary instances share the corpus): > + > +``` > +# Terminal 1 -- main instance: > +afl-fuzz -M main -i fuzzing/testcase_dir -o fuzzing/sync_dir \ > + -- ./passt.fuzz --foreground > + > +# Terminal 2 -- secondary with different power schedule: > +afl-fuzz -S variant1 -p rare -i fuzzing/testcase_dir \ > + -o fuzzing/sync_dir -- ./passt.fuzz --foreground > +``` > + > +### Architecture > + > +The fuzzer uses AFL++ persistent mode with shared memory fuzzing. > +Each iteration: > + > +1. Resets deterministic state (clock, flow table, epoll) > +2. Reads an epoll event and packet data from AFL++ shared memory > +3. For TAP events: injects a packet with fixed L2/L3/L4 headers > + into the tap pipeline via tap_add_packet() + tap_handler(). > + The TCP destination port is fixed to 9999 (the test server's > + listening port). > +4. Exchanges a turn flag with fuzz-server for bidirectional flow. > +5. Calls passt_worker() to process the epoll event > +6. Polls for host-side TCP events (connect completion, server data) > + > +The test server connects to passt's UNIX socket (SOCK_SEQPACKET) > +and listens on 127.0.0.1:9999 for TCP connections. It responds to > +ARP requests and TCP SYNs on the UNIX socket, and echoes TCP data > +(XOR'd with AFL++ shared memory) on the loopback side. > + > +Deterministic wrappers in fuzz.c replace clock_gettime(), > +getrandom(), getsockopt(TCP_INFO), and recv/recvmsg/recvfrom/ > +recvmmsg to eliminate non-determinism from kernel state. The recv > +wrappers return AFL++ buffer data for non-TAP file descriptors, > +giving the fuzzer control over what passt "receives" from host > +sockets. > + > +### Seed inputs > + > +`testcase_dir/empty.bin` is a 12-byte zero file > +(sizeof(struct epoll_event)). AFL++ discovers packet formats > +through mutation from this minimal seed. > + > +### Reproducing crashes > + > +``` > +./passt.fuzz --foreground < \ > + fuzzing/sync_dir/default/crashes/id:000000,... > +``` > + > +Minimize a crash input: > + > +``` > +afl-tmin -i fuzzing/sync_dir/default/crashes/id:000000,... \ > + -o crash_minimized -- ./passt.fuzz --foreground > +``` > diff --git a/fuzzing/testcase_dir/empty.bin b/fuzzing/testcase_dir/empty.= bin > new file mode 100644 > index 0000000000000000000000000000000000000000..ce58bc9f84b9623e708de4eb8= 427a57d9f9a160f > GIT binary patch > literal 12 > KcmZQzKmY&$3;+QD >=20 > literal 0 > HcmV?d00001 >=20 > --=20 > 2.55.0 >=20 --=20 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 --FM45jXr1PWqxd9Y/ Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmp+rJQACgkQzQJF27ox 2Gf4KxAAmdx9FSR+d+iK85i9ao7UWF3lq2Gdicc6dcU4N2LyHcsdCCy79mNoTyKz gyECrtuL3cxC59ChKNoTY715wLrGTICqX2n6cSwNmIl3edEoQ/JyMQCAiIWuKJCc CD8JCSN40fDckyCdHhp4LBR6/8RaBPnZkTxH31sxabITl7AbobnO1UjYs217OVt3 eVz0aZknQhZ2/hhzdA54i/w9bZSzlRanNktvcdhWwMSgVUhALfXtTMYojgrLKGGN HYtKOIMVmuXNq/6yfZrylUhSNanCwFXdamljrUwhsRzrmT3oolZDuZV1/6AutYKc kFj8zv0fpWeHf/88M+QCDQFpt9h12Os9yC3nFIYp8c9KGJokektjr7R2fuppePQ+ s/WQ78/fVpinbYMB0M1mzz6bJsJ/pktUb1rL+GAls0jFmYDdEyEMRleMmfCAUTec Kgv+H8ZqOZ7VzJckl1VHFtVe1/vu4aX9ZwjuxuiP2i4koCZF2gsBZ478F5OPtR49 ZPRWl3NBZUJRw7BLB0OiJgvoldb5fwHo8kPmPA9FVguihQTSHATZE1MiQJxDigrE aMggzprHvHcd2i+UlP6nFs1E/nuspXJDbretR2+CMGlJtimq3AbWqUDvJkP6i9GF dbmqdKXrDeLlXW2VNhJEH5KkKyUw7qQt5MyGk70uIRNhI6drhwU= =lrSL -----END PGP SIGNATURE----- --FM45jXr1PWqxd9Y/--