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=Gh18Elge; 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 A1E5F5A061A for ; Mon, 28 Sep 2026 07:17:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1790572669; 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=R+R792wMuvBr9siuEdLsBSgWFdX8wHD9ThCGktwpSNs=; b=Gh18ElgeOcO4YMeEOBtc8/FDaVaQsu8ehfAJvJz7Gntwb45QbiC+8QCD1fVmqABTROvxMd 7O3JFC+zHK3Ye+oTkGV5PVTa9RpLqEqEh2rUDCo0vRYZdaCyXQ1/c5SM0xcLsA5Flt4Fuz 0PNZjiyvRlKb3ZgvU0XlLnTGkxBsk1o= Received: from mx-prod-mc-03.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-628-3kTzg8MNPdm9fvZivtjB1w-1; Mon, 28 Sep 2026 01:17:47 -0400 X-MC-Unique: 3kTzg8MNPdm9fvZivtjB1w-1 X-Mimecast-MFC-AGG-ID: 3kTzg8MNPdm9fvZivtjB1w_1790572667 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-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 037891944AAB; Mon, 28 Sep 2026 05:17:47 +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 D0ED819560AB; Mon, 28 Sep 2026 05:17:44 +0000 (UTC) From: Anshu Kumari To: sbrivio@redhat.com, passt-dev@passt.top Subject: [PATCH v2 5/7] fuzz: Add AFL++ persistent mode fuzz loop Date: Mon, 28 Sep 2026 10:47:25 +0530 Message-ID: <20260928051727.2251281-6-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: I5eOWrdPUd0bwnKEJHNJ_9RkHQd1j6tlYNknQhtgVFY_1790572667 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: O7MOBKVNQV7EHEDDIND2X2QCZ5FTS76Y X-Message-ID-Hash: O7MOBKVNQV7EHEDDIND2X2QCZ5FTS76Y 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: 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 --- 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 #include #include +#include #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