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=cEJ4a+JC; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by passt.top (Postfix) with ESMTPS id B634D5A0275 for ; Wed, 12 Aug 2026 09:26:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786519617; 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=FkCprmK+fj4UM0+C0xUmqSMQwsiIcS33DetyBhSHnqA=; b=cEJ4a+JCYnABtpCA17Pn1+WF0vXM1D7TYo+61kyfHmgYPs4YMR+SgNjMhiZtO4+ZxyEUc/ wpUxfv7FHAnzF8f6V19UlvAD5P6cWDUWokzyTymAAxLOk4dmhGmXJhsxuAh8j3Agt84N7e Cqgwbw0W89hUgQNWRjVLflJO/Rj31Gw= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-158-vvb_uwDYOtiPxeMoqGQbaw-1; Wed, 12 Aug 2026 03:26:44 -0400 X-MC-Unique: vvb_uwDYOtiPxeMoqGQbaw-1 X-Mimecast-MFC-AGG-ID: vvb_uwDYOtiPxeMoqGQbaw_1786519603 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (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-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9E565180034F; Wed, 12 Aug 2026 07:26:42 +0000 (UTC) Received: from anskuma-thinkpadp1gen7.bengluru.csb (unknown [10.74.80.105]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 52687195DF96; Wed, 12 Aug 2026 07:26:38 +0000 (UTC) From: Anshu Kumari To: david@gibson.dropbear.id.au, sbrivio@redhat.com, passt-dev@passt.top Subject: [PATCH 1/5] fuzz: Add deterministic wrappers for system calls Date: Wed, 12 Aug 2026 12:56:24 +0530 Message-ID: <20260812072630.3235261-2-anskuma@redhat.com> In-Reply-To: <20260812072630.3235261-1-anskuma@redhat.com> References: <20260812072630.3235261-1-anskuma@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: HL93B4HNyTwhv2hNm40sQ2h4sD013l_AvWhp_FHYjGQ_1786519603 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: SRHBIQXUSYU7S5NCN2CEVDYQFIWMT5TY X-Message-ID-Hash: SRHBIQXUSYU7S5NCN2CEVDYQFIWMT5TY 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: aerosound161@gmail.com, abdobngad@gmail.com, anskuma@redhat.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: Add fuzz.h and fuzz.c with wrapper implementations for clock_gettime(), getrandom(), getsockopt(), recv(), recvmsg(), recvfrom() and recvmmsg(). Under -DFUZZING, these macros replace the real system calls across the codebase: - fuzz_clock_gettime(): returns a deterministic clock that advances by 1 microsecond per call. - fuzz_getrandom(): fills buffers with a fixed 0x41 pattern. - fuzz_getsockopt(): returns static values for TCP_INFO, SO_ERROR, SO_RCVBUF, SO_SNDBUF. - fuzz_recv/recvmsg/recvfrom/recvmmsg(): for fd_tap, calls the real syscall; for all other fds, returns data from AFL++ shared memory buffer These wrappers eliminate kernel-level non-determinism during AFL++ fuzzing. Signed-off-by: Anshu Kumari --- Makefile | 14 +-- fuzz.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ fuzz.h | 62 +++++++++++++ 3 files changed, 344 insertions(+), 7 deletions(-) create mode 100644 fuzz.c create mode 100644 fuzz.h diff --git a/Makefile b/Makefile index b315242..fe1df58 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \ isolation.c lineread.c log.c mld.c ndp.c netlink.c migrate.c packet.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 + vhost_user.c virtio.c vu_common.c fuzz.c 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,12 +47,12 @@ 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.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 0000000..a1f6c01 --- /dev/null +++ b/fuzz.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* fuzz.c - AFL++ fuzzing support: deterministic wrappers for + * clock_gettime(), getrandom(), getsockopt(), recv(), + * recvmsg(), recvfrom() and recvmmsg() + * + * Copyright Red Hat + * Author: Anshu Kumari + */ + +#ifdef FUZZING + +#include +#include +#include +#include +#include "passt.h" +#include "fuzz.h" + +/* Undo macros so definitions here call the real syscalls */ +#undef clock_gettime +#undef getrandom +#undef getsockopt +#undef recv +#undef recvmsg +#undef recvfrom +#undef recvmmsg + +const unsigned char *fuzz_recv_data; +int fuzz_recv_data_len; + +#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_getrandom() - Return static deterministic bytes + * @buf: Output buffer + * @buflen: Bytes to fill + * @flags: Ignored + * + * Fills buffer with a repeating 0x41 pattern. Every call with the + * same length returns identical bytes, eliminating randomness + * + * Return: buflen (always succeeds) + */ +ssize_t fuzz_getrandom(void *buf, size_t buflen, unsigned int flags) +{ + (void)flags; + memset(buf, 0x41, buflen); + return buflen; +} + +/** + * 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, SO_ERROR, SO_RCVBUF, SO_SNDBUF returns determinstic + * values. For all other options: calls the real getsockopt. + * + * 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_ERROR) { + *(int *)optval = 0; + return 0; + } + if (optname == SO_RCVBUF || optname == SO_SNDBUF) { + *(int *)optval = 212992; /* default linux buff size */ + return 0; + } + } + + /* intercept SOL_TCP option: TCP_INFO */ + if (level == SOL_TCP && optname == TCP_INFO) { + size_t fill = *optlen; + + memset(optval, 0, fill); + + if (fill >= sizeof(struct tcp_info)) { + struct tcp_info *ti = optval; + + ti->tcpi_state = 1; /* TCP_ESTABLISHED */ + ti->tcpi_rto = 200000; /* 200ms */ + ti->tcpi_rtt = 1000; /* 1ms RTT */ + ti->tcpi_rttvar = 500; + ti->tcpi_snd_mss = 1460; + ti->tcpi_rcv_mss = 1460; + ti->tcpi_snd_cwnd = 10; + ti->tcpi_advmss = 1460; + ti->tcpi_pmtu = 1500; + + *optlen = sizeof(struct tcp_info); + } + + return 0; + } + + return getsockopt(fd, level, optname, optval, optlen); +} + +/** + * fuzz_recv() - recv wrapper + * @fd: File descriptor + * @buf: Output buffer + * @len: Max bytes + * @flags: recv flags (passed through for fd_tap) + * + * real recv() for fd_tap, AFL++ data for everything else + * + * Return: bytes read, or -1 + */ +ssize_t fuzz_recv(int fd, void *buf, size_t len, int flags) +{ + size_t n; + + /* fd is TAP socket for UNIX connection */ + if (fd == passt_ctx.fd_tap) + return recv(fd, buf, len, flags); + + if (!fuzz_recv_data || fuzz_recv_data_len <= 0) { + errno = EAGAIN; + return -1; + } + + n = (len < (size_t)fuzz_recv_data_len) ? + len : (size_t)fuzz_recv_data_len; + memcpy(buf, fuzz_recv_data, n); + fuzz_recv_data += n; + fuzz_recv_data_len -= n; + return n; +} + +/** + * fuzz_recvmsg() - recvmsg wrapper + * @fd: File descriptor + * @msg: Message header + * @flags: recvmsg flags + * + * For fd_tap: calls real recvmsg. For all other fds: fills each + * buffer sequentially from the AFL++ shared memory stream. + * + * Return: total bytes read across all iovecs, or -1 + */ +ssize_t fuzz_recvmsg(int fd, struct msghdr *msg, int flags) +{ + size_t total = 0; + size_t i; + + if (fd == passt_ctx.fd_tap) + return recvmsg(fd, msg, flags); + + if (!fuzz_recv_data || fuzz_recv_data_len <= 0) { + errno = EAGAIN; + return -1; + } + + for (i = 0; i < (size_t)msg->msg_iovlen && + fuzz_recv_data_len > 0; i++) { + size_t n = msg->msg_iov[i].iov_len; + + if ((int)n > fuzz_recv_data_len) + n = fuzz_recv_data_len; + memcpy(msg->msg_iov[i].iov_base, fuzz_recv_data, n); + fuzz_recv_data += n; + fuzz_recv_data_len -= n; + total += n; + } + + return total; +} + +/** + * fuzz_recvfrom() - recvfrom wrapper + * @fd: File descriptor + * @buf: Output buffer + * @len: Max bytes + * @flags: recv flags + * @src: Source address output + * @addrlen: Source address length + * + * For fd_tap: calls real recvfrom(). For all other fds: zeroes the + * source address (so callers see a deterministic sender) and + * delegates to fuzz_recv() for the payload. + * + * Return: bytes read, or -1 + */ +ssize_t fuzz_recvfrom(int fd, void *buf, size_t len, int flags, + struct sockaddr *src, socklen_t *addrlen) +{ + if (fd == passt_ctx.fd_tap) + return recvfrom(fd, buf, len, flags, src, addrlen); + + if (src && addrlen) + memset(src, 0, *addrlen); + + return fuzz_recv(fd, buf, len, flags); +} + +/** + * fuzz_recvmmsg() - recvmmsg wrapper + * @fd: File descriptor + * @mmh: Array of mmsghdr structures to fill + * @vlen: Number of mmsghdr entries available + * @flags: recv flags + * @timeout: Timeout + * + * For fd_tap: calls real recvmmsg(). For all other fds: fills only + * the first message from the AFL++ buffer via fuzz_recvmsg() and + * returns 1. + * + * Return: number of messages received (0 or 1), or -1 + */ +int fuzz_recvmmsg(int fd, struct mmsghdr *mmh, unsigned int vlen, + int flags, struct timespec *timeout) +{ + ssize_t n; + + if (fd == passt_ctx.fd_tap) + return recvmmsg(fd, mmh, vlen, flags, timeout); + + if (!vlen || !fuzz_recv_data || fuzz_recv_data_len <= 0) { + errno = EAGAIN; + return -1; + } + + n = fuzz_recvmsg(fd, &mmh[0].msg_hdr, flags); + if (n < 0) + return -1; + + mmh[0].msg_len = n; + return 1; +} + +#endif diff --git a/fuzz.h b/fuzz.h new file mode 100644 index 0000000..3f834a0 --- /dev/null +++ b/fuzz.h @@ -0,0 +1,62 @@ +//SPDX-License-Identifier: GPL-2.0-or-later + +/* fuzz.h - AFL++ fuzzing support for passt + * + * Copyright Red Hat + * Author: Anshu Kumari + */ + +#ifndef FUZZ_H +#define FUZZ_H + +#ifdef FUZZING + +#include +#include +#include +#include +#include + +int fuzz_clock_gettime(clockid_t clk, struct timespec *tp); +void fuzz_clock_reset(void); +ssize_t fuzz_getrandom(void *buf, size_t buflen, unsigned int flags); +int fuzz_getsockopt(int fd, int level, int optname, void *optval, + socklen_t *optlen); + +#define clock_gettime(clk, tp) fuzz_clock_gettime(clk, tp) +#define getrandom(buf, len, flags) fuzz_getrandom(buf, len, flags) +#define getsockopt(fd, level, name, val, len) \ + fuzz_getsockopt(fd, level, name, val, len) + +/* AFL++ buf layout: [0..11] epoll_event, [12..65547] recv payload */ +#define FUZZ_RECV_OFF 12 +#define FUZZ_RECV_MAX (64 * 1024) + +extern const unsigned char *fuzz_recv_data; +extern int fuzz_recv_data_len; + +ssize_t fuzz_recv(int fd, void *buf, size_t len, int flags); +ssize_t fuzz_recvmsg(int fd, struct msghdr *msg, int flags); +ssize_t fuzz_recvfrom(int fd, void *buf, size_t len, int flags, + struct sockaddr *src, socklen_t *addrlen); +int fuzz_recvmmsg(int fd, struct mmsghdr *mmh, unsigned int vlen, + int flags, struct timespec *timeout); + +/* Override existing wrappers for recvfrom() present inside util.h*/ +#undef recvfrom + +#define recv(fd, buf, len, flags) fuzz_recv(fd, buf, len, flags) +#define recvmsg(fd, msg, flags) fuzz_recvmsg(fd, msg, flags) +#define recvfrom(fd, buf, len, flags, src, sl) \ + fuzz_recvfrom(fd, buf, len, flags, src, sl) +#define recvmmsg(fd, mmh, vlen, flags, timeout) \ + fuzz_recvmmsg(fd, mmh, vlen, flags, timeout) + +#define FUZZ_TURN_PATH "/dev/shm/passt_fuzz_turn" + +struct fuzz_turn { + uint32_t turn; +}; + +#endif +#endif -- 2.55.0