// 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