From: Anton Protopopov <a.s.protopopov@gmail.com>
To: bpf <bpf@vger.kernel.org>,
lsm <linux-security-module@vger.kernel.org>,
netdev <netdev@vger.kernel.org>,
Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
KP Singh <kpsingh@kernel.org>,
Matt Bobrowski <matt@bobrowski.net>,
John Fastabend <john.fastabend@gmail.com>,
Christian Brauner <brauner@kernel.org>,
Paul Moore <paul@paul-moore.com>,
Linus Torvalds <torvalds@linux-foundation.org>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Anton Protopopov <a.s.protopopov@gmail.com>
Subject: [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook
Date: Mon, 31 Aug 2026 11:09:31 +0000 [thread overview]
Message-ID: <20260831110934.241898-7-a.s.protopopov@gmail.com> (raw)
In-Reply-To: <20260831110934.241898-1-a.s.protopopov@gmail.com>
Add a few tests for the new generic netlink BPF hook.
Policies only apply themselves to the "nlctrl" family.
The following tests are being added:
* doit: allow or deny CTRL_CMD_GETFAMILY
* dump: allow or deny CTRL_CMD_GETPOLICY, nlmsg_flags |= NLM_F_DUMP
* nlmsg_flags: allow or deny a command based on nlmsg_flags
* other_family: check that other families still pass
Test also uses "ethtool" generic netlink family, so enable it in config.
This family is also required for the subsequent ethtool-specific selftests.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
tools/testing/selftests/bpf/config | 1 +
.../selftests/bpf/prog_tests/genl_lsm.c | 242 ++++++++++++++++++
tools/testing/selftests/bpf/progs/genl_lsm.c | 58 +++++
3 files changed, 301 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/genl_lsm.c
create mode 100644 tools/testing/selftests/bpf/progs/genl_lsm.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index 2f79688dcf7c..d4f9d9e6cb9f 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -28,6 +28,7 @@ CONFIG_DMABUF_HEAPS=y
CONFIG_DMABUF_HEAPS_SYSTEM=y
CONFIG_DUMMY=y
CONFIG_DYNAMIC_FTRACE=y
+CONFIG_ETHTOOL_NETLINK=y
CONFIG_FPROBE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_FUNCTION_ERROR_INJECTION=y
diff --git a/tools/testing/selftests/bpf/prog_tests/genl_lsm.c b/tools/testing/selftests/bpf/prog_tests/genl_lsm.c
new file mode 100644
index 000000000000..2d880fe7461f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/genl_lsm.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/genetlink.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "netlink_helpers.h"
+#include "network_helpers.h"
+#include "test_progs.h"
+
+#include "genl_lsm.skel.h"
+
+#define NLCTRL_FAMILY_NAME "nlctrl"
+#define OTHER_FAMILY_NAME "ethtool"
+
+/* not probable to encounter this errno in real life */
+#define TEST_ERRNO EDOTDOT
+
+static int nlctrl_request(int fd, __u8 cmd, bool dump)
+{
+ static __u32 sequence = 1;
+ struct genl_req req = {};
+ __u32 seq = sequence++;
+ int err;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ req.nlh.nlmsg_type = GENL_ID_CTRL;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | (dump ? NLM_F_DUMP : 0);
+ req.nlh.nlmsg_seq = seq;
+ req.genl.cmd = cmd;
+ req.genl.version = 2;
+ if (addattrstrz(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, NLCTRL_FAMILY_NAME))
+ return -EMSGSIZE;
+
+ err = genl_send(fd, &req.nlh);
+ if (err)
+ return err;
+
+ return genl_recv(fd, seq, GENL_ID_CTRL, dump);
+}
+
+static void test_doit(struct genl_lsm *skel, int fd)
+{
+ int err;
+
+ skel->bss->target_cmd = CTRL_CMD_GETFAMILY;
+ skel->bss->target_flags = 0;
+ skel->bss->target_netns_inum = 0;
+ skel->bss->allow = true;
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ if (!ASSERT_OK(err, "CTRL_CMD_GETFAMILY (allow)"))
+ return;
+
+ skel->bss->allow = false;
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ ASSERT_EQ(err, -TEST_ERRNO, "CTRL_CMD_GETFAMILY (deny)");
+}
+
+static void test_dump(struct genl_lsm *skel, int fd)
+{
+ int err;
+
+ skel->bss->target_cmd = CTRL_CMD_GETPOLICY;
+ skel->bss->target_flags = 0;
+ skel->bss->target_netns_inum = 0;
+ skel->bss->allow = true;
+
+ err = nlctrl_request(fd, CTRL_CMD_GETPOLICY, true);
+ if (!ASSERT_OK(err, "allow_getpolicy_dump"))
+ return;
+
+ skel->bss->allow = false;
+ err = nlctrl_request(fd, CTRL_CMD_GETPOLICY, true);
+ ASSERT_EQ(err, -TEST_ERRNO, "deny_getpolicy_dump");
+}
+
+static void test_nlmsg_flags(struct genl_lsm *skel, int fd)
+{
+ int err;
+
+ skel->bss->target_cmd = CTRL_CMD_GETFAMILY;
+ skel->bss->target_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ skel->bss->target_netns_inum = 0;
+ skel->bss->allow = false;
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, true);
+ ASSERT_EQ(err, -TEST_ERRNO, "deny_dump_flags");
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ ASSERT_OK(err, "doit_flags_not_matched");
+
+ /* now, the other way around */
+ skel->bss->target_flags = NLM_F_REQUEST;
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, true);
+ ASSERT_OK(err, "doit_flags_not_matched");
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ ASSERT_EQ(err, -TEST_ERRNO, "deny_dump_flags");
+}
+
+static int other_family_request(int fd, __u16 family_id)
+{
+ static __u32 sequence = 1000;
+ struct genl_req req = {};
+ __u32 seq = sequence++;
+ int err;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ req.nlh.nlmsg_type = family_id;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ req.nlh.nlmsg_seq = seq;
+ req.genl.cmd = ETHTOOL_MSG_LINKSTATE_GET;
+ req.genl.version = ETHTOOL_GENL_VERSION;
+
+ err = genl_send(fd, &req.nlh);
+ if (err)
+ return err;
+
+ return genl_recv(fd, seq, family_id, true);
+}
+
+static void test_other_family(struct genl_lsm *skel, int fd, __u16 other_id)
+{
+ int err;
+
+ skel->bss->target_cmd = 0;
+ skel->bss->target_flags = 0;
+ skel->bss->target_netns_inum = 0;
+ skel->bss->allow = false;
+
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ if (!ASSERT_EQ(err, -TEST_ERRNO, "nlctrl_denied"))
+ return;
+
+ err = other_family_request(fd, other_id);
+ ASSERT_OK(err, "other_family_request ok");
+}
+
+static __u32 netns_inum(void)
+{
+ struct stat st;
+
+ if (stat("/proc/self/ns/net", &st))
+ return 0;
+
+ return st.st_ino;
+}
+
+static void test_netns(struct genl_lsm *skel, int fd)
+{
+ struct netns_obj *netns = NULL;
+ struct nstoken *nstoken = NULL;
+ int ns_fd = -1;
+ int err;
+
+ SYS_NOFAIL("ip netns del genl_lsm_ns");
+ netns = netns_new("genl_lsm_ns", false);
+ if (!ASSERT_OK_PTR(netns, "netns_new"))
+ return;
+
+ nstoken = open_netns("genl_lsm_ns");
+ if (!ASSERT_OK_PTR(nstoken, "open_netns"))
+ goto out;
+
+ ns_fd = genl_open(0);
+ if (!ASSERT_OK_FD(ns_fd, "genl_open"))
+ goto out;
+
+ skel->bss->target_cmd = CTRL_CMD_GETFAMILY;
+ skel->bss->target_flags = 0;
+ skel->bss->target_netns_inum = netns_inum();
+ skel->bss->allow = false;
+
+ if (!ASSERT_NEQ(skel->bss->target_netns_inum, 0, "netns_inum"))
+ goto out;
+
+ err = nlctrl_request(ns_fd, CTRL_CMD_GETFAMILY, false);
+ ASSERT_EQ(err, -TEST_ERRNO, "denied_in_target_netns");
+
+ close_netns(nstoken);
+ nstoken = NULL;
+
+ /* same request, same policy, but now from the original namespace */
+ err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
+ ASSERT_OK(err, "allowed_outside_target_netns");
+
+out:
+ if (ns_fd >= 0)
+ close(ns_fd);
+ close_netns(nstoken);
+ netns_free(netns);
+}
+
+void test_genl_lsm(void)
+{
+ struct genl_lsm *skel;
+ int other_id, fd = -1;
+ int err;
+
+ skel = genl_lsm__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "genl_lsm__open_and_load"))
+ return;
+
+ fd = genl_open(0);
+ if (!ASSERT_OK_FD(fd, "genl_open"))
+ goto cleanup;
+
+ /* do this before attaching our hook, just in case */
+ other_id = genl_resolve_family(fd, OTHER_FAMILY_NAME);
+ if (other_id == -ENOENT) {
+ test__skip();
+ goto cleanup;
+ }
+ if (!ASSERT_GT(other_id, 0, "genl_resolve_family"))
+ goto cleanup;
+
+ skel->bss->monitored_pid = getpid();
+ err = genl_lsm__attach(skel);
+ if (!ASSERT_OK(err, "genl_lsm__attach"))
+ goto cleanup;
+
+ if (test__start_subtest("doit"))
+ test_doit(skel, fd);
+ if (test__start_subtest("dump"))
+ test_dump(skel, fd);
+ if (test__start_subtest("nlmsg_flags"))
+ test_nlmsg_flags(skel, fd);
+ if (test__start_subtest("other_family"))
+ test_other_family(skel, fd, other_id);
+ if (test__start_subtest("netns"))
+ test_netns(skel, fd);
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ genl_lsm__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/genl_lsm.c b/tools/testing/selftests/bpf/progs/genl_lsm.c
new file mode 100644
index 000000000000..b8364a3ce776
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/genl_lsm.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#include <errno.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#define GENL_NAMSIZ 16
+
+__u32 monitored_pid;
+__u32 target_cmd;
+__u32 target_flags;
+__u32 target_netns_inum;
+bool allow;
+
+static bool is_nlctrl(const struct genl_family *family)
+{
+ static const char nlctrl_name[] = "nlctrl";
+ char name[GENL_NAMSIZ];
+ long len;
+
+ len = BPF_CORE_READ_STR_INTO(&name, family, name);
+ return len == sizeof(nlctrl_name) &&
+ bpf_strncmp(name, sizeof(nlctrl_name), nlctrl_name) == 0;
+}
+
+/* Swiss-knife-like policy used in all tests */
+SEC("lsm/genl_family_rcv_msg")
+int BPF_PROG(test_genl_family_rcv_msg, const struct genl_family *family,
+ const struct net *net, __u32 cmd, __u16 nlmsg_flags, int ret)
+{
+ __u32 pid;
+
+ if (ret)
+ return ret;
+
+ pid = bpf_get_current_pid_tgid() >> 32;
+ if (pid != monitored_pid)
+ return 0;
+
+ if (!family || !net || !is_nlctrl(family))
+ return 0;
+
+ if (target_cmd && cmd != target_cmd)
+ return 0;
+
+ if (target_flags && nlmsg_flags != target_flags)
+ return 0;
+
+ if (target_netns_inum && net->ns.inum != target_netns_inum)
+ return 0;
+
+ return allow ? 0 : -EDOTDOT; /* unlikely to see this errno outside this test */
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.0
next prev parent reply other threads:[~2026-08-31 10:59 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks Anton Protopopov
2026-08-31 11:50 ` bot+bpf-ci
2026-08-31 12:48 ` Anton Protopopov
2026-08-31 22:42 ` Paul Moore
2026-09-01 13:36 ` Anton Protopopov
2026-09-01 22:15 ` Paul Moore
2026-09-02 15:31 ` Anton Protopopov
2026-09-02 19:43 ` Paul Moore
2026-08-31 11:09 ` [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 13:22 ` Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 3/7] net, bpf: Add bpf hooks for ethtool control path Anton Protopopov
2026-09-01 10:59 ` sashiko-bot
2026-08-31 11:09 ` [PATCH bpf-next 4/7] selftests/bpf: Extract some helpers from tests to the netlink library Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 12:55 ` Anton Protopopov
2026-09-01 10:59 ` sashiko-bot
2026-08-31 11:09 ` Anton Protopopov [this message]
2026-08-31 12:07 ` [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook bot+bpf-ci
2026-08-31 13:01 ` Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 13:12 ` Anton Protopopov
2026-08-31 22:34 ` [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Jakub Kicinski
2026-09-01 12:29 ` Anton Protopopov
2026-09-02 0:49 ` Jakub Kicinski
2026-09-02 15:11 ` Anton Protopopov
2026-09-02 18:07 ` Alexei Starovoitov
2026-09-02 19:31 ` Anton Protopopov
2026-09-03 12:16 ` Justin Suess
2026-09-03 13:23 ` Anton Protopopov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260831110934.241898-7-a.s.protopopov@gmail.com \
--to=a.s.protopopov@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=john.fastabend@gmail.com \
--cc=kpsingh@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=matt@bobrowski.net \
--cc=memxor@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=paul@paul-moore.com \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.