[PATCH RFC bpf-next 3/4] selftests/bpf: Add audit helpers for BPF tests

Frederick Lawler fred at cloudflare.com
Wed Mar 11 21:31:19 UTC 2026


Add audit helper utilities for reading and parsing audit messages
in BPF selftests.

Assisted-by: Claude:claude-4.5-opus
Signed-off-by: Frederick Lawler <fred at cloudflare.com>
---
 tools/testing/selftests/bpf/Makefile        |   3 +-
 tools/testing/selftests/bpf/audit_helpers.c | 281 ++++++++++++++++++++++++++++
 tools/testing/selftests/bpf/audit_helpers.h |  55 ++++++
 3 files changed, 338 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 869b582b1d1ff496fb07736597708487be3438ed..76a428539add5e03fe3811b41c55005c22f5cead 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -754,7 +754,8 @@ TRUNNER_EXTRA_SOURCES := test_progs.c		\
 			 flow_dissector_load.h	\
 			 ip_check_defrag_frags.h	\
 			 bpftool_helpers.c	\
-			 usdt_1.c usdt_2.c
+			 usdt_1.c usdt_2.c	\
+			 audit_helpers.c
 TRUNNER_LIB_SOURCES := find_bit.c
 TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read				\
 		       $(OUTPUT)/liburandom_read.so			\
diff --git a/tools/testing/selftests/bpf/audit_helpers.c b/tools/testing/selftests/bpf/audit_helpers.c
new file mode 100644
index 0000000000000000000000000000000000000000..a105136a581f92a1af73b9456b1e85dc88176678
--- /dev/null
+++ b/tools/testing/selftests/bpf/audit_helpers.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF audit helpers
+ *
+ * Borrowed code from tools/selftests/landlock/audit.h
+ *
+ * Copyright (C) 2024-2025 Microsoft Corporation
+ * Copyright (c) 2026 Cloudflare
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include "audit_helpers.h"
+
+static __u32 seq;
+
+int audit_init(void)
+{
+	int bufsize = 1024 * 1024; /* 1MB receive buffer */
+	struct audit_message msg;
+	int fd, err;
+
+	fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+	if (fd < 0)
+		return -errno;
+
+	/*
+	 * Increase receive buffer to reduce kernel-side queueing.
+	 * When the socket buffer fills up, audit records get queued in
+	 * the kernel's hold/retry queues and delivered on subsequent runs.
+	 */
+	setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
+
+	seq = 0;
+	err = audit_send(fd, AUDIT_SET, AUDIT_STATUS_ENABLED, 1);
+	if (err)
+		goto out_close;
+
+	do {
+		err = audit_recv(fd, &msg, 0);
+		if (err < 0)
+			goto out_close;
+	} while (msg.nlh.nlmsg_type != NLMSG_ERROR);
+
+	if (msg.err.error)
+		goto out_close;
+
+	err = audit_send(fd, AUDIT_SET, AUDIT_STATUS_PID, getpid());
+	if (err)
+		goto out_close;
+
+	do {
+		err = audit_recv(fd, &msg, 0);
+		if (err < 0)
+			goto out_close;
+	} while (msg.nlh.nlmsg_type != NLMSG_ERROR);
+
+	if (msg.err.error)
+		goto out_close;
+
+	return fd;
+
+out_close:
+	close(fd);
+	return err;
+}
+
+void audit_cleanup(int fd)
+{
+	if (fd > 0)
+		close(fd);
+}
+
+int audit_send(int fd, __u16 type, __u32 key, __u32 val)
+{
+	struct audit_message msg = {
+		.nlh = {
+			.nlmsg_len = NLMSG_SPACE(sizeof(msg.status)),
+			.nlmsg_type = type,
+			.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+			.nlmsg_seq = ++seq,
+		},
+		.status = {
+			.mask = key,
+			.enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
+			.pid = key == AUDIT_STATUS_PID ? val : 0,
+		},
+	};
+	struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
+	int ret;
+
+	do {
+		ret = sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
+			     (struct sockaddr *)&addr, sizeof(addr));
+	} while (ret < 0 && errno == EINTR);
+
+	return ret == msg.nlh.nlmsg_len ? 0 : -errno;
+}
+
+/*
+ * Receive an audit message from the netlink socket.
+ * Returns:
+ *   > 0: message type on success
+ *   0: ACK received (NLMSG_ERROR with error=0)
+ *   < 0: negative errno on error
+ */
+int audit_recv(int fd, struct audit_message *msg, int flags)
+{
+	struct sockaddr_nl addr;
+	socklen_t addrlen = sizeof(addr);
+	int ret;
+
+	do {
+		ret = recvfrom(fd, msg, sizeof(*msg), flags,
+			       (struct sockaddr *)&addr, &addrlen);
+	} while (ret < 0 && errno == EINTR);
+
+	if (ret < 0)
+		return -errno;
+
+	/* Must be from kernel (pid 0) */
+	if (addrlen != sizeof(addr) || addr.nl_pid != 0)
+		return -EINVAL;
+
+	/*
+	 * NLMSG_ERROR with error=0 is an ACK. The kernel sends this in
+	 * response to messages with NLM_F_ACK flag set.
+	 */
+	if (msg->nlh.nlmsg_type == NLMSG_ERROR) {
+		if (msg->err.error == 0)
+			return 0; /* ACK */
+		return msg->err.error;
+	}
+
+	return msg->nlh.nlmsg_type;
+}
+
+__printf(2, 3) static inline void
+debug(struct audit_observer *obs, const char *fmt, ...)
+{
+	va_list args;
+
+	if (!obs || !obs->log)
+		return;
+
+	va_start(args, fmt);
+	vfprintf(obs->log, fmt, args);
+	va_end(args);
+}
+
+void audit_observer_init(struct audit_observer *obs, int audit_fd, FILE *log,
+			 int wait_timeout_ms)
+{
+	obs->audit_fd = audit_fd;
+	obs->wait_timeout = wait_timeout_ms;
+
+	if (log)
+		obs->log = log;
+
+	audit_observer_reset(obs);
+}
+
+void audit_observer_reset(struct audit_observer *obs)
+{
+	memset(obs->expects, 0, sizeof(obs->expects));
+	obs->num_expects = 0;
+}
+
+int audit_observer_expect(struct audit_observer *obs, int audit_type,
+			  const char *pattern, int count)
+{
+	struct audit_expectation *exp;
+
+	if (obs->num_expects >= AUDIT_EXPECT_MAX)
+		return -EINVAL;
+
+	exp = &obs->expects[obs->num_expects++];
+	exp->type = audit_type;
+	exp->pattern = pattern;
+	exp->expected_count = count;
+	exp->matched_count = 0;
+	return 0;
+}
+
+/*
+ * Check if a message matches any pending expectation.
+ * Returns 1 if all expectations are satisfied, 0 otherwise.
+ */
+static int audit_observer_match(struct audit_observer *obs,
+				struct audit_message *msg)
+{
+	int all_satisfied = 1;
+
+	for (int i = 0; i < obs->num_expects; i++) {
+		struct audit_expectation *exp = &obs->expects[i];
+
+		if (exp->matched_count >= exp->expected_count)
+			continue;
+
+		/* Check if this message matches */
+		if (exp->type && msg->nlh.nlmsg_type != exp->type)
+			goto check_satisfied;
+
+		if (strstr(msg->data, exp->pattern)) {
+			exp->matched_count++;
+			debug(obs, "%s: matched [%d/%d] %s\n", __func__,
+			      exp->matched_count, exp->expected_count,
+			      exp->pattern);
+		}
+
+check_satisfied:
+		if (exp->matched_count < exp->expected_count)
+			all_satisfied = 0;
+	}
+
+	return all_satisfied;
+}
+
+/*
+ * Wait for all expected audit messages to arrive.
+ * Returns 0 on success (all expectations met), -ETIMEDOUT on timeout.
+ */
+int audit_observer_wait(struct audit_observer *obs)
+{
+	struct pollfd pfd = { .fd = obs->audit_fd, .events = POLLIN };
+	struct audit_message msg;
+	int ret;
+
+	while (1) {
+		ret = poll(&pfd, 1, obs->wait_timeout);
+		if (ret < 0)
+			return -errno;
+		if (ret == 0)
+			return -ETIMEDOUT;
+
+		memset(&msg, 0, sizeof(msg));
+		ret = audit_recv(obs->audit_fd, &msg, MSG_DONTWAIT);
+
+		if (ret == -EAGAIN || ret == -EWOULDBLOCK)
+			continue;
+
+		if (ret <= 0)
+			continue;
+
+		debug(obs, "%s: recv type=%d %s\n", __func__,
+		      msg.nlh.nlmsg_type, msg.data);
+
+		if (audit_observer_match(obs, &msg))
+			return 0;
+	}
+}
+
+int audit_observer_check_satisfied(struct audit_observer *obs)
+{
+	for (int i = 0; i < obs->num_expects; i++) {
+		struct audit_expectation *exp = &obs->expects[i];
+
+		if (exp->matched_count < exp->expected_count) {
+			debug(obs, "%s: FAILED pattern '%s' got %d/%d\n",
+			      __func__, exp->pattern, exp->matched_count,
+			      exp->expected_count);
+			return 0;
+		}
+	}
+
+	return 1;
+}
diff --git a/tools/testing/selftests/bpf/audit_helpers.h b/tools/testing/selftests/bpf/audit_helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..40f3d20635bb25c305067756897593f34d54531e
--- /dev/null
+++ b/tools/testing/selftests/bpf/audit_helpers.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Cloudflare */
+#pragma once
+
+#include <linux/audit.h>
+#include <linux/netlink.h>
+#include <stdio.h>
+
+#define MAX_AUDIT_MESSAGE_LENGTH 8970
+
+struct audit_message {
+	struct nlmsghdr nlh;
+	union {
+		struct audit_status status;
+		struct nlmsgerr err;
+		char data[MAX_AUDIT_MESSAGE_LENGTH];
+	};
+};
+
+/*
+ * Observer-based audit message matching.
+ * Tests register expected patterns before triggering events, then
+ * wait for matches. Messages that don't match any pattern are skipped.
+ */
+#define AUDIT_EXPECT_MAX 32
+
+struct audit_expectation {
+	__u16 type;
+	const char *pattern;
+	int expected_count;
+	int matched_count;
+};
+
+struct audit_observer {
+	struct audit_expectation expects[AUDIT_EXPECT_MAX];
+	int num_expects;
+	FILE *log;
+	int wait_timeout;
+	int audit_fd;
+};
+
+int audit_init(void);
+void audit_cleanup(int fd);
+int audit_wait_ack(int fd);
+int audit_send(int fd, __u16 type, __u32 key, __u32 val);
+int audit_recv(int fd, struct audit_message *msg, int flags);
+int audit_wait_ack(int fd);
+
+void audit_observer_init(struct audit_observer *obs, int audit_fd, FILE *log,
+			 int wait_timeout);
+void audit_observer_reset(struct audit_observer *obs);
+int audit_observer_expect(struct audit_observer *obs, int audit_type,
+			  const char *pattern, int count);
+int audit_observer_wait(struct audit_observer *obs);
+int audit_observer_check_satisfied(struct audit_observer *obs);

-- 
2.43.0




More information about the Linux-security-module-archive mailing list