From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=f4Dk8YEu; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 5E1E45A026E for ; Mon, 28 Sep 2026 07:17:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1790572675; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ABIM8zNKVqU3z7FuS9XIxLFL8ml0JgGfYQKtFL2ggIM=; b=f4Dk8YEuZyE7nTHU7XoohVs3jf6BWuZltxf1i3DYNy7FTUAyiYg82MA5cMiiFhR5EvKOuR JzocecQbK1dXeygNBZdQc9LNCBfuxx4Yn5Y1sWkUpX9gqtDol20AQBoNCgcnynkKY1NdDC M+P5ppcs2MXfyS0M7BEjMe07mMpBqAQ= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-147-Q2nnlkBlNhifhzjEUpL4bg-1; Mon, 28 Sep 2026 01:17:53 -0400 X-MC-Unique: Q2nnlkBlNhifhzjEUpL4bg-1 X-Mimecast-MFC-AGG-ID: Q2nnlkBlNhifhzjEUpL4bg_1790572672 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 842451953977; Mon, 28 Sep 2026 05:17:52 +0000 (UTC) Received: from anskuma-thinkpadp1gen7.bengluru.csb (unknown [10.74.80.83]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 5D7D31956045; Mon, 28 Sep 2026 05:17:50 +0000 (UTC) From: Anshu Kumari To: sbrivio@redhat.com, passt-dev@passt.top Subject: [PATCH v2 7/7] fuzz: Add build targets, namespace setup and documentation Date: Mon, 28 Sep 2026 10:47:27 +0530 Message-ID: <20260928051727.2251281-8-anskuma@redhat.com> In-Reply-To: <20260928051727.2251281-1-anskuma@redhat.com> References: <20260928051727.2251281-1-anskuma@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: hymiYcsbMpycPBoZk152M1QKlM-RsOXvNhk94RQyMag_1790572672 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: GQPNBYQNJFUUNVWD2D7PU4SUM5TASWDR X-Message-ID-Hash: GQPNBYQNJFUUNVWD2D7PU4SUM5TASWDR X-MailFrom: anskuma@redhat.com 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: lvivier@redhat.com, anskuma@redhat.com, abdobngad@gmail.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: 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 --- 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 + +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