* [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
@ 2026-08-31 11:09 Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks Anton Protopopov
` (8 more replies)
0 siblings, 9 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
The BPF LSM programs are allowed to attach to LSM hooks. This enables
operators to mitigate known bugs without a need to reboot or livepatch
machines. BPF has shown very useful to create such runtime policies.
However, many APIs and parts of kernel aren't covered by existing LSM
hooks and this would be beneficial to extend the coverage.
To simplify the process of adding new hooks this patch series enables
BPF to attach policy programs to hooks defined outside of the
official LSM list.
One of the reasons to add a new mechanism is that in order to add a
new LSM hook an implementation, at least one in-kernel LSM must be
added, such as SELinux or AppArmor, and BPF is specifically not
considered as a reference implementation [1]. This is, however, not
feasible for the use cases and capabilities covered by BPF LSMs,
which are not directly comparable to those of traditional LSMs.
This series introduces several initial hooks as a starting point for
adding further networking and non-networking hooks. The selection of
these hooks is guided by clusters of CVEs published by the kernel
numbering authority.
The series consists of the following patches:
Patch 1 adds a new mechanism to add hooks. (It is deliberately
made as simple as possible.)
Patch 2 adds a new BPF hook for generic netlink.
Patch 3 adds new BPF hooks for the ethtool APIs: generic netlink
family and ioctl.
The rest of patches add corresponding selftests.
(The series applies to bpf-next. Two net-next-related patches adding
actual hooks are accompanied by BPF selftests, so it looks like
bpf-next also might be the right destination.)
Links:
[1] Linux Security Module Subsystem Readme,
https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git/tree/README.md
Anton Protopopov (7):
bpf: Allow BPF LSM programs to attach to more hooks
net, bpf: Add a generic netlink hook on msg_rcv
net, bpf: Add bpf hooks for ethtool control path
selftests/bpf: Extract some helpers from tests to the netlink library
selftests/bpf: Add netdevsim helper library
selftests/bpf: Add tests for the generic netlink BPF hook
selftests/bpf: Add tests for BPF ethtool hooks
MAINTAINERS | 1 +
include/linux/bpf_lsm.h | 14 +
include/linux/bpf_lsm_hook_defs.h | 18 +
kernel/bpf/bpf_lsm.c | 2 +
net/ethtool/cabletest.c | 6 +
net/ethtool/features.c | 3 +
net/ethtool/ioctl.c | 5 +
net/ethtool/module.c | 3 +
net/ethtool/netlink.c | 17 +-
net/ethtool/netlink.h | 25 ++
net/ethtool/rss.c | 10 +
net/ethtool/tsinfo.c | 13 +
net/ethtool/tunnels.c | 14 +
net/netlink/genetlink.c | 6 +
tools/testing/selftests/bpf/Makefile | 1 +
tools/testing/selftests/bpf/config | 1 +
.../testing/selftests/bpf/netdevsim_helpers.c | 176 ++++++++++
.../testing/selftests/bpf/netdevsim_helpers.h | 9 +
tools/testing/selftests/bpf/netlink_helpers.c | 176 ++++++++++
tools/testing/selftests/bpf/netlink_helpers.h | 12 +
.../selftests/bpf/prog_tests/ethtool_lsm.c | 330 ++++++++++++++++++
.../selftests/bpf/prog_tests/genl_lsm.c | 242 +++++++++++++
.../selftests/bpf/prog_tests/test_bpf_smc.c | 160 ++-------
.../testing/selftests/bpf/progs/ethtool_lsm.c | 168 +++++++++
tools/testing/selftests/bpf/progs/genl_lsm.c | 58 +++
25 files changed, 1345 insertions(+), 125 deletions(-)
create mode 100644 include/linux/bpf_lsm_hook_defs.h
create mode 100644 tools/testing/selftests/bpf/netdevsim_helpers.c
create mode 100644 tools/testing/selftests/bpf/netdevsim_helpers.h
create mode 100644 tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/genl_lsm.c
create mode 100644 tools/testing/selftests/bpf/progs/ethtool_lsm.c
create mode 100644 tools/testing/selftests/bpf/progs/genl_lsm.c
--
2.43.0
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
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 ` Anton Protopopov
2026-08-31 11:50 ` bot+bpf-ci
2026-08-31 22:42 ` Paul Moore
2026-08-31 11:09 ` [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv Anton Protopopov
` (7 subsequent siblings)
8 siblings, 2 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
The BPF LSM programs are allowed to attach to LSM hooks, all of which
are defined in the <lsm_hook_defs.h> header file. From BPF's point
of view the set of attachment points is defined in the bpf_lsm_hooks
BTF set. By analogy with existing code, add a new header file
<bpf_lsm_hook_defs.h> which will also be included in the bpf_lsm_hooks
BTF set.
This change allows attaching BPF LSM programs to more functions.
The actual hooks are added in subsequent commits.
Each BPF hook calls a [__weak] noinline function each time a hook is
reached. This may be too expensive for hot paths if a BPF program is
not attached. A future commit will optimize this by adding a per-hook
static key and inc/dec it on attach/detach. This way disabled hooks
will be bypassed efficiently.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
MAINTAINERS | 1 +
include/linux/bpf_lsm.h | 12 ++++++++++++
include/linux/bpf_lsm_hook_defs.h | 6 ++++++
kernel/bpf/bpf_lsm.c | 2 ++
4 files changed, 21 insertions(+)
create mode 100644 include/linux/bpf_lsm_hook_defs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 460cb7268845..d01dd1f096fc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5035,6 +5035,7 @@ L: bpf@vger.kernel.org
S: Maintained
F: Documentation/bpf/prog_lsm.rst
F: include/linux/bpf_lsm.h
+F: include/linux/bpf_lsm_hook_defs.h
F: kernel/bpf/bpf_lsm.c
F: kernel/bpf/bpf_lsm_proto.c
F: kernel/trace/bpf_trace.c
diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index dda272d78f01..1e54c7cca27a 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -16,9 +16,19 @@
extern bool bpf_lsm_initialized __ro_after_init;
+/*
+ * Technically, checking bpf_lsm_initialized is not necessary.
+ * But if it is off, then this means that all security_* calls
+ * do not call BPF, and it doesn't look reasonable to enable
+ * only "non-LSM" bpf hooks...
+ */
+#define bpf_lsm_hook(NAME, ...) \
+ (bpf_lsm_initialized ? bpf_lsm_##NAME(__VA_ARGS__) : 0)
+
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
RET bpf_lsm_##NAME(__VA_ARGS__);
#include <linux/lsm_hook_defs.h>
+#include <linux/bpf_lsm_hook_defs.h>
#undef LSM_HOOK
struct bpf_storage_blob {
@@ -114,6 +124,8 @@ static inline bool bpf_lsm_hook_returns_errno(u32 btf_id)
{
return true;
}
+
+#define bpf_lsm_hook(NAME, ...) 0
#endif /* CONFIG_BPF_LSM */
#endif /* _LINUX_BPF_LSM_H */
diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
new file mode 100644
index 000000000000..29bc0b514d16
--- /dev/null
+++ b/include/linux/bpf_lsm_hook_defs.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * This is a set of BPF LSM hooks, which are _not_ fully implemented
+ * as LSM hooks. Thus, they only can be used by BPF LSM programs.
+ */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 82c5988417a0..add344ea2691 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -28,11 +28,13 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
}
#include <linux/lsm_hook_defs.h>
+#include <linux/bpf_lsm_hook_defs.h>
#undef LSM_HOOK
#define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
BTF_SET_START(bpf_lsm_hooks)
#include <linux/lsm_hook_defs.h>
+#include <linux/bpf_lsm_hook_defs.h>
#undef LSM_HOOK
BTF_SET_END(bpf_lsm_hooks)
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv
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:09 ` Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 11:09 ` [PATCH bpf-next 3/7] net, bpf: Add bpf hooks for ethtool control path Anton Protopopov
` (6 subsequent siblings)
8 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
Add a new bpf_lsm_genl_family_rcv_msg() BPF hook called from within
the genl_family_rcv_msg() function. The hook sees the genl family,
net, cmd, and nlmsg_flags. As the hook receives all the information
pre-parsed, it is not put into the sleepable list.
There is already an existing LSM hook sitting right at netlink_send,
however, it looks to be too coarse-grained to be used directly. The
netlink_send hook sees the struct sock and struct skb, and thus must
duplicate all the parsing inside BPF. The messages can actually contain
multiple netlink datagrams, dynamic info (see e.g., selinux_nlmsg_lookup()
in context of generic netlink), etc.
The purpose of this hook is to mitigate, in a coarse-grained way,
CVEs reachable via the corresponding control path: genl recvmsg.
While looking at the recent CVEs, robots found ~300 CVEs reachable
via the genl_rcv. We can roughly split them as follows:
* Around 100 are ethtool-related. For more detailed information,
see the following commit which adds even more finer-grained
ethtool hooks.
* Around 100 of the remaining are fully dominated by the new hook.
* Around 100 remaining aren't fully mitigatable, as other control
paths, besides the genl, lead to the buggy code.
To be more precise, the following CVEs are properly mitigated by the
new hook:
* CVE-2021-47518 CVE-2021-47594 CVE-2021-47612 CVE-2022-48687
CVE-2022-49060 CVE-2022-49085 CVE-2022-49300 CVE-2022-49862
CVE-2022-49959 CVE-2022-50656 CVE-2022-50834 CVE-2022-50854
CVE-2023-52698 CVE-2023-52702 CVE-2023-52977 CVE-2023-53113
CVE-2023-53298 CVE-2023-53686 CVE-2023-53843 CVE-2024-26608
CVE-2024-26663 CVE-2024-26754 CVE-2024-26811 CVE-2024-26950
CVE-2024-27025 CVE-2024-27410 CVE-2024-36941 CVE-2024-38562
CVE-2024-43912 CVE-2024-45010 CVE-2024-46711 CVE-2024-49937
CVE-2024-50085 CVE-2025-21706 CVE-2025-21787 CVE-2025-21862
CVE-2025-21875 CVE-2025-21947 CVE-2025-22024 CVE-2025-38184
CVE-2025-38443 CVE-2025-40084 CVE-2025-68263 CVE-2025-68366
CVE-2026-23297 CVE-2026-23436 CVE-2026-43252 CVE-2026-43394
CVE-2026-53128 CVE-2026-53227 CVE-2026-53233 CVE-2026-53238
CVE-2026-53257
The following are mitigated as well, but, theoretically, could
benefit from a finer-grained hook, running after a deeper
parsing/taking locks (this is however out of scope for now to
add finer-grained hooks for each involved subsystems, as, unlike
ethtool, they are more-or-less evenly distributed):
* CVE-2021-47250 CVE-2021-47257 CVE-2021-47554 CVE-2022-48915
CVE-2022-49374 CVE-2022-49505 CVE-2022-50042 CVE-2022-50678
CVE-2023-52845 CVE-2023-53141 CVE-2023-53543 CVE-2023-53652
CVE-2023-54031 CVE-2024-26725 CVE-2024-26951 CVE-2024-42073
CVE-2024-42154 CVE-2024-46676 CVE-2024-47687 CVE-2024-50028
CVE-2025-21973 CVE-2025-38020 CVE-2025-38628 CVE-2025-40254
CVE-2025-40321 CVE-2025-68785 CVE-2025-71091 CVE-2025-71297
CVE-2026-23321 CVE-2026-31707 CVE-2026-31744 CVE-2026-43012
CVE-2026-43467 CVE-2026-43481 CVE-2026-45840 CVE-2026-52978
CVE-2026-52979 CVE-2023-53570 CVE-2024-42114 CVE-2024-53189
CVE-2024-56663 CVE-2025-21909 CVE-2026-53182
Policies for selected CVEs are added in the corresponding
selftests patch.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
include/linux/bpf_lsm.h | 2 ++
include/linux/bpf_lsm_hook_defs.h | 7 +++++++
net/netlink/genetlink.c | 6 ++++++
3 files changed, 15 insertions(+)
diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index 1e54c7cca27a..452b9d1d7c49 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -12,6 +12,8 @@
#include <linux/bpf_verifier.h>
#include <linux/lsm_hooks.h>
+struct genl_family; /* for the bpf_lsm_genl_family_rcv_msg hook */
+
#ifdef CONFIG_BPF_LSM
extern bool bpf_lsm_initialized __ro_after_init;
diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
index 29bc0b514d16..3f57022744a3 100644
--- a/include/linux/bpf_lsm_hook_defs.h
+++ b/include/linux/bpf_lsm_hook_defs.h
@@ -4,3 +4,10 @@
* This is a set of BPF LSM hooks, which are _not_ fully implemented
* as LSM hooks. Thus, they only can be used by BPF LSM programs.
*/
+
+#ifdef CONFIG_NET
+
+LSM_HOOK(int, 0, genl_family_rcv_msg, const struct genl_family *family,
+ const struct net *net, u32 cmd, u16 nlmsg_flags)
+
+#endif /* CONFIG_NET */
diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index 0da39eaed255..12b4f49c7253 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -17,6 +17,7 @@
#include <linux/skbuff.h>
#include <linux/mutex.h>
#include <linux/bitmap.h>
+#include <linux/bpf_lsm.h>
#include <linux/rwsem.h>
#include <linux/idr.h>
#include <net/sock.h>
@@ -1161,6 +1162,7 @@ static int genl_family_rcv_msg(const struct genl_family *family,
struct genlmsghdr *hdr = nlmsg_data(nlh);
struct genl_split_ops op;
int hdrlen;
+ int err;
u8 flags;
/* this family doesn't exist in this netns */
@@ -1187,6 +1189,10 @@ static int genl_family_rcv_msg(const struct genl_family *family,
!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
return -EPERM;
+ err = bpf_lsm_hook(genl_family_rcv_msg, family, net, hdr->cmd, nlh->nlmsg_flags);
+ if (err)
+ return err;
+
if (flags & GENL_CMD_CAP_DUMP)
return genl_family_rcv_msg_dumpit(family, skb, nlh, extack,
&op, hdrlen, net);
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 3/7] net, bpf: Add bpf hooks for ethtool control path
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:09 ` [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv Anton Protopopov
@ 2026-08-31 11:09 ` Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 4/7] selftests/bpf: Extract some helpers from tests to the netlink library Anton Protopopov
` (5 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
This commit adds BPF hooks to enable runtime mitigation of bugs
caused, directly or indirectly, by running a specific ethtool
configuration/dump path. The bigger part of the patch adds
netlink-related hooks, and a small, trivial, part adds a hook
in the ioctl path.
The hooks added in this patch are "coarse-grained", which means here
that they only receive high-level info. Each hook is invoked with the
locking (RTNL and/or device), which is required to mitigate some of
CVEs, for example those, which need to consider the device state,
etc. However, for some cases (like flash update), the actual
operation runs without locking, so only "stateless" policies are
applicable.
If a bug lies in the parsing done before locking, then the previously
added generic netlink hook can be used to prevent this bug.
Overall, this patch allows to mitigate [prevent] biger chunk of the
CVEs caused by the ethtool control path. Exceptions are CVEs which
can be triggered via ethtool_ops via sysfs or directly by kernel,
see the comments below.
The hooks are added to bpf_lsm_hook_defs.h and are as follows:
ethtool_ioctl(const struct net_device *dev, u32 cmd, u32 sub_cmd)
ethtool_netlink_doit(const struct net_device *dev, u32 cmd, u32 phy_index)
ethtool_netlink_dump(const struct net_device *dev, u32 cmd, u32 phy_index)
Hooks aren't added to sleepable list: they receive all the infa,
plus run under a lock.
Another small note is that for a dump request a hook can be executed
multiple times: on sendto and on consequent recvfrom[s]. One
consequence on properly policing dump requests is that if a netlink
socket is shared, then dump hooks for a particular dump request can
run from within different cgroups and checked accordingly.
To validate that this actually makes sense to add these hooks, the
set of known CVEs was analysed and it was found that the following
CVEs could have been mitigated by these hooks:
* ioctl-only: CVE-2021-46916 CVE-2021-46947 CVE-2021-47148
CVE-2021-47556 CVE-2022-48688 CVE-2022-49725 CVE-2022-49581
CVE-2022-49368 CVE-2023-52780 CVE-2023-53509 CVE-2023-53661
CVE-2023-53798 CVE-2023-53142 CVE-2023-53495 CVE-2023-54240
CVE-2024-40928 CVE-2024-42162 CVE-2025-21650 CVE-2025-37911
CVE-2025-38422 CVE-2025-39875 CVE-2025-68795 CVE-2026-23353
CVE-2026-31494 CVE-2026-31505 CVE-2026-31727 CVE-2026-23024
* only doit: CVE-2025-21921 CVE-2024-43836 CVE-2025-37791
* only dump: CVE-2022-50651
* more than one hook required: CVE-2021-47159 CVE-2021-47241
CVE-2021-47517 CVE-2021-47399 CVE-2021-47139 CVE-2022-49096
CVE-2022-49227 CVE-2022-49869 CVE-2022-50003 CVE-2022-50710
CVE-2022-49192 CVE-2023-53659 CVE-2023-54037 CVE-2023-53556
CVE-2024-39502 CVE-2024-46834 CVE-2024-46770 CVE-2024-56728
CVE-2024-46799 CVE-2025-21799 CVE-2025-71137 CVE-2025-38402
CVE-2025-39922 CVE-2025-21701 CVE-2025-38735 CVE-2025-39874
CVE-2025-40255 CVE-2026-22985 CVE-2026-23054 CVE-2026-23389
CVE-2026-45891 CVE-2026-53007 CVE-2026-22993 CVE-2026-23165
CVE-2026-53323
As was mentioned before, some ethtool_ops-related CVEs can't be
mitigated by the new hooks, as they are executed via other channels.
Around 30 of such were found. Examples are:
* CVE-2022-50054, CVE-2024-46679, CVE-2024-50274
The CVEs listed above were first extracted and classifed by AI
robots, and then re-classified until different agents were in
99% agreement. Selected policies were hand-picked and added as
selftests in the corresponding patch.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
include/linux/bpf_lsm_hook_defs.h | 5 +++++
net/ethtool/cabletest.c | 6 ++++++
net/ethtool/features.c | 3 +++
net/ethtool/ioctl.c | 5 +++++
net/ethtool/module.c | 3 +++
net/ethtool/netlink.c | 17 ++++++++++++++---
net/ethtool/netlink.h | 25 +++++++++++++++++++++++++
| 10 ++++++++++
net/ethtool/tsinfo.c | 13 +++++++++++++
net/ethtool/tunnels.c | 14 ++++++++++++++
10 files changed, 98 insertions(+), 3 deletions(-)
diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
index 3f57022744a3..a7f84756946e 100644
--- a/include/linux/bpf_lsm_hook_defs.h
+++ b/include/linux/bpf_lsm_hook_defs.h
@@ -10,4 +10,9 @@
LSM_HOOK(int, 0, genl_family_rcv_msg, const struct genl_family *family,
const struct net *net, u32 cmd, u16 nlmsg_flags)
+LSM_HOOK(int, 0, ethtool_ioctl, const struct net_device *dev, u32 cmd, u32 sub_cmd)
+LSM_HOOK(int, 0, ethtool_netlink_doit, const struct net_device *dev__nullable, u32 cmd,
+ u32 phy_index)
+LSM_HOOK(int, 0, ethtool_netlink_dump, const struct net_device *dev, u32 cmd, u32 phy_index)
+
#endif /* CONFIG_NET */
diff --git a/net/ethtool/cabletest.c b/net/ethtool/cabletest.c
index 9c22d4c767c6..dc846146bd2e 100644
--- a/net/ethtool/cabletest.c
+++ b/net/ethtool/cabletest.c
@@ -74,6 +74,9 @@ int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;
netdev_lock_ops_compat(dev);
+ ret = ethnl_bpf_lsm_doit(&req_info, info->genlhdr->cmd);
+ if (ret)
+ goto out_unlock;
phydev = ethnl_req_get_phydev(&req_info, tb,
ETHTOOL_A_CABLE_TEST_HEADER,
info->extack);
@@ -341,6 +344,9 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info)
goto out_dev_put;
netdev_lock_ops_compat(dev);
+ ret = ethnl_bpf_lsm_doit(&req_info, info->genlhdr->cmd);
+ if (ret)
+ goto out_unlock;
phydev = ethnl_req_get_phydev(&req_info, tb,
ETHTOOL_A_CABLE_TEST_TDR_HEADER,
info->extack);
diff --git a/net/ethtool/features.c b/net/ethtool/features.c
index d9455b30aec9..2aff2aa171f2 100644
--- a/net/ethtool/features.c
+++ b/net/ethtool/features.c
@@ -237,6 +237,9 @@ int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
rtnl_lock();
netdev_lock_ops(dev);
+ ret = ethnl_bpf_lsm_doit(&req_info, info->genlhdr->cmd);
+ if (ret)
+ goto out_unlock;
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_unlock;
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 4b0bc503f930..16b02a7abb2c 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -8,6 +8,7 @@
*/
#include <linux/compat.h>
+#include <linux/bpf_lsm.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/types.h>
@@ -3330,6 +3331,10 @@ dev_ethtool_locked(struct net *net, struct net_device *dev,
netdev_assert_locked_ops_compat(dev);
+ rc = bpf_lsm_hook(ethtool_ioctl, dev, ethcmd, sub_cmd);
+ if (rc)
+ return rc;
+
if (dev->dev.parent)
pm_runtime_get_sync(dev->dev.parent);
diff --git a/net/ethtool/module.c b/net/ethtool/module.c
index 9cf670e089f2..b316b465b953 100644
--- a/net/ethtool/module.c
+++ b/net/ethtool/module.c
@@ -430,6 +430,9 @@ int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info)
dev = req_info.dev;
netdev_lock_ops_compat(dev);
+ ret = ethnl_bpf_lsm_doit(&req_info, info->genlhdr->cmd);
+ if (ret)
+ goto out_unlock;
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_unlock;
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 1af395b54330..80c8bde50f53 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -543,7 +543,9 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
rtnl_lock();
netdev_lock_ops(req_info->dev);
}
- ret = ops->prepare_data(req_info, reply_data, info);
+ ret = ethnl_bpf_lsm_doit(req_info, cmd);
+ if (!ret)
+ ret = ops->prepare_data(req_info, reply_data, info);
if (req_info->dev) {
netdev_unlock_ops(req_info->dev);
if (need_rtnl)
@@ -595,6 +597,7 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
const struct ethnl_dump_ctx *ctx,
const struct genl_info *info)
{
+ int cmd = ctx->ops->request_cmd;
bool need_rtnl;
void *ehdr;
int ret;
@@ -607,11 +610,15 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
need_rtnl = !netdev_need_ops_lock(dev) ||
- ethtool_nl_msg_needs_rtnl(dev, ctx->ops->request_cmd);
+ ethtool_nl_msg_needs_rtnl(dev, cmd);
if (need_rtnl)
rtnl_lock();
netdev_lock_ops(dev);
- ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info);
+
+ ret = ethnl_bpf_lsm_dump(dev, ctx->req_info->phy_index, cmd);
+ if (!ret)
+ ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info);
+
netdev_unlock_ops(dev);
if (need_rtnl)
rtnl_unlock();
@@ -934,6 +941,9 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
if (need_rtnl)
rtnl_lock();
netdev_lock_ops(dev);
+ ret = ethnl_bpf_lsm_doit(req_info, cmd);
+ if (ret)
+ goto out_unlock;
dev->cfg_pending = kmemdup(dev->cfg, sizeof(*dev->cfg),
GFP_KERNEL_ACCOUNT);
if (!dev->cfg_pending) {
@@ -961,6 +971,7 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
kfree(dev->cfg_pending);
out_tie_cfg:
dev->cfg_pending = dev->cfg;
+out_unlock:
netdev_unlock_ops(dev);
if (need_rtnl)
rtnl_unlock();
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 3e969a070f9f..30c56942149c 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -5,6 +5,7 @@
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
+#include <linux/bpf_lsm.h>
#include <net/genetlink.h>
#include <net/sock.h>
@@ -354,6 +355,30 @@ struct ethnl_sock_priv {
int ethnl_sock_priv_set(struct sk_buff *skb, struct net *net, u32 portid,
enum ethnl_sock_type type);
+static inline int ethnl_bpf_lsm_doit(const struct ethnl_req_info *req_info, u32 cmd)
+{
+ return bpf_lsm_hook(ethtool_netlink_doit, req_info->dev, cmd, req_info->phy_index);
+}
+
+static inline int ethnl_bpf_lsm_dump(const struct net_device *dev,
+ u32 phy_index,
+ u32 cmd)
+{
+ int ret;
+
+ ret = bpf_lsm_hook(ethtool_netlink_dump, dev, cmd, phy_index);
+
+ /*
+ * For a BPF policy this doesn't make any sense to return -EOPNOTSUPP.
+ * But if it does, it will not be treated as an error by ethtool code,
+ * so patch it here.
+ */
+ if (ret == -EOPNOTSUPP)
+ ret = -EPERM;
+
+ return ret;
+}
+
/**
* struct ethnl_request_ops - unified handling of GET and SET requests
* @request_cmd: command id for request (GET)
--git a/net/ethtool/rss.c b/net/ethtool/rss.c
index d4a1a4724b67..d10bf701c336 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -475,6 +475,10 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
if (ctx->match_ifindex && ctx->match_ifindex != ctx->ifindex)
break;
+ ret = ethnl_bpf_lsm_dump(dev, 0, ETHTOOL_MSG_RSS_GET);
+ if (ret)
+ break;
+
ret = rss_dump_one_dev(skb, cb, dev);
if (ret)
break;
@@ -1035,6 +1039,9 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
goto exit_free_dev;
netdev_lock_ops_compat(dev);
+ ret = ethnl_bpf_lsm_doit(&req.base, info->genlhdr->cmd);
+ if (ret)
+ goto exit_dev_unlock;
ret = ethnl_ops_begin(dev);
if (ret < 0)
@@ -1176,6 +1183,9 @@ int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info)
}
netdev_lock_ops_compat(dev);
+ ret = ethnl_bpf_lsm_doit(&req, info->genlhdr->cmd);
+ if (ret)
+ goto exit_dev_unlock;
ret = ethnl_ops_begin(dev);
if (ret < 0)
diff --git a/net/ethtool/tsinfo.c b/net/ethtool/tsinfo.c
index c9b680a9cc3f..d6131780f2df 100644
--- a/net/ethtool/tsinfo.c
+++ b/net/ethtool/tsinfo.c
@@ -352,6 +352,10 @@ static int ethnl_tsinfo_dump_one_phydev(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct ethnl_tsinfo_dump_ctx *ctx = (void *)cb->ctx;
+ struct ethnl_req_info hook_req_info = {
+ .dev = dev,
+ .phy_index = phydev->phyindex,
+ };
struct tsinfo_reply_data *reply_data;
struct tsinfo_req_info *req_info;
void *ehdr = NULL;
@@ -362,6 +366,11 @@ static int ethnl_tsinfo_dump_one_phydev(struct sk_buff *skb,
reply_data = ctx->reply_data;
req_info = ctx->req_info;
+ ret = ethnl_bpf_lsm_dump(dev, hook_req_info.phy_index,
+ ETHTOOL_MSG_TSINFO_GET);
+ if (ret)
+ return ret;
+
ehdr = ethnl_tsinfo_prepare_dump(skb, dev, reply_data, cb);
if (IS_ERR(ehdr))
return PTR_ERR(ehdr);
@@ -401,6 +410,10 @@ static int ethnl_tsinfo_dump_one_netdev(struct sk_buff *skb,
reply_data = ctx->reply_data;
req_info = ctx->req_info;
+ ret = ethnl_bpf_lsm_dump(dev, 0, ETHTOOL_MSG_TSINFO_GET);
+ if (ret)
+ return ret;
+
for (; ctx->pos_phcqualifier < HWTSTAMP_PROVIDER_QUALIFIER_CNT;
ctx->pos_phcqualifier++) {
if (!net_support_hwtstamp_qualifier(dev,
diff --git a/net/ethtool/tunnels.c b/net/ethtool/tunnels.c
index b4ce47dd2aa6..3d2798e716e0 100644
--- a/net/ethtool/tunnels.c
+++ b/net/ethtool/tunnels.c
@@ -179,6 +179,13 @@ int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info)
return ret;
rtnl_lock();
+
+ netdev_lock_ops(req_info.dev);
+ ret = ethnl_bpf_lsm_doit(&req_info, info->genlhdr->cmd);
+ netdev_unlock_ops(req_info.dev);
+ if (ret)
+ goto err_unlock_rtnl;
+
ret = ethnl_tunnel_info_reply_size(&req_info, info->extack);
if (ret < 0)
goto err_unlock_rtnl;
@@ -248,6 +255,13 @@ int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
rtnl_lock();
for_each_netdev_dump(net, dev, ctx->ifindex) {
+ netdev_lock_ops(dev);
+ ret = ethnl_bpf_lsm_dump(dev, ctx->req_info.phy_index,
+ ETHTOOL_MSG_TUNNEL_INFO_GET);
+ netdev_unlock_ops(dev);
+ if (ret)
+ break;
+
ehdr = ethnl_dump_put(skb, cb,
ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY);
if (!ehdr) {
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 4/7] selftests/bpf: Extract some helpers from tests to the netlink library
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (2 preceding siblings ...)
2026-08-31 11:09 ` [PATCH bpf-next 3/7] net, bpf: Add bpf hooks for ethtool control path Anton Protopopov
@ 2026-08-31 11:09 ` Anton Protopopov
2026-08-31 11:09 ` [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library Anton Protopopov
` (4 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
Extract and generalize, a bit, generic netlink code used in the
bpf_smc test to the netlink_helpers.{c,h} library such that it
can be reused by other tests.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
tools/testing/selftests/bpf/netlink_helpers.c | 176 ++++++++++++++++++
tools/testing/selftests/bpf/netlink_helpers.h | 12 ++
.../selftests/bpf/prog_tests/test_bpf_smc.c | 160 ++++------------
3 files changed, 226 insertions(+), 122 deletions(-)
diff --git a/tools/testing/selftests/bpf/netlink_helpers.c b/tools/testing/selftests/bpf/netlink_helpers.c
index caf36eb1d032..ff0ec10cc3a1 100644
--- a/tools/testing/selftests/bpf/netlink_helpers.c
+++ b/tools/testing/selftests/bpf/netlink_helpers.c
@@ -8,11 +8,187 @@
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
+#include <sys/time.h>
#include "netlink_helpers.h"
static int rcvbuf = 1024 * 1024;
+int genl_open(__u32 pid)
+{
+ struct sockaddr_nl local = {
+ .nl_family = AF_NETLINK,
+ .nl_pid = pid,
+ };
+ struct timeval timeout = {
+ .tv_sec = 1
+ };
+ int ret;
+ int fd;
+
+ fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC);
+ if (fd < 0)
+ return -1;
+
+ if (bind(fd, (void *)&local, sizeof(local)))
+ goto err_close;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)))
+ goto err_close;
+
+ return fd;
+
+err_close:
+ ret = -errno;
+ close(fd);
+ return ret;
+}
+
+int genl_send(int fd, const struct nlmsghdr *nlh)
+{
+ struct sockaddr_nl kernel = {
+ .nl_family = AF_NETLINK
+ };
+ ssize_t sent;
+
+ sent = sendto(fd, nlh, nlh->nlmsg_len, 0, (void *)&kernel, sizeof(kernel));
+ if (sent < 0)
+ return -errno;
+ if (sent != nlh->nlmsg_len)
+ return -EIO;
+
+ return 0;
+}
+
+int genl_recv(int fd, __u32 seq, __u16 family_id, bool dump)
+{
+ char buf[64 * 1024];
+
+ for (;;) {
+ struct nlmsghdr *nlh;
+ int remaining;
+ ssize_t len;
+
+ len = recv(fd, buf, sizeof(buf), 0);
+ if (len < 0)
+ return -errno;
+ if (!len)
+ return -ENODATA;
+
+ remaining = len;
+ for (nlh = (struct nlmsghdr *)buf;
+ NLMSG_OK(nlh, remaining);
+ nlh = NLMSG_NEXT(nlh, remaining)) {
+ if (nlh->nlmsg_seq != seq)
+ continue;
+
+ if (nlh->nlmsg_type == NLMSG_ERROR) {
+ const struct nlmsgerr *nlerr = NLMSG_DATA(nlh);
+
+ if (NLMSG_PAYLOAD(nlh, 0) < sizeof(*nlerr))
+ return -EBADMSG;
+ if (nlerr->error || !dump)
+ return nlerr->error;
+ continue;
+ }
+
+ if (nlh->nlmsg_type == NLMSG_DONE) {
+ int done_err = 0;
+
+ if (NLMSG_PAYLOAD(nlh, 0) >= sizeof(done_err))
+ memcpy(&done_err, NLMSG_DATA(nlh),
+ sizeof(done_err));
+ if (done_err)
+ return done_err;
+ return 0;
+ }
+
+ if (nlh->nlmsg_type == family_id) {
+ if (!dump)
+ return 0;
+ }
+ }
+ if (remaining)
+ return -EBADMSG;
+ }
+}
+
+int genl_resolve_family(int fd, const char *name)
+{
+ struct genl_req req = {};
+ char buf[4096];
+ struct nlmsghdr *nlh;
+ int remaining;
+ ssize_t len;
+ int err;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ req.nlh.nlmsg_type = GENL_ID_CTRL;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST;
+ req.nlh.nlmsg_seq = 1;
+ req.genl.cmd = CTRL_CMD_GETFAMILY;
+ req.genl.version = 2;
+ if (addattrstrz(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, name))
+ return -EMSGSIZE;
+
+ err = genl_send(fd, &req.nlh);
+ if (err)
+ return err;
+
+ len = recv(fd, buf, sizeof(buf), 0);
+ if (len < 0)
+ return -errno;
+ if (!len)
+ return -ENODATA;
+
+ remaining = len;
+ for (nlh = (struct nlmsghdr *)buf;
+ NLMSG_OK(nlh, remaining);
+ nlh = NLMSG_NEXT(nlh, remaining)) {
+ struct nlattr *attr;
+ int attr_len;
+
+ if (nlh->nlmsg_seq != req.nlh.nlmsg_seq)
+ continue;
+
+ if (nlh->nlmsg_type == NLMSG_ERROR) {
+ const struct nlmsgerr *nlerr = NLMSG_DATA(nlh);
+
+ if (NLMSG_PAYLOAD(nlh, 0) < sizeof(*nlerr))
+ return -EBADMSG;
+ return nlerr->error ?: -ENOENT;
+ }
+ if (nlh->nlmsg_type != GENL_ID_CTRL)
+ continue;
+ if (NLMSG_PAYLOAD(nlh, 0) < GENL_HDRLEN)
+ return -EBADMSG;
+
+ attr = (struct nlattr *)((char *)NLMSG_DATA(nlh) +
+ GENL_HDRLEN);
+ attr_len = NLMSG_PAYLOAD(nlh, GENL_HDRLEN);
+ while (attr_len >= (int)sizeof(*attr) &&
+ attr->nla_len >= sizeof(*attr) &&
+ attr->nla_len <= attr_len) {
+ __u16 family_id;
+ int step;
+
+ if ((attr->nla_type & NLA_TYPE_MASK) ==
+ CTRL_ATTR_FAMILY_ID &&
+ attr->nla_len >= NLA_HDRLEN + sizeof(family_id)) {
+ memcpy(&family_id, (char *)attr + NLA_HDRLEN,
+ sizeof(family_id));
+ return family_id;
+ }
+
+ step = NLA_ALIGN(attr->nla_len);
+ attr_len -= step;
+ attr = (struct nlattr *)((char *)attr + step);
+ }
+ }
+
+ return remaining ? -EBADMSG : -ENOENT;
+}
+
void rtnl_close(struct rtnl_handle *rth)
{
if (rth->fd >= 0) {
diff --git a/tools/testing/selftests/bpf/netlink_helpers.h b/tools/testing/selftests/bpf/netlink_helpers.h
index 68116818a47e..964e8ad94a1f 100644
--- a/tools/testing/selftests/bpf/netlink_helpers.h
+++ b/tools/testing/selftests/bpf/netlink_helpers.h
@@ -3,9 +3,21 @@
#define NETLINK_HELPERS_H
#include <string.h>
+#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
+struct genl_req {
+ struct nlmsghdr nlh;
+ struct genlmsghdr genl;
+ char attrs[256];
+};
+
+int genl_open(__u32 pid);
+int genl_send(int fd, const struct nlmsghdr *nlh);
+int genl_recv(int fd, __u32 seq, __u16 family_id, bool dump);
+int genl_resolve_family(int fd, const char *name);
+
struct rtnl_handle {
int fd;
struct sockaddr_nl local;
diff --git a/tools/testing/selftests/bpf/prog_tests/test_bpf_smc.c b/tools/testing/selftests/bpf/prog_tests/test_bpf_smc.c
index 40d38280c091..45e479a649c0 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_bpf_smc.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_bpf_smc.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <linux/genetlink.h>
+#include "netlink_helpers.h"
#include "network_helpers.h"
#include "bpf_smc.skel.h"
@@ -44,105 +45,28 @@ enum {
SMC_NLA_EID_TABLE_ENTRY, /* string */
};
-struct msgtemplate {
- struct nlmsghdr n;
- struct genlmsghdr g;
- char buf[1024];
-};
-
-#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
-#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
-#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
-#define NLA_PAYLOAD(len) ((len) - NLA_HDRLEN)
-
#define SMC_GENL_FAMILY_NAME "SMC_GEN_NETLINK"
+#define SMC_GENL_FAMILY_VERSION 1
#define SMC_BPFTEST_UEID "SMC-BPFTEST-UEID"
+#define SMC_BPFTEST_UEID_LEN 32
-static uint16_t smc_nl_family_id = -1;
-
-static int send_cmd(int fd, __u16 nlmsg_type, __u32 nlmsg_pid,
- __u16 nlmsg_flags, __u8 genl_cmd, __u16 nla_type,
- void *nla_data, int nla_len)
-{
- struct nlattr *na;
- struct sockaddr_nl nladdr;
- int r, buflen;
- char *buf;
-
- struct msgtemplate msg = {0};
-
- msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
- msg.n.nlmsg_type = nlmsg_type;
- msg.n.nlmsg_flags = nlmsg_flags;
- msg.n.nlmsg_seq = 0;
- msg.n.nlmsg_pid = nlmsg_pid;
- msg.g.cmd = genl_cmd;
- msg.g.version = 1;
- na = (struct nlattr *)GENLMSG_DATA(&msg);
- na->nla_type = nla_type;
- na->nla_len = nla_len + 1 + NLA_HDRLEN;
- memcpy(NLA_DATA(na), nla_data, nla_len);
- msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len);
-
- buf = (char *)&msg;
- buflen = msg.n.nlmsg_len;
- memset(&nladdr, 0, sizeof(nladdr));
- nladdr.nl_family = AF_NETLINK;
-
- while ((r = sendto(fd, buf, buflen, 0, (struct sockaddr *)&nladdr,
- sizeof(nladdr))) < buflen) {
- if (r > 0) {
- buf += r;
- buflen -= r;
- } else if (errno != EAGAIN) {
- return -1;
- }
- }
- return 0;
-}
+static __u16 smc_nl_family_id;
static bool get_smc_nl_family_id(void)
{
- struct sockaddr_nl nl_src;
- struct msgtemplate msg;
- struct nlattr *nl;
int fd, ret;
pid_t pid;
- fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ pid = getpid();
+ fd = genl_open(pid);
if (!ASSERT_OK_FD(fd, "nl_family socket"))
return false;
- pid = getpid();
-
- memset(&nl_src, 0, sizeof(nl_src));
- nl_src.nl_family = AF_NETLINK;
- nl_src.nl_pid = pid;
-
- ret = bind(fd, (struct sockaddr *)&nl_src, sizeof(nl_src));
- if (!ASSERT_OK(ret, "nl_family bind"))
- goto fail;
-
- ret = send_cmd(fd, GENL_ID_CTRL, pid,
- NLM_F_REQUEST, CTRL_CMD_GETFAMILY,
- CTRL_ATTR_FAMILY_NAME, (void *)SMC_GENL_FAMILY_NAME,
- strlen(SMC_GENL_FAMILY_NAME));
- if (!ASSERT_OK(ret, "nl_family query"))
- goto fail;
-
- ret = recv(fd, &msg, sizeof(msg), 0);
- if (msg.n.nlmsg_type == NLMSG_ERROR)
- goto fail;
- if (!ASSERT_FALSE(ret < 0 || !NLMSG_OK(&msg.n, ret),
- "nl_family response"))
- goto fail;
-
- nl = (struct nlattr *)GENLMSG_DATA(&msg);
- nl = (struct nlattr *)((char *)nl + NLA_ALIGN(nl->nla_len));
- if (!ASSERT_EQ(nl->nla_type, CTRL_ATTR_FAMILY_ID, "nl_family nla type"))
+ ret = genl_resolve_family(fd, SMC_GENL_FAMILY_NAME);
+ if (!ASSERT_GT(ret, 0, "nl_family query"))
goto fail;
- smc_nl_family_id = *(uint16_t *)NLA_DATA(nl);
+ smc_nl_family_id = ret;
close(fd);
return true;
fail:
@@ -152,56 +76,48 @@ static bool get_smc_nl_family_id(void)
static bool smc_ueid(int op)
{
- struct sockaddr_nl nl_src;
- struct msgtemplate msg;
- struct nlmsgerr *err;
- char test_ueid[32];
+ char test_ueid[SMC_BPFTEST_UEID_LEN + 1] = {};
+ struct genl_req req = {};
int fd, ret;
pid_t pid;
/* UEID required */
- memset(test_ueid, '\x20', sizeof(test_ueid));
- memcpy(test_ueid, SMC_BPFTEST_UEID, strlen(SMC_BPFTEST_UEID));
- fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ memset(test_ueid, ' ', SMC_BPFTEST_UEID_LEN);
+ memcpy(test_ueid, SMC_BPFTEST_UEID, sizeof(SMC_BPFTEST_UEID) - 1);
+ pid = getpid();
+ fd = genl_open(pid);
if (!ASSERT_OK_FD(fd, "ueid socket"))
return false;
- pid = getpid();
- memset(&nl_src, 0, sizeof(nl_src));
- nl_src.nl_family = AF_NETLINK;
- nl_src.nl_pid = pid;
-
- ret = bind(fd, (struct sockaddr *)&nl_src, sizeof(nl_src));
- if (!ASSERT_OK(ret, "ueid bind"))
+ req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ req.nlh.nlmsg_type = smc_nl_family_id;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.nlh.nlmsg_pid = pid;
+ req.genl.cmd = op;
+ req.genl.version = SMC_GENL_FAMILY_VERSION;
+ ret = addattrstrz(&req.nlh, sizeof(req), SMC_NLA_EID_TABLE_ENTRY,
+ test_ueid);
+ if (!ASSERT_OK(ret, "ueid attribute"))
goto fail;
- ret = send_cmd(fd, smc_nl_family_id, pid,
- NLM_F_REQUEST | NLM_F_ACK, op, SMC_NLA_EID_TABLE_ENTRY,
- (void *)test_ueid, sizeof(test_ueid));
+ ret = genl_send(fd, &req.nlh);
if (!ASSERT_OK(ret, "ueid cmd"))
goto fail;
- ret = recv(fd, &msg, sizeof(msg), 0);
- if (!ASSERT_FALSE(ret < 0 ||
- !NLMSG_OK(&msg.n, ret), "ueid response"))
- goto fail;
-
- if (msg.n.nlmsg_type == NLMSG_ERROR) {
- err = NLMSG_DATA(&msg);
- switch (op) {
- case SMC_NETLINK_REMOVE_UEID:
- if (!ASSERT_FALSE((err->error && err->error != -ENOENT),
- "ueid remove"))
- goto fail;
- break;
- case SMC_NETLINK_ADD_UEID:
- if (!ASSERT_OK(err->error, "ueid add"))
- goto fail;
- break;
- default:
- break;
- }
+ ret = genl_recv(fd, req.nlh.nlmsg_seq, smc_nl_family_id, false);
+ switch (op) {
+ case SMC_NETLINK_REMOVE_UEID:
+ if (!ASSERT_FALSE(ret && ret != -ENOENT, "ueid remove"))
+ goto fail;
+ break;
+ case SMC_NETLINK_ADD_UEID:
+ if (!ASSERT_OK(ret, "ueid add"))
+ goto fail;
+ break;
+ default:
+ break;
}
+
close(fd);
return true;
fail:
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (3 preceding siblings ...)
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 ` Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 11:09 ` [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook Anton Protopopov
` (3 subsequent siblings)
8 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
Add a minimal netdevsim helper library for the BPF selftests. This
covers basics: netdevsim creation with one port and one queue, which
we need for the upcoming BPF selftests.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
tools/testing/selftests/bpf/Makefile | 1 +
.../testing/selftests/bpf/netdevsim_helpers.c | 176 ++++++++++++++++++
.../testing/selftests/bpf/netdevsim_helpers.h | 9 +
3 files changed, 186 insertions(+)
create mode 100644 tools/testing/selftests/bpf/netdevsim_helpers.c
create mode 100644 tools/testing/selftests/bpf/netdevsim_helpers.h
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index b481b867f372..c21f89dbe70c 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -862,6 +862,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
unpriv_helpers.c \
sysctl_helpers.c \
netlink_helpers.c \
+ netdevsim_helpers.c \
jit_disasm_helpers.c \
io_helpers.c \
test_loader.c \
diff --git a/tools/testing/selftests/bpf/netdevsim_helpers.c b/tools/testing/selftests/bpf/netdevsim_helpers.c
new file mode 100644
index 000000000000..fbed7442b2e8
--- /dev/null
+++ b/tools/testing/selftests/bpf/netdevsim_helpers.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/rtnetlink.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "netdevsim_helpers.h"
+
+static int echo(const char *path, const char *fmt, ...)
+{
+ char buf[64];
+ va_list ap;
+ int fd, len, err = 0;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ return -errno;
+ if (write(fd, buf, len) != len)
+ err = -errno;
+ close(fd);
+
+ return err;
+}
+
+void netdevsim_destroy(unsigned int id)
+{
+ echo("/sys/bus/netdevsim/del_device", "%u", id);
+}
+
+static int create_new_device(void)
+{
+ unsigned int id;
+ int err;
+
+ /* if 10K is not enough, then something is clearly not right */
+ for (id = 0; id < 10000; id++) {
+ err = echo("/sys/bus/netdevsim/new_device", "%u", id);
+ if (!err)
+ return id;
+ if (err != -ENOSPC)
+ return err;
+ }
+
+ return -ENOSPC;
+}
+
+static int open_link_socket(void)
+{
+ struct sockaddr_nl addr = {
+ .nl_family = AF_NETLINK,
+ .nl_groups = RTMGRP_LINK,
+ };
+ int fd;
+
+ fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0)
+ return -errno;
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) {
+ int err = -errno;
+
+ close(fd);
+ return err;
+ }
+
+ return fd;
+}
+
+static int remaining_timeout_ms(const struct timespec *deadline)
+{
+ struct timespec now;
+ long long remaining;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ return -errno;
+
+ remaining = (deadline->tv_sec - now.tv_sec) * 1000 +
+ (deadline->tv_nsec - now.tv_nsec) / 1000000;
+
+ return remaining > 0 ? remaining : 0;
+}
+
+static int recv_device_ifindex(int fd, unsigned int id, unsigned int *ifindex)
+{
+ char parent_name[32], buf[16 * 1024];
+ struct pollfd pfd = {
+ .fd = fd,
+ .events = POLLIN,
+ };
+ struct timespec deadline;
+ struct nlmsghdr *nlh;
+ int len, ret, timeout;
+
+ snprintf(parent_name, sizeof(parent_name), "netdevsim%u", id);
+ if (clock_gettime(CLOCK_MONOTONIC, &deadline))
+ return -errno;
+ deadline.tv_sec += 5;
+
+ for (timeout = remaining_timeout_ms(&deadline); timeout > 0;
+ timeout = remaining_timeout_ms(&deadline)) {
+ ret = poll(&pfd, 1, timeout);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (!ret)
+ return -ETIMEDOUT;
+ if (!(pfd.revents & POLLIN))
+ return -EIO;
+
+ len = recv(fd, buf, sizeof(buf), 0);
+ if (len < 0)
+ return -errno;
+
+ for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
+ nlh = NLMSG_NEXT(nlh, len)) {
+ struct ifinfomsg *ifm;
+ struct rtattr *attr;
+ int attr_len;
+
+ if (nlh->nlmsg_type != RTM_NEWLINK)
+ continue;
+
+ ifm = NLMSG_DATA(nlh);
+ attr = IFLA_RTA(ifm);
+ attr_len = IFLA_PAYLOAD(nlh);
+ for (; RTA_OK(attr, attr_len);
+ attr = RTA_NEXT(attr, attr_len)) {
+ if (attr->rta_type != IFLA_PARENT_DEV_NAME)
+ continue;
+ if (strcmp(RTA_DATA(attr), parent_name))
+ continue;
+
+ *ifindex = ifm->ifi_index;
+ return 0;
+ }
+ }
+ }
+
+ return timeout < 0 ? timeout : -ETIMEDOUT;
+}
+
+int netdevsim_create(unsigned int *ifindex)
+{
+ int fd, id, err;
+
+ fd = open_link_socket();
+ if (fd < 0)
+ return fd;
+
+ id = create_new_device();
+ if (id < 0) {
+ close(fd);
+ return id;
+ }
+
+ err = recv_device_ifindex(fd, id, ifindex);
+ close(fd);
+ if (err) {
+ netdevsim_destroy(id);
+ return err;
+ }
+
+ return id;
+}
diff --git a/tools/testing/selftests/bpf/netdevsim_helpers.h b/tools/testing/selftests/bpf/netdevsim_helpers.h
new file mode 100644
index 000000000000..0a37d564a2c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/netdevsim_helpers.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef NETDEVSIM_HELPERS_H
+#define NETDEVSIM_HELPERS_H
+
+int netdevsim_create(unsigned int *ifindex);
+void netdevsim_destroy(unsigned int id);
+
+#endif /* NETDEVSIM_HELPERS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (4 preceding siblings ...)
2026-08-31 11:09 ` [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library Anton Protopopov
@ 2026-08-31 11:09 ` Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 11:09 ` [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks Anton Protopopov
` (2 subsequent siblings)
8 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
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
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (5 preceding siblings ...)
2026-08-31 11:09 ` [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook Anton Protopopov
@ 2026-08-31 11:09 ` Anton Protopopov
2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 22:34 ` [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Jakub Kicinski
2026-09-02 18:07 ` Alexei Starovoitov
8 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 11:09 UTC (permalink / raw)
To: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Anton Protopopov
Add tests for the new ethtool BPF hooks. The ioctl tests are
more-or-less straightforward, as there is only one hook point.
However, the doit/dump hooks are called from multiple sites, and the
tests are trying to reach as many of them as possible. The
exceptions are ethnl_rss_create_doit(), ethnl_rss_delete_doit() and
ethnl_tsinfo_dump_one_phydev(), which aren't reachable via netdevsim.
This is how the list off sub-tests maps to call sites:
linkstate_get_doit: netlink.c (ethnl_default_doit)
linkstate_get_dump: netlink.c (ethnl_default_dump_one)
cable_test_act: cabletest.c (ethnl_act_cable_test)
cable_test_tdr_act: cabletest.c (ethnl_act_cable_test_tdr)
features_set: features.c (ethnl_set_features)
module_fw_flash_act: module.c (ethnl_act_module_fw_flash)
tunnel_info_get_doit: tunnels.c (ethnl_tunnel_info_doit)
tunnel_info_get_dump: tunnels.c (ethnl_tunnel_info_dumpit)
tsinfo_get_dump: tsinfo.c (ethnl_tsinfo_dump_one_netdev)
rss_get_dump: rss.c (ethnl_rss_dumpit)
channels_set_doit: netlink.c (ethnl_default_set_doit)
ioctl: ioctl.c (ethtool_bpf_ioctl_hook)
ioctl_sub_cmd: ioctl.c (ethtool_bpf_ioctl_hook)
no tests: rss.c (ethnl_rss_{create,delete}_doit)
Two ioctl tests check, correspondingly, that the cmd and sub_cmd
are passed correctly.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
.../selftests/bpf/prog_tests/ethtool_lsm.c | 330 ++++++++++++++++++
.../testing/selftests/bpf/progs/ethtool_lsm.c | 168 +++++++++
2 files changed, 498 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
create mode 100644 tools/testing/selftests/bpf/progs/ethtool_lsm.c
diff --git a/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
new file mode 100644
index 000000000000..f4662fb1ae3c
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/genetlink.h>
+#include <net/if.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "netdevsim_helpers.h"
+#include "network_helpers.h"
+#include "netlink_helpers.h"
+#include "test_progs.h"
+
+#include "ethtool_lsm.skel.h"
+
+/* not probable to encounter this errno in real life */
+#define TEST_ERRNO EDOTDOT
+#define TEST_PHY_INDEX 1
+
+/* ethtool_netlink_generated.h is not copied to tools/include/uapi. */
+#define ETHTOOL_A_MODULE_FW_FLASH_HEADER 1
+#define ETHTOOL_MSG_MODULE_FW_FLASH_ACT 44
+#define ETHTOOL_A_HEADER_PHY_INDEX 4
+
+static int ethnl_request(int fd, __u16 family_id, __u8 cmd, __u16 hdr_attr,
+ __u16 extra_nest, __u32 ifindex, __u32 phy_index, bool dump)
+{
+ static __u32 sequence = 10;
+ struct genl_req req = {};
+ __u32 seq = sequence++;
+ struct rtattr *nest;
+ int err;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
+ req.nlh.nlmsg_type = family_id;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | (dump ? NLM_F_DUMP : 0);
+ req.nlh.nlmsg_seq = seq;
+ req.genl.cmd = cmd;
+ req.genl.version = ETHTOOL_GENL_VERSION;
+
+ nest = addattr_nest(&req.nlh, sizeof(req), hdr_attr | NLA_F_NESTED);
+ if (ifindex && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_DEV_INDEX, ifindex))
+ return -EMSGSIZE;
+ if (phy_index && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_PHY_INDEX, phy_index))
+ return -EMSGSIZE;
+ if (addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_FLAGS, ETHTOOL_FLAG_COMPACT_BITSETS))
+ return -EMSGSIZE;
+ addattr_nest_end(&req.nlh, nest);
+
+ if (extra_nest) {
+ nest = addattr_nest(&req.nlh, sizeof(req), extra_nest | NLA_F_NESTED);
+ addattr_nest_end(&req.nlh, nest);
+ }
+
+ err = genl_send(fd, &req.nlh);
+ if (err)
+ return err;
+
+ return genl_recv(fd, seq, family_id, dump);
+}
+
+static int netdev_set_up(__u32 ifindex)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct ifinfomsg ifm;
+ } req = {
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .nlh.nlmsg_type = RTM_NEWLINK,
+ .nlh.nlmsg_flags = NLM_F_REQUEST,
+ .ifm.ifi_family = AF_UNSPEC,
+ .ifm.ifi_index = ifindex,
+ .ifm.ifi_flags = IFF_UP,
+ .ifm.ifi_change = IFF_UP,
+ };
+ struct rtnl_handle rth;
+ int err;
+
+ err = rtnl_open(&rth, 0);
+ if (err)
+ return err;
+ err = rtnl_talk(&rth, &req.nlh, NULL);
+ rtnl_close(&rth);
+
+ return err;
+}
+
+static int ethtool_ioctl(__u32 ifindex, void *data)
+{
+ struct ifreq ifr = {};
+ int fd, err;
+
+ if (!if_indextoname(ifindex, ifr.ifr_name))
+ return -errno;
+
+ fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -errno;
+
+ ifr.ifr_data = data;
+ err = ioctl(fd, SIOCETHTOOL, &ifr);
+ if (err)
+ err = -errno;
+ close(fd);
+
+ return err;
+}
+
+static void check_doit(struct ethtool_lsm *skel, int fd, __u16 family_id,
+ __u32 ifindex, __u8 cmd, __u16 hdr_attr,
+ __u16 extra_nest, __u32 phy_index)
+{
+ int err;
+
+ skel->bss->target_ifindex = ifindex;
+ skel->bss->target_cmd = cmd;
+ skel->bss->target_sub_cmd = 0;
+ skel->bss->target_phy_index = phy_index;
+
+ skel->bss->allow = true;
+ err = ethnl_request(fd, family_id, cmd, hdr_attr, extra_nest, ifindex, phy_index, false);
+ if (!ASSERT_NEQ(err, -TEST_ERRNO, "doit (allow)"))
+ return;
+
+ skel->bss->allow = false;
+ err = ethnl_request(fd, family_id, cmd, hdr_attr, extra_nest, ifindex, phy_index, false);
+ ASSERT_EQ(err, -TEST_ERRNO, "doit (deny)");
+}
+
+static void check_dump(struct ethtool_lsm *skel, int fd, __u16 family_id,
+ __u32 ifindex, __u8 cmd, __u16 hdr_attr, bool single_dev,
+ __u32 phy_index)
+{
+ __u32 req_ifindex = single_dev ? ifindex : 0;
+ int err;
+
+ /*
+ * A phy_index which is not present on netdevsim can fail preparation on
+ * the first device in a dump, so match the hook on any device here.
+ */
+ skel->bss->target_ifindex = phy_index ? 0 : ifindex;
+
+ skel->bss->target_cmd = cmd;
+ skel->bss->target_sub_cmd = 0;
+ skel->bss->target_phy_index = phy_index;
+
+ skel->bss->allow = true;
+ err = ethnl_request(fd, family_id, cmd, hdr_attr, 0, req_ifindex, phy_index, true);
+ if (!ASSERT_NEQ(err, -TEST_ERRNO, "dump (allow)"))
+ return;
+
+ skel->bss->allow = false;
+ err = ethnl_request(fd, family_id, cmd, hdr_attr, 0, req_ifindex, phy_index, true);
+ ASSERT_EQ(err, -TEST_ERRNO, "dump (deny)");
+}
+
+static void check_ioctl(struct ethtool_lsm *skel, __u32 ifindex)
+{
+ struct ethtool_value value = { .cmd = ETHTOOL_GLINK };
+ int err;
+
+ skel->bss->target_ifindex = ifindex;
+ skel->bss->target_cmd = ETHTOOL_GLINK;
+ skel->bss->target_sub_cmd = 0;
+ skel->bss->target_phy_index = 0;
+
+ skel->bss->allow = true;
+ err = ethtool_ioctl(ifindex, &value);
+ if (!ASSERT_NEQ(err, -TEST_ERRNO, "ETHTOOL_GLINK (allow)"))
+ return;
+
+ skel->bss->allow = false;
+ err = ethtool_ioctl(ifindex, &value);
+ ASSERT_EQ(err, -TEST_ERRNO, "ETHTOOL_GLINK (deny)");
+}
+
+static void check_ioctl_sub_cmd(struct ethtool_lsm *skel, __u32 ifindex)
+{
+ struct ethtool_per_queue_op req = { .cmd = ETHTOOL_PERQUEUE };
+ int err;
+
+ /*
+ * ETHTOOL_PERQUEUE is the only cmd where sub_cmd differs from cmd,
+ * so try to apply policy only to one sub_cmd of two
+ */
+ skel->bss->target_ifindex = ifindex;
+ skel->bss->target_cmd = ETHTOOL_PERQUEUE;
+ skel->bss->target_sub_cmd = ETHTOOL_SCOALESCE;
+ skel->bss->target_phy_index = 0;
+ skel->bss->allow = false;
+
+ req.sub_command = ETHTOOL_SCOALESCE;
+ err = ethtool_ioctl(ifindex, &req);
+ ASSERT_EQ(err, -TEST_ERRNO, "ETHTOOL_SCOALESCE");
+
+ /* this fails in any case on netdevsim, but the errno is not ours => success */
+ req.sub_command = ETHTOOL_GCOALESCE;
+ ASSERT_NEQ(ethtool_ioctl(ifindex, &req), -TEST_ERRNO, "ETHTOOL_GCOALESCE");
+}
+
+void test_ethtool_lsm(void)
+{
+ __u32 netdevsim_ifindex;
+ struct ethtool_lsm *skel = NULL;
+ struct netns_obj *netns = NULL;
+ struct nstoken *nstoken = NULL;
+ int netdevsim_id = -1, family_id, fd = -1, err;
+
+ SYS_NOFAIL("ip netns del ethtool_lsm_ns");
+ netns = netns_new("ethtool_lsm_ns", false);
+ if (!ASSERT_OK_PTR(netns, "netns_new"))
+ goto out;
+
+ nstoken = open_netns("ethtool_lsm_ns");
+ if (!ASSERT_OK_PTR(nstoken, "open_netns"))
+ goto out;
+
+ netdevsim_id = netdevsim_create(&netdevsim_ifindex);
+ if (!ASSERT_GE(netdevsim_id, 0, "netdevsim_create"))
+ goto out;
+
+ err = netdev_set_up(netdevsim_ifindex);
+ if (!ASSERT_OK(err, "netdev_set_up"))
+ goto out;
+
+ skel = ethtool_lsm__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto out;
+
+ /* load-only examples */
+ bpf_program__set_autoattach(skel->progs.cve_2021_46916, false);
+ bpf_program__set_autoattach(skel->progs.cve_2025_21701, false);
+ bpf_program__set_autoattach(skel->progs.cve_2022_50651, false);
+ bpf_program__set_autoattach(skel->progs.cve_2024_46834_ioctl, false);
+ bpf_program__set_autoattach(skel->progs.cve_2024_46834_doit, false);
+
+ skel->bss->monitored_pid = getpid();
+
+ err = ethtool_lsm__attach(skel);
+ if (!ASSERT_OK(err, "attach"))
+ goto out;
+
+ fd = genl_open(0);
+ if (!ASSERT_OK_FD(fd, "genl_open"))
+ goto out;
+
+ family_id = genl_resolve_family(fd, ETHTOOL_GENL_NAME);
+ if (family_id == -ENOENT) {
+ test__skip();
+ goto out;
+ }
+ if (!ASSERT_GT(family_id, 0, "resolve_ethtool_family"))
+ goto out;
+
+ if (test__start_subtest("linkstate_get_doit"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_LINKSTATE_GET, ETHTOOL_A_LINKSTATE_HEADER, 0, 0);
+
+ if (test__start_subtest("linkstate_get_dump"))
+ check_dump(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_LINKSTATE_GET, ETHTOOL_A_LINKSTATE_HEADER, false, 0);
+
+ if (test__start_subtest("cable_test_act"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_CABLE_TEST_ACT, ETHTOOL_A_CABLE_TEST_HEADER, 0, 0);
+
+ if (test__start_subtest("cable_test_tdr_act"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
+ ETHTOOL_A_CABLE_TEST_TDR_HEADER, 0, 0);
+
+ if (test__start_subtest("features_set"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_FEATURES_SET, ETHTOOL_A_FEATURES_HEADER,
+ ETHTOOL_A_FEATURES_WANTED, 0);
+
+ if (test__start_subtest("module_fw_flash_act"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_MODULE_FW_FLASH_ACT,
+ ETHTOOL_A_MODULE_FW_FLASH_HEADER, 0, 0);
+
+ if (test__start_subtest("tunnel_info_get_doit"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_TUNNEL_INFO_GET, ETHTOOL_A_TUNNEL_INFO_HEADER, 0, 0);
+
+ if (test__start_subtest("tunnel_info_get_dump"))
+ check_dump(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_TUNNEL_INFO_GET, ETHTOOL_A_TUNNEL_INFO_HEADER,
+ false, 0);
+
+ if (test__start_subtest("tsinfo_get_dump"))
+ check_dump(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_TSINFO_GET, ETHTOOL_A_TSINFO_HEADER, true, 0);
+
+ if (test__start_subtest("rss_get_dump"))
+ check_dump(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_RSS_GET, ETHTOOL_A_RSS_HEADER, true, 0);
+
+ if (test__start_subtest("channels_set_doit"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_CHANNELS_SET, ETHTOOL_A_CHANNELS_HEADER, 0, 0);
+
+ if (test__start_subtest("cable_test_phy_index"))
+ check_doit(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_CABLE_TEST_ACT, ETHTOOL_A_CABLE_TEST_HEADER,
+ 0, TEST_PHY_INDEX);
+
+ if (test__start_subtest("strset_get_phy_index_dump"))
+ check_dump(skel, fd, family_id, netdevsim_ifindex,
+ ETHTOOL_MSG_STRSET_GET, ETHTOOL_A_STRSET_HEADER, true,
+ TEST_PHY_INDEX);
+
+ if (test__start_subtest("ioctl"))
+ check_ioctl(skel, netdevsim_ifindex);
+
+ if (test__start_subtest("ioctl_sub_cmd"))
+ check_ioctl_sub_cmd(skel, netdevsim_ifindex);
+
+out:
+ if (fd >= 0)
+ close(fd);
+ ethtool_lsm__destroy(skel);
+ if (netdevsim_id >= 0)
+ netdevsim_destroy(netdevsim_id);
+ close_netns(nstoken);
+ netns_free(netns);
+}
diff --git a/tools/testing/selftests/bpf/progs/ethtool_lsm.c b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
new file mode 100644
index 000000000000..c6b21aeed6fe
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
@@ -0,0 +1,168 @@
+// 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>
+
+__u32 monitored_pid;
+__u32 target_ifindex;
+__u32 target_cmd;
+__u32 target_sub_cmd;
+__u32 target_phy_index;
+bool allow;
+
+/*
+ * This test is used for all hooks, just checks that it is ours,
+ * and allows/denies based on the "allow" global variable
+ */
+static int test_policy(const struct net_device *dev, __u32 cmd, __u32 sub_cmd, __u32 phy_index)
+{
+ __u32 pid;
+
+ pid = bpf_get_current_pid_tgid() >> 32;
+ if (pid != monitored_pid)
+ return 0;
+
+ if (!dev || (target_ifindex && dev->ifindex != target_ifindex))
+ return 0;
+
+ if (target_cmd && cmd != target_cmd)
+ return 0;
+
+ if (target_sub_cmd && sub_cmd != target_sub_cmd)
+ return 0;
+
+ if (phy_index != target_phy_index)
+ return -EDOTDOT;
+
+ return allow ? 0 : -EDOTDOT; /* unlikely to see this errno outside this test */
+}
+
+SEC("lsm/ethtool_ioctl")
+int BPF_PROG(test_ethtool_ioctl, const struct net_device *dev, __u32 cmd, __u32 sub_cmd, int ret)
+{
+ if (ret)
+ return ret;
+
+ return test_policy(dev, cmd, sub_cmd, 0);
+}
+
+SEC("lsm/ethtool_netlink_doit")
+int BPF_PROG(test_ethtool_doit, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
+{
+ if (ret)
+ return ret;
+
+ return test_policy(dev, cmd, 0, phy_index);
+}
+
+SEC("lsm/ethtool_netlink_dump")
+int BPF_PROG(test_ethtool_dump, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
+{
+ if (ret)
+ return ret;
+
+ return test_policy(dev, cmd, 0, phy_index);
+}
+
+/*
+ * The programs below are policy examples for four CVEs. They are
+ * load only, and added to illustrate how actual policies might look
+ * like for different types of CVEs.
+ */
+
+#define ETHTOOL_TEST_CMD 0x0000001aU
+#define ETHTOOL_SCHANNELS_CMD 0x0000003dU
+
+static bool is_ixgbe(const struct net_device *dev)
+{
+ char driver_name[16];
+ long len;
+
+ if (!dev || !dev->dev.parent || !dev->dev.parent->driver)
+ return false;
+
+ len = bpf_probe_read_kernel_str(driver_name, sizeof(driver_name),
+ dev->dev.parent->driver->name);
+ if (len < 0)
+ return false;
+
+ return bpf_strncmp(driver_name, 6, "ixgbe") == 0;
+}
+
+/*
+ * CVE-2021-46916 is an example of ioctl-only bug for a particular driver (ixgbe).
+ */
+SEC("lsm/ethtool_ioctl")
+int BPF_PROG(cve_2021_46916, const struct net_device *dev, __u32 cmd, __u32 sub_cmd, int ret)
+{
+ if (ret)
+ return ret;
+
+ if (cmd != ETHTOOL_TEST_CMD)
+ return 0;
+
+ return is_ixgbe(dev) ? -EPERM : 0;
+}
+
+/*
+ * CVE-2025-21701 is an example of a bug which must be mitigated under a lock,
+ * as access to dev->reg_state must be protected.
+ */
+SEC("lsm/ethtool_netlink_doit")
+int BPF_PROG(cve_2025_21701, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
+{
+ if (ret)
+ return ret;
+
+ if (!dev)
+ return 0;
+
+ return dev->reg_state >= NETREG_UNREGISTERING ? -ENODEV : 0;
+}
+
+/*
+ * CVE-2022-50651 is an example of a bug triggered only by .dump, not by .doit.
+ */
+SEC("lsm/ethtool_netlink_dump")
+int BPF_PROG(cve_2022_50651, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
+{
+ if (ret)
+ return ret;
+
+ return cmd == ETHTOOL_MSG_MODULE_EEPROM_GET ? -EPERM : 0;
+}
+
+/*
+ * CVE-2024-46834 is an example of a bug which requires to filter out both
+ * channels: ioctl and netlink. Note that they receive different cmd values.
+ */
+
+SEC("lsm/ethtool_ioctl")
+int BPF_PROG(cve_2024_46834_ioctl, const struct net_device *dev, __u32 cmd, __u32 sub_cmd, int ret)
+{
+ if (ret)
+ return ret;
+
+ if (cmd == ETHTOOL_SCHANNELS_CMD)
+ return -EPERM;
+
+ return 0;
+}
+
+SEC("lsm/ethtool_netlink_doit")
+int BPF_PROG(cve_2024_46834_doit, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
+{
+ if (ret)
+ return ret;
+
+ if (cmd == ETHTOOL_MSG_CHANNELS_SET)
+ return -EPERM;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
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
1 sibling, 1 reply; 31+ messages in thread
From: bot+bpf-ci @ 2026-08-31 11:50 UTC (permalink / raw)
To: a.s.protopopov, bpf, linux-security-module, netdev, ast, daniel,
andrii, eddyz87, memxor, kpsingh, matt, john.fastabend, brauner,
paul, torvalds, edumazet, kuba, pabeni
Cc: a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4295 bytes --]
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 460cb72688450..d01dd1f096fc1 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5035,6 +5035,7 @@ L: bpf@vger.kernel.org
> S: Maintained
> F: Documentation/bpf/prog_lsm.rst
> F: include/linux/bpf_lsm.h
> +F: include/linux/bpf_lsm_hook_defs.h
> F: kernel/bpf/bpf_lsm.c
> F: kernel/bpf/bpf_lsm_proto.c
> F: kernel/trace/bpf_trace.c
[ ... ]
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index dda272d78f013..1e54c7cca27ad 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -16,9 +16,19 @@
>
> extern bool bpf_lsm_initialized __ro_after_init;
>
> +/*
> + * Technically, checking bpf_lsm_initialized is not necessary.
> + * But if it is off, then this means that all security_* calls
> + * do not call BPF, and it doesn't look reasonable to enable
> + * only "non-LSM" bpf hooks...
> + */
> +#define bpf_lsm_hook(NAME, ...) \
> + (bpf_lsm_initialized ? bpf_lsm_##NAME(__VA_ARGS__) : 0)
Does the new bpf_lsm_hook() macro constrain what hook declarations
can appear in bpf_lsm_hook_defs.h? The macro hardcodes 0 as the return
value in two places:
1. When bpf_lsm_initialized is false, the ternary returns 0, but when
it is true with no program attached, the __weak nop returns the hook's
declared DEFAULT. This means LSM_HOOK(int, 0, ...) behaves consistently,
but LSM_HOOK(int, -EOPNOTSUPP, ...) would return different values
depending on whether 'bpf' appears in lsm= - 0 before initialization,
-EOPNOTSUPP after.
2. Because of the ternary, LSM_HOOK(void, ...) cannot compile at call
sites, since the expression `cond ? void_expr : 0` is invalid.
3. The CONFIG_BPF_LSM=n stub at line 128:
#define bpf_lsm_hook(NAME, ...) 0
discards its arguments entirely, so call-site argument checking only
happens on =y builds. Does this create a risk that =n configurations
would fail to catch type mismatches or side-effect issues?
> #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> RET bpf_lsm_##NAME(__VA_ARGS__);
> #include <linux/lsm_hook_defs.h>
> +#include <linux/bpf_lsm_hook_defs.h>
> #undef LSM_HOOK
[ ... ]
> diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
> new file mode 100644
> index 0000000000000..29bc0b514d168
> --- /dev/null
> +++ b/include/linux/bpf_lsm_hook_defs.h
> @@ -0,0 +1,6 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * This is a set of BPF LSM hooks, which are _not_ fully implemented
> + * as LSM hooks. Thus, they only can be used by BPF LSM programs.
> + */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 82c5988417a0c..add344ea26914 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -28,11 +28,13 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
> }
>
> #include <linux/lsm_hook_defs.h>
> +#include <linux/bpf_lsm_hook_defs.h>
> #undef LSM_HOOK
>
> #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
> BTF_SET_START(bpf_lsm_hooks)
> #include <linux/lsm_hook_defs.h>
> +#include <linux/bpf_lsm_hook_defs.h>
> #undef LSM_HOOK
> BTF_SET_END(bpf_lsm_hooks)
The patch adds bpf_lsm_hook_defs.h to three of the four BPF-owned
LSM_HOOK expansion sites, but not the fourth. Does this need updating?
Updated:
include/linux/bpf_lsm.h (prototypes, lines 30-31)
kernel/bpf/bpf_lsm.c (__weak nop definitions, lines 30-31)
kernel/bpf/bpf_lsm.c (bpf_lsm_hooks BTF set, lines 36-37)
Not updated:
kernel/bpf/bpf_lsm.c (void_lsm_hooks BTF set, further down in the file)
The void_lsm_hooks set is consumed by bpf_lsm_hook_returns_errno() in
bpf_lsm.c, which feeds cgroup_lsm_atype[i].returns_errno. If a hook
declared as LSM_HOOK(void, ...) appeared in the new header, wouldn't it
be omitted from void_lsm_hooks and incorrectly reported as errno-returning?
This is latent today because the four hooks added later in the series all
return int, but is there a reason the fourth site shouldn't include the
new header, or should the header document that it only supports int hooks?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library
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
0 siblings, 1 reply; 31+ messages in thread
From: bot+bpf-ci @ 2026-08-31 12:07 UTC (permalink / raw)
To: a.s.protopopov, bpf, linux-security-module, netdev, ast, daniel,
andrii, eddyz87, memxor, kpsingh, matt, john.fastabend, brauner,
paul, torvalds, edumazet, kuba, pabeni
Cc: a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 7090 bytes --]
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index b481b867f372e..c21f89dbe70c0 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -862,6 +862,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
> unpriv_helpers.c \
> sysctl_helpers.c \
> netlink_helpers.c \
> + netdevsim_helpers.c \
> jit_disasm_helpers.c \
> io_helpers.c \
> test_loader.c \
> diff --git a/tools/testing/selftests/bpf/netdevsim_helpers.c b/tools/testing/selftests/bpf/netdevsim_helpers.c
> new file mode 100644
> index 0000000000000..fbed7442b2e8d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/netdevsim_helpers.c
> @@ -0,0 +1,176 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <linux/rtnetlink.h>
> +#include <poll.h>
> +#include <stdarg.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/socket.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +#include "netdevsim_helpers.h"
> +
> +static int echo(const char *path, const char *fmt, ...)
> +{
> + char buf[64];
> + va_list ap;
> + int fd, len, err = 0;
> +
> + va_start(ap, fmt);
> + len = vsnprintf(buf, sizeof(buf), fmt, ap);
> + va_end(ap);
> +
> + fd = open(path, O_WRONLY);
> + if (fd < 0)
> + return -errno;
> + if (write(fd, buf, len) != len)
> + err = -errno;
> + close(fd);
> +
> + return err;
> +}
A subsystem pattern flags this as potentially concerning: the guide
directs reporting when a test hand-rolls sysfs/file I/O instead of using
existing utilities that the subsystem already provides.
Looking at echo(), it implements the open(O_WRONLY)/write()/close()
sequence that write_sysctl() in test_progs.c already performs for BPF
selftests. Could this reuse that existing helper?
> +
> +void netdevsim_destroy(unsigned int id)
> +{
> + echo("/sys/bus/netdevsim/del_device", "%u", id);
> +}
> +
> +static int create_new_device(void)
> +{
> + unsigned int id;
> + int err;
> +
> + /* if 10K is not enough, then something is clearly not right */
> + for (id = 0; id < 10000; id++) {
> + err = echo("/sys/bus/netdevsim/new_device", "%u", id);
> + if (!err)
> + return id;
> + if (err != -ENOSPC)
> + return err;
> + }
> +
> + return -ENOSPC;
> +}
> +
> +static int open_link_socket(void)
> +{
> + struct sockaddr_nl addr = {
> + .nl_family = AF_NETLINK,
> + .nl_groups = RTMGRP_LINK,
> + };
> + int fd;
> +
> + fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
> + if (fd < 0)
> + return -errno;
> + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) {
> + int err = -errno;
> +
> + close(fd);
> + return err;
> + }
> +
> + return fd;
> +}
The subsystem guide also flags this pattern: open_link_socket() appears
to duplicate rtnl_open(&rth, RTMGRP_LINK) from netlink_helpers.c, which
does the same socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)
plus bind() with nl_groups set to the subscriptions parameter.
In particular, it looks structurally similar to genl_open() in
netlink_helpers.c, which the immediately preceding commit in this series
(a1205972510b7 "selftests/bpf: Extract some helpers from tests to the
netlink library") added explicitly for sharing netlink plumbing.
The re-implementation also appears to omit the SO_RCVBUF = 1024 * 1024
setting that rtnl_open_byproto() sets in netlink_helpers.c, so the new
socket runs on the default net.core.rmem_default receive buffer.
Similarly, the receive/poll loop in recv_device_ifindex() appears to
hand-roll what rtnl_recvmsg()/__rtnl_recvmsg() in netlink_helpers.c
already do. Notably, those use MSG_PEEK | MSG_TRUNC to size the receive
buffer, whereas the new loop uses a fixed 16 KiB buffer with a plain
recv(fd, buf, sizeof(buf), 0) and no MSG_TRUNC check. Additionally,
__rtnl_recvmsg() retries on EINTR/EAGAIN where the implementation below
returns -errno.
The agent notes that functionally these hand-rolled versions are correct
for their specific use case, and this is selftest-only code. However,
could the new helpers reuse the existing netlink infrastructure?
> +
> +static int remaining_timeout_ms(const struct timespec *deadline)
> +{
> + struct timespec now;
> + long long remaining;
> +
> + if (clock_gettime(CLOCK_MONOTONIC, &now))
> + return -errno;
> +
> + remaining = (deadline->tv_sec - now.tv_sec) * 1000 +
> + (deadline->tv_nsec - now.tv_nsec) / 1000000;
> +
> + return remaining > 0 ? remaining : 0;
> +}
This isn't a bug, but since clock_gettime(CLOCK_MONOTONIC, ...) on a
valid stack timespec can't really fail, would it be simpler to have
remaining_timeout_ms() just return milliseconds? That way the timeout
check at the end of recv_device_ifindex() (which currently needs
`return timeout < 0 ? timeout : -ETIMEDOUT;` to forward the
clock_gettime error) could be a plain `return -ETIMEDOUT;`.
> +
> +static int recv_device_ifindex(int fd, unsigned int id, unsigned int *ifindex)
> +{
> + char parent_name[32], buf[16 * 1024];
> + struct pollfd pfd = {
> + .fd = fd,
> + .events = POLLIN,
> + };
> + struct timespec deadline;
> + struct nlmsghdr *nlh;
> + int len, ret, timeout;
> +
> + snprintf(parent_name, sizeof(parent_name), "netdevsim%u", id);
> + if (clock_gettime(CLOCK_MONOTONIC, &deadline))
> + return -errno;
> + deadline.tv_sec += 5;
> +
> + for (timeout = remaining_timeout_ms(&deadline); timeout > 0;
> + timeout = remaining_timeout_ms(&deadline)) {
> + ret = poll(&pfd, 1, timeout);
> + if (ret < 0) {
> + if (errno == EINTR)
> + continue;
> + return -errno;
> + }
> + if (!ret)
> + return -ETIMEDOUT;
> + if (!(pfd.revents & POLLIN))
> + return -EIO;
> +
> + len = recv(fd, buf, sizeof(buf), 0);
> + if (len < 0)
> + return -errno;
> +
> + for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
> + nlh = NLMSG_NEXT(nlh, len)) {
> + struct ifinfomsg *ifm;
> + struct rtattr *attr;
> + int attr_len;
> +
> + if (nlh->nlmsg_type != RTM_NEWLINK)
> + continue;
> +
> + ifm = NLMSG_DATA(nlh);
> + attr = IFLA_RTA(ifm);
> + attr_len = IFLA_PAYLOAD(nlh);
> + for (; RTA_OK(attr, attr_len);
> + attr = RTA_NEXT(attr, attr_len)) {
> + if (attr->rta_type != IFLA_PARENT_DEV_NAME)
> + continue;
> + if (strcmp(RTA_DATA(attr), parent_name))
> + continue;
> +
> + *ifindex = ifm->ifi_index;
> + return 0;
> + }
> + }
> + }
> +
> + return timeout < 0 ? timeout : -ETIMEDOUT;
> +}
> +
> +int netdevsim_create(unsigned int *ifindex)
> +{
> + int fd, id, err;
> +
> + fd = open_link_socket();
> + if (fd < 0)
> + return fd;
> +
> + id = create_new_device();
> + if (id < 0) {
> + close(fd);
> + return id;
> + }
> +
> + err = recv_device_ifindex(fd, id, ifindex);
> + close(fd);
> + if (err) {
> + netdevsim_destroy(id);
> + return err;
> + }
> +
> + return id;
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook
2026-08-31 11:09 ` [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook Anton Protopopov
@ 2026-08-31 12:07 ` bot+bpf-ci
2026-08-31 13:01 ` Anton Protopopov
0 siblings, 1 reply; 31+ messages in thread
From: bot+bpf-ci @ 2026-08-31 12:07 UTC (permalink / raw)
To: a.s.protopopov, bpf, linux-security-module, netdev, ast, daniel,
andrii, eddyz87, memxor, kpsingh, matt, john.fastabend, brauner,
paul, torvalds, edumazet, kuba, pabeni
Cc: a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 5286 bytes --]
> 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
The enumeration lists four subtests but the patch registers five. The
"netns" subtest is missing from this list:
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);
The netns subtest is the only one exercising the net argument of
bpf_lsm_genl_family_rcv_msg(), and it introduces target_netns_inum plus
a filter in the BPF program, netns_new()/open_netns(), a second socket,
and the netns_inum() helper - roughly 45 of the 242 added lines. Since
the netns filtering path is the least obvious part of the hook contract,
would it make sense to add a fifth bullet, for example "* netns: deny a
command only inside a target netns, allow it elsewhere"?
> diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
> index 08c6e8a4e..6ca0b083a 100644
> --- a/tools/testing/selftests/bpf/config
> +++ b/tools/testing/selftests/bpf/config
> @@ -30,6 +30,7 @@ CONFIG_DYNAMIC_FTRACE=y
> CONFIG_FPROBE=y
> CONFIG_FUNCTION_TRACER=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 000000000..9f866c4c6
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/genl_lsm.c
[ ... ]
> +static void test_nlmsg_flags(struct genl_lsm *skel, int fd)
> +{
> + int err;
> +
> + /* ensure denying works */
> + skel->bss->target_cmd = CTRL_CMD_GETFAMILY;
> + skel->bss->target_flags = NLM_F_REQUEST | NLM_F_DUMP;
> + skel->bss->allow = false;
> +
> + err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
> + ASSERT_OK(err, "doit_flags_not_matched");
> +
> + err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, true);
> + ASSERT_EQ(err, -TEST_ERRNO, "deny_dump_flags");
> +
> + /* 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");
> +}
The second half reuses the assertion tags from the first half, but they
name the opposite case. The dump request gets "doit_flags_not_matched"
and the doit request gets "deny_dump_flags". The test logic is correct
but when test_progs prints only the tag and __func__, a CI failure
reporting "genl_lsm:FAIL:deny_dump_flags" cannot be attributed to either
of the two assertions with that name, and whichever one it is, the tag
names the wrong netlink operation. Would four distinct tags - for example
"deny_dump_flags_matched", "allow_doit_flags_unmatched",
"allow_dump_flags_unmatched", "deny_doit_flags_matched" - remove the
ambiguity?
[ ... ]
> +void test_genl_lsm(void)
> +{
> + struct genl_lsm *skel = NULL;
> + int fd = -1;
> + int err, other_id;
> +
> + 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;
> + }
A subsystem pattern flags this as potentially concerning: when
CONFIG_ETHTOOL_NETLINK is absent, genl_resolve_family() returns -ENOENT
and the test skips with no printed reason. A reader of the TAP output
sees "#N genl_lsm:SKIP" with nothing indicating the missing prerequisite
was the ethtool genl family, so a silently-skipped test looks identical
to a deliberately-disabled one. All five subtests are skipped this way,
including the four (doit, dump, nlmsg_flags, netns) that do not need
ethtool - only test_other_family() consumes other_id.
This pattern is common in bpf selftests (e.g. aggregate_ret.c:52,
arena_spin_lock.c:57 and :66 all do exactly this), and the config hunk
in this same patch adds CONFIG_ETHTOOL_NETLINK so the skip path should
not be taken in a properly configured CI run. Would a one-line printf
plus optionally deferring the ethtool resolution into test_other_family()
so the other four subtests still run be worth addressing?
[ ... ]
> 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 000000000..c4d8b4d23
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/genl_lsm.c
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv
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
0 siblings, 1 reply; 31+ messages in thread
From: bot+bpf-ci @ 2026-08-31 12:07 UTC (permalink / raw)
To: a.s.protopopov, bpf, linux-security-module, netdev, ast, daniel,
andrii, eddyz87, memxor, kpsingh, matt, john.fastabend, brauner,
paul, torvalds, edumazet, kuba, pabeni
Cc: a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6860 bytes --]
> commit 18faf289d6ccb694a2ce1ae68f5c3cf881de2fd4
> Author: Anton Protopopov <a.s.protopopov@gmail.com>
>
> net, bpf: Add a generic netlink hook on msg_rcv
>
> Add a new bpf_lsm_genl_family_rcv_msg() BPF hook called from within
> the genl_family_rcv_msg() function. The hook sees the genl family,
> net, cmd, and nlmsg_flags. As the hook receives all the information
> pre-parsed, it is not put into the sleepable list.
>
> There is already an existing LSM hook sitting right at netlink_send,
> however, it looks to be too coarse-grained to be used directly. The
> netlink_send hook sees the struct sock and struct skb, and thus must
> duplicate all the parsing inside BPF. The messages can actually contain
> multiple netlink datagrams, dynamic info (see e.g., selinux_nlmsg_lookup()
> in context of generic netlink), etc.
>
> The purpose of this hook is to mitigate, in a coarse-grained way,
> CVEs reachable via the corresponding control path: genl recvmsg.
> While looking at the recent CVEs, robots found ~300 CVEs reachable
> via the genl_rcv. We can roughly split them as follows:
>
> * Around 100 are ethtool-related. For more detailed information,
> see the following commit which adds even more finer-grained
> ethtool hooks.
>
> * Around 100 of the remaining are fully dominated by the new hook.
>
> * Around 100 remaining aren't fully mitigatable, as other control
> paths, besides the genl, lead to the buggy code.
>
> To be more precise, the following CVEs are properly mitigated by the
> new hook:
>
> * CVE-2021-47518 CVE-2021-47594 CVE-2021-47612 CVE-2022-48687
> CVE-2022-49060 CVE-2022-49085 CVE-2022-49300 CVE-2022-49862
> CVE-2022-49959 CVE-2022-50656 CVE-2022-50834 CVE-2022-50854
> CVE-2023-52698 CVE-2023-52702 CVE-2023-52977 CVE-2023-53113
> CVE-2023-53298 CVE-2023-53686 CVE-2023-53843 CVE-2024-26608
> CVE-2024-26663 CVE-2024-26754 CVE-2024-26811 CVE-2024-26950
> CVE-2024-27025 CVE-2024-27410 CVE-2024-36941 CVE-2024-38562
> CVE-2024-43912 CVE-2024-45010 CVE-2024-46711 CVE-2024-49937
> CVE-2024-50085 CVE-2025-21706 CVE-2025-21787 CVE-2025-21862
> CVE-2025-21875 CVE-2025-21947 CVE-2025-22024 CVE-2025-38184
> CVE-2025-38443 CVE-2025-40084 CVE-2025-68263 CVE-2025-68366
> CVE-2026-23297 CVE-2026-23436 CVE-2026-43252 CVE-2026-43394
> CVE-2026-53128 CVE-2026-53227 CVE-2026-53233 CVE-2026-53238
> CVE-2026-53257
>
> The following are mitigated as well, but, theoretically, could
> benefit from a finer-grained hook, running after a deeper
> parsing/taking locks (this is however out of scope for now to
> add finer-grained hooks for each involved subsystems, as, unlike
> ethtool, they are more-or-less evenly distributed):
>
> * CVE-2021-47250 CVE-2021-47257 CVE-2021-47554 CVE-2022-48915
> CVE-2022-49374 CVE-2022-49505 CVE-2022-50042 CVE-2022-50678
> CVE-2023-52845 CVE-2023-53141 CVE-2023-53543 CVE-2023-53652
> CVE-2023-54031 CVE-2024-26725 CVE-2024-26951 CVE-2024-42073
> CVE-2024-42154 CVE-2024-46676 CVE-2024-47687 CVE-2024-50028
> CVE-2025-21973 CVE-2025-38020 CVE-2025-38628 CVE-2025-40254
> CVE-2025-40321 CVE-2025-68785 CVE-2025-71091 CVE-2025-71297
> CVE-2026-23321 CVE-2026-31707 CVE-2026-31744 CVE-2026-43012
> CVE-2026-43467 CVE-2026-43481 CVE-2026-45840 CVE-2026-52978
> CVE-2026-52979 CVE-2023-53570 CVE-2024-42114 CVE-2024-53189
> CVE-2024-56663 CVE-2025-21909 CVE-2026-53182
>
> Policies for selected CVEs are added in the corresponding
> selftests patch.
>
> Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
This isn't a bug, but would it be worth trimming the two CVE enumerations
or pointing at where the classification lives so the coarse-grained-vs-
netlink_send rationale is not outnumbered roughly 3:1 by bare IDs?
The selftests patch is already referenced for the policies. The tail of
the second CVE list also breaks the ascending order it starts with
(CVE-2026-52979 is followed by CVE-2023-53570).
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 1e54c7cca27ad..452b9d1d7c494 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -12,6 +12,8 @@
> #include <linux/bpf_verifier.h>
> #include <linux/lsm_hooks.h>
>
> +struct genl_family; /* for the bpf_lsm_genl_family_rcv_msg hook */
> +
> #ifdef CONFIG_BPF_LSM
>
> extern bool bpf_lsm_initialized __ro_after_init;
> diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
> index 29bc0b514d168..3f57022744a31 100644
> --- a/include/linux/bpf_lsm_hook_defs.h
> +++ b/include/linux/bpf_lsm_hook_defs.h
> @@ -4,3 +4,10 @@
> * This is a set of BPF LSM hooks, which are _not_ fully implemented
> * as LSM hooks. Thus, they only can be used by BPF LSM programs.
> */
> +
> +#ifdef CONFIG_NET
> +
> +LSM_HOOK(int, 0, genl_family_rcv_msg, const struct genl_family *family,
> + const struct net *net, u32 cmd, u16 nlmsg_flags)
> +
> +#endif /* CONFIG_NET */
> diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
> index 0da39eaed255f..12b4f49c72534 100644
> --- a/net/netlink/genetlink.c
> +++ b/net/netlink/genetlink.c
> @@ -17,6 +17,7 @@
> #include <linux/skbuff.h>
> #include <linux/mutex.h>
> #include <linux/bitmap.h>
> +#include <linux/bpf_lsm.h>
> #include <linux/rwsem.h>
> #include <linux/idr.h>
> #include <net/sock.h>
> @@ -1161,6 +1162,7 @@ static int genl_family_rcv_msg(const struct genl_family *family,
> struct genlmsghdr *hdr = nlmsg_data(nlh);
> struct genl_split_ops op;
> int hdrlen;
> + int err;
> u8 flags;
This isn't a bug, but would int err sit better after u8 flags so the
declaration block stays longest-to-shortest, per the reverse xmas tree
ordering in Documentation/process/maintainer-netdev.rst?
[ ... ]
> @@ -1187,6 +1189,10 @@ static int genl_family_rcv_msg(const struct genl_family *family,
> !netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
> return -EPERM;
>
> + err = bpf_lsm_hook(genl_family_rcv_msg, family, net, hdr->cmd, nlh->nlmsg_flags);
> + if (err)
> + return err;
> +
This isn't a bug, but could the bpf_lsm_hook() call be wrapped the way
the genl_family_rcv_msg_dumpit() / _doit() calls just below it are, so
it stays under 80 columns like the rest of the file?
> if (flags & GENL_CMD_CAP_DUMP)
> return genl_family_rcv_msg_dumpit(family, skb, nlh, extack,
> &op, hdrlen, net);
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks
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
0 siblings, 1 reply; 31+ messages in thread
From: bot+bpf-ci @ 2026-08-31 12:07 UTC (permalink / raw)
To: a.s.protopopov, bpf, linux-security-module, netdev, ast, daniel,
andrii, eddyz87, memxor, kpsingh, matt, john.fastabend, brauner,
paul, torvalds, edumazet, kuba, pabeni
Cc: a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 8999 bytes --]
> This is how the list off sub-tests maps to call sites:
>
> linkstate_get_doit: netlink.c (ethnl_default_doit)
> linkstate_get_dump: netlink.c (ethnl_default_dump_one)
> cable_test_act: cabletest.c (ethnl_act_cable_test)
> cable_test_tdr_act: cabletest.c (ethnl_act_cable_test_tdr)
> features_set: features.c (ethnl_set_features)
> module_fw_flash_act: module.c (ethnl_act_module_fw_flash)
> tunnel_info_get_doit: tunnels.c (ethnl_tunnel_info_doit)
> tunnel_info_get_dump: tunnels.c (ethnl_tunnel_info_dumpit)
> tsinfo_get_dump: tsinfo.c (ethnl_tsinfo_dump_one_netdev)
> rss_get_dump: rss.c (ethnl_rss_dumpit)
> channels_set_doit: netlink.c (ethnl_default_set_doit)
> ioctl: ioctl.c (ethtool_bpf_ioctl_hook)
> ioctl_sub_cmd: ioctl.c (ethtool_bpf_ioctl_hook)
> no tests: rss.c (ethnl_rss_{create,delete}_doit)
The changelog's sub-tests table does not match the sub-tests the patch
adds. The code registers 15 sub-tests, including cable_test_phy_index
(check_doit with TEST_PHY_INDEX, exercising the phy_index argument of
the doit hook) and strset_get_phy_index_dump (check_dump with
TEST_PHY_INDEX, exercising the phy_index argument of the dump hook),
neither of which appears in the 13-row table.
These are the only two sub-tests that cover the phy_index parameter at
all, i.e. the least obvious part of the new hook ABI, so their absence
from the mapping is the part a reviewer is most likely to want
documented.
Two smaller inconsistencies in the same block: the prose names
ethnl_tsinfo_dump_one_phydev() as unreachable via netdevsim, but the
'no tests' row lists only rss.c (ethnl_rss_{create,delete}_doit); and
'the list off sub-tests' should read 'the list of sub-tests'.
> diff --git a/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
> new file mode 100644
> index 0000000000000..f4662fb1ae3ce
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
> @@ -0,0 +1,330 @@
[ ... ]
> +static int ethnl_request(int fd, __u16 family_id, __u8 cmd, __u16 hdr_attr,
> + __u16 extra_nest, __u32 ifindex, __u32 phy_index, bool dump)
> +{
> + static __u32 sequence = 10;
> + struct genl_req req = {};
> + __u32 seq = sequence++;
> + struct rtattr *nest;
> + int err;
> +
> + req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
> + req.nlh.nlmsg_type = family_id;
> + req.nlh.nlmsg_flags = NLM_F_REQUEST | (dump ? NLM_F_DUMP : 0);
> + req.nlh.nlmsg_seq = seq;
> + req.genl.cmd = cmd;
> + req.genl.version = ETHTOOL_GENL_VERSION;
> +
> + nest = addattr_nest(&req.nlh, sizeof(req), hdr_attr | NLA_F_NESTED);
> + if (ifindex && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_DEV_INDEX, ifindex))
> + return -EMSGSIZE;
> + if (phy_index && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_PHY_INDEX, phy_index))
> + return -EMSGSIZE;
> + if (addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_FLAGS, ETHTOOL_FLAG_COMPACT_BITSETS))
> + return -EMSGSIZE;
> + addattr_nest_end(&req.nlh, nest);
> +
> + if (extra_nest) {
> + nest = addattr_nest(&req.nlh, sizeof(req), extra_nest | NLA_F_NESTED);
> + addattr_nest_end(&req.nlh, nest);
> + }
> +
> + err = genl_send(fd, &req.nlh);
> + if (err)
> + return err;
> +
> + return genl_recv(fd, seq, family_id, dump);
> +}
ethnl_request() never sets NLM_F_ACK, so a doit request that succeeds
without producing a reply message gets no netlink response at all:
netlink_rcv_skb() only calls netlink_ack() when
'nlh->nlmsg_flags & NLM_F_ACK || err'.
This is exactly what happens in the allow arm of the channels_set_doit
subtest. ETHTOOL_MSG_CHANNELS_SET is dispatched through
ethnl_default_set_doit(); ethnl_set_channels_validate() returns 1 on
netdevsim (both get_channels and set_channels are implemented), so the
hook is reached and the deny arm works, but the request carries only
the header nest, so ethnl_set_channels() takes 'if (!mod) return 0;'
and the SET handler produces neither a reply nor an error.
genl_recv() then blocks until the SO_RCVTIMEO set in genl_open()
(struct timeval { .tv_sec = 1 }) expires and returns -EAGAIN.
Consequences: (a) 'if (!ASSERT_NEQ(err, -TEST_ERRNO, "doit (allow)"))'
passes vacuously - -EAGAIN would also be returned if the kernel had
wrongly dropped the request, so the allow arm of this subtest pins
nothing; (b) the subtest silently costs a full second of wall time, and
would grow with each additional SET-style subtest added later.
Setting NLM_F_ACK in nlmsg_flags (rtnl_talk() in netlink_helpers.c
does this itself when no answer is requested) makes the kernel ack the
successful set, so the allow arm returns 0 immediately and actually
distinguishes success from 'nothing came back'.
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/ethtool_lsm.c b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
> new file mode 100644
> index 0000000000000..c6b21aeed6fec
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
> @@ -0,0 +1,168 @@
[ ... ]
> +/*
> + * This test is used for all hooks, just checks that it is ours,
> + * and allows/denies based on the "allow" global variable
> + */
> +static int test_policy(const struct net_device *dev, __u32 cmd, __u32 sub_cmd, __u32 phy_index)
> +{
> + __u32 pid;
> +
> + pid = bpf_get_current_pid_tgid() >> 32;
> + if (pid != monitored_pid)
> + return 0;
> +
> + if (!dev || (target_ifindex && dev->ifindex != target_ifindex))
> + return 0;
> +
> + if (target_cmd && cmd != target_cmd)
> + return 0;
> +
> + if (target_sub_cmd && sub_cmd != target_sub_cmd)
> + return 0;
> +
> + if (phy_index != target_phy_index)
> + return -EDOTDOT;
> +
> + return allow ? 0 : -EDOTDOT; /* unlikely to see this errno outside this test */
> +}
The comment on test_policy() says the helper 'just checks that it is
ours, and allows/denies based on the "allow" global variable', but the
function has a third behaviour the comment does not mention: the
phy_index test returns -EDOTDOT unconditionally, i.e. it denies
regardless of the value of allow.
That is not a cosmetic difference: it is the only place where the deny
errno is produced independently of allow, and it is precisely the
mechanism the phy_index sub-tests (cable_test_phy_index,
strset_get_phy_index_dump in prog_tests/ethtool_lsm.c) rely on - a
mismatched phy_index makes the allow phase return -EDOTDOT and
therefore fail ASSERT_NEQ(err, -TEST_ERRNO, "doit (allow)").
Note also that every other filter in the function bails out with
'return 0' (allow) when the request does not match, while this one
bails out with a denial, so the asymmetry is easy to misread as a
copy/paste slip.
A reader who trusts the comment will assume allow == true always means
'permit', and a later change to the phy_index handling can silently
turn the phy_index sub-tests into no-ops. Suggest documenting that the
phy_index comparison is an assertion that the hook argument was
propagated correctly (or gating it on target_phy_index like the other
filters and asserting the value from user space instead).
[ ... ]
> +/*
> + * CVE-2025-21701 is an example of a bug which must be mitigated under a lock,
> + * as access to dev->reg_state must be protected.
> + */
> +SEC("lsm/ethtool_netlink_doit")
> +int BPF_PROG(cve_2025_21701, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
> +{
> + if (ret)
> + return ret;
> +
> + if (!dev)
> + return 0;
> +
> + return dev->reg_state >= NETREG_UNREGISTERING ? -ENODEV : 0;
> +}
The example policy for CVE-2025-21701 installs only an
lsm/ethtool_netlink_doit program, but the hook-adding commit in this
same series (36bc9190590e4) classifies CVE-2025-21701 under 'more than
one hook required', in contrast to CVE-2024-46834 which is given both
an ioctl and a doit program a few lines below.
The upstream fix for that CVE (12e070eb6964b, 'net: avoid race between
device unregistration and ethnl ops') widened the test in
ethnl_ops_begin() to 'dev->reg_state >= NETREG_UNREGISTERING', and
ethnl_ops_begin() is reached from the dump path as well as from doit:
net/ethtool/netlink.c:ethnl_default_dump_one()
-> ctx->ops->prepare_data()
-> net/ethtool/channels.c:35 / net/ethtool/rss.c:158
-> ethnl_ops_begin()
A doit-only program therefore leaves the dump entry into the same
window unpoliced, so as an 'illustration of how an actual policy might
look' it is incomplete for the CVE it names. Either add the matching
lsm/ethtool_netlink_dump program (as done for cve_2024_46834_*), or
state in the comment that only the doit half is shown and why.
Secondary nit on the same line: '? -ENODEV' contains a doubled space
after the '?'.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
2026-08-31 11:50 ` bot+bpf-ci
@ 2026-08-31 12:48 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 12:48 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, linux-security-module, netdev, ast, daniel, andrii, eddyz87,
memxor, kpsingh, matt, john.fastabend, brauner, paul, torvalds,
edumazet, kuba, pabeni, martin.lau, yonghong.song, mason,
ihor.solodrai
On 26/08/31 11:50AM, bot+bpf-ci@kernel.org wrote:
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 460cb72688450..d01dd1f096fc1 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -5035,6 +5035,7 @@ L: bpf@vger.kernel.org
> > S: Maintained
> > F: Documentation/bpf/prog_lsm.rst
> > F: include/linux/bpf_lsm.h
> > +F: include/linux/bpf_lsm_hook_defs.h
> > F: kernel/bpf/bpf_lsm.c
> > F: kernel/bpf/bpf_lsm_proto.c
> > F: kernel/trace/bpf_trace.c
>
> [ ... ]
>
> > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > index dda272d78f013..1e54c7cca27ad 100644
> > --- a/include/linux/bpf_lsm.h
> > +++ b/include/linux/bpf_lsm.h
> > @@ -16,9 +16,19 @@
> >
> > extern bool bpf_lsm_initialized __ro_after_init;
> >
> > +/*
> > + * Technically, checking bpf_lsm_initialized is not necessary.
> > + * But if it is off, then this means that all security_* calls
> > + * do not call BPF, and it doesn't look reasonable to enable
> > + * only "non-LSM" bpf hooks...
> > + */
> > +#define bpf_lsm_hook(NAME, ...) \
> > + (bpf_lsm_initialized ? bpf_lsm_##NAME(__VA_ARGS__) : 0)
>
> Does the new bpf_lsm_hook() macro constrain what hook declarations
> can appear in bpf_lsm_hook_defs.h? The macro hardcodes 0 as the return
> value in two places:
>
> 1. When bpf_lsm_initialized is false, the ternary returns 0, but when
> it is true with no program attached, the __weak nop returns the hook's
> declared DEFAULT. This means LSM_HOOK(int, 0, ...) behaves consistently,
> but LSM_HOOK(int, -EOPNOTSUPP, ...) would return different values
> depending on whether 'bpf' appears in lsm= - 0 before initialization,
> -EOPNOTSUPP after.
>
> 2. Because of the ternary, LSM_HOOK(void, ...) cannot compile at call
> sites, since the expression `cond ? void_expr : 0` is invalid.
>
> 3. The CONFIG_BPF_LSM=n stub at line 128:
>
> #define bpf_lsm_hook(NAME, ...) 0
>
> discards its arguments entirely, so call-site argument checking only
> happens on =y builds. Does this create a risk that =n configurations
> would fail to catch type mismatches or side-effect issues?
Yes, it does (intentionally). For new hooks, which aim to be attach
points for BPF programs, there is no real reason to return anything,
but 0 or error. If they are called from a funciton returning a pointer,
the calling funciton should covert the return value appropriately.
(Also, new void hooks do not make sense.)
> > #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > RET bpf_lsm_##NAME(__VA_ARGS__);
> > #include <linux/lsm_hook_defs.h>
> > +#include <linux/bpf_lsm_hook_defs.h>
> > #undef LSM_HOOK
>
> [ ... ]
>
> > diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
> > new file mode 100644
> > index 0000000000000..29bc0b514d168
> > --- /dev/null
> > +++ b/include/linux/bpf_lsm_hook_defs.h
> > @@ -0,0 +1,6 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +
> > +/*
> > + * This is a set of BPF LSM hooks, which are _not_ fully implemented
> > + * as LSM hooks. Thus, they only can be used by BPF LSM programs.
> > + */
> > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > index 82c5988417a0c..add344ea26914 100644
> > --- a/kernel/bpf/bpf_lsm.c
> > +++ b/kernel/bpf/bpf_lsm.c
> > @@ -28,11 +28,13 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
> > }
> >
> > #include <linux/lsm_hook_defs.h>
> > +#include <linux/bpf_lsm_hook_defs.h>
> > #undef LSM_HOOK
> >
> > #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
> > BTF_SET_START(bpf_lsm_hooks)
> > #include <linux/lsm_hook_defs.h>
> > +#include <linux/bpf_lsm_hook_defs.h>
> > #undef LSM_HOOK
> > BTF_SET_END(bpf_lsm_hooks)
>
> The patch adds bpf_lsm_hook_defs.h to three of the four BPF-owned
> LSM_HOOK expansion sites, but not the fourth. Does this need updating?
>
> Updated:
> include/linux/bpf_lsm.h (prototypes, lines 30-31)
> kernel/bpf/bpf_lsm.c (__weak nop definitions, lines 30-31)
> kernel/bpf/bpf_lsm.c (bpf_lsm_hooks BTF set, lines 36-37)
>
> Not updated:
> kernel/bpf/bpf_lsm.c (void_lsm_hooks BTF set, further down in the file)
>
> The void_lsm_hooks set is consumed by bpf_lsm_hook_returns_errno() in
> bpf_lsm.c, which feeds cgroup_lsm_atype[i].returns_errno. If a hook
> declared as LSM_HOOK(void, ...) appeared in the new header, wouldn't it
> be omitted from void_lsm_hooks and incorrectly reported as errno-returning?
>
> This is latent today because the four hooks added later in the series all
> return int, but is there a reason the fourth site shouldn't include the
> new header, or should the header document that it only supports int hooks?
Yes, there is no intent to add new void hooks.
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 5/7] selftests/bpf: Add netdevsim helper library
2026-08-31 12:07 ` bot+bpf-ci
@ 2026-08-31 12:55 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 12:55 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, linux-security-module, netdev, ast, daniel, andrii, eddyz87,
memxor, kpsingh, matt, john.fastabend, brauner, paul, torvalds,
edumazet, kuba, pabeni, martin.lau, yonghong.song, mason,
ihor.solodrai
On 26/08/31 12:07PM, bot+bpf-ci@kernel.org wrote:
> > diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> > index b481b867f372e..c21f89dbe70c0 100644
> > --- a/tools/testing/selftests/bpf/Makefile
> > +++ b/tools/testing/selftests/bpf/Makefile
> > @@ -862,6 +862,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
> > unpriv_helpers.c \
> > sysctl_helpers.c \
> > netlink_helpers.c \
> > + netdevsim_helpers.c \
> > jit_disasm_helpers.c \
> > io_helpers.c \
> > test_loader.c \
> > diff --git a/tools/testing/selftests/bpf/netdevsim_helpers.c b/tools/testing/selftests/bpf/netdevsim_helpers.c
> > new file mode 100644
> > index 0000000000000..fbed7442b2e8d
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/netdevsim_helpers.c
> > @@ -0,0 +1,176 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <errno.h>
> > +#include <fcntl.h>
> > +#include <linux/rtnetlink.h>
> > +#include <poll.h>
> > +#include <stdarg.h>
> > +#include <stdio.h>
> > +#include <string.h>
> > +#include <sys/socket.h>
> > +#include <time.h>
> > +#include <unistd.h>
> > +
> > +#include "netdevsim_helpers.h"
> > +
> > +static int echo(const char *path, const char *fmt, ...)
> > +{
> > + char buf[64];
> > + va_list ap;
> > + int fd, len, err = 0;
> > +
> > + va_start(ap, fmt);
> > + len = vsnprintf(buf, sizeof(buf), fmt, ap);
> > + va_end(ap);
> > +
> > + fd = open(path, O_WRONLY);
> > + if (fd < 0)
> > + return -errno;
> > + if (write(fd, buf, len) != len)
> > + err = -errno;
> > + close(fd);
> > +
> > + return err;
> > +}
>
> A subsystem pattern flags this as potentially concerning: the guide
> directs reporting when a test hand-rolls sysfs/file I/O instead of using
> existing utilities that the subsystem already provides.
>
> Looking at echo(), it implements the open(O_WRONLY)/write()/close()
> sequence that write_sysctl() in test_progs.c already performs for BPF
> selftests. Could this reuse that existing helper?
Thanks robot, all comments here and below make sense.
I will simplify this in v2.
> > +
> > +void netdevsim_destroy(unsigned int id)
> > +{
> > + echo("/sys/bus/netdevsim/del_device", "%u", id);
> > +}
> > +
> > +static int create_new_device(void)
> > +{
> > + unsigned int id;
> > + int err;
> > +
> > + /* if 10K is not enough, then something is clearly not right */
> > + for (id = 0; id < 10000; id++) {
> > + err = echo("/sys/bus/netdevsim/new_device", "%u", id);
> > + if (!err)
> > + return id;
> > + if (err != -ENOSPC)
> > + return err;
> > + }
> > +
> > + return -ENOSPC;
> > +}
> > +
> > +static int open_link_socket(void)
> > +{
> > + struct sockaddr_nl addr = {
> > + .nl_family = AF_NETLINK,
> > + .nl_groups = RTMGRP_LINK,
> > + };
> > + int fd;
> > +
> > + fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
> > + if (fd < 0)
> > + return -errno;
> > + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) {
> > + int err = -errno;
> > +
> > + close(fd);
> > + return err;
> > + }
> > +
> > + return fd;
> > +}
>
> The subsystem guide also flags this pattern: open_link_socket() appears
> to duplicate rtnl_open(&rth, RTMGRP_LINK) from netlink_helpers.c, which
> does the same socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)
> plus bind() with nl_groups set to the subscriptions parameter.
>
> In particular, it looks structurally similar to genl_open() in
> netlink_helpers.c, which the immediately preceding commit in this series
> (a1205972510b7 "selftests/bpf: Extract some helpers from tests to the
> netlink library") added explicitly for sharing netlink plumbing.
>
> The re-implementation also appears to omit the SO_RCVBUF = 1024 * 1024
> setting that rtnl_open_byproto() sets in netlink_helpers.c, so the new
> socket runs on the default net.core.rmem_default receive buffer.
>
> Similarly, the receive/poll loop in recv_device_ifindex() appears to
> hand-roll what rtnl_recvmsg()/__rtnl_recvmsg() in netlink_helpers.c
> already do. Notably, those use MSG_PEEK | MSG_TRUNC to size the receive
> buffer, whereas the new loop uses a fixed 16 KiB buffer with a plain
> recv(fd, buf, sizeof(buf), 0) and no MSG_TRUNC check. Additionally,
> __rtnl_recvmsg() retries on EINTR/EAGAIN where the implementation below
> returns -errno.
>
> The agent notes that functionally these hand-rolled versions are correct
> for their specific use case, and this is selftest-only code. However,
> could the new helpers reuse the existing netlink infrastructure?
>
> > +
> > +static int remaining_timeout_ms(const struct timespec *deadline)
> > +{
> > + struct timespec now;
> > + long long remaining;
> > +
> > + if (clock_gettime(CLOCK_MONOTONIC, &now))
> > + return -errno;
> > +
> > + remaining = (deadline->tv_sec - now.tv_sec) * 1000 +
> > + (deadline->tv_nsec - now.tv_nsec) / 1000000;
> > +
> > + return remaining > 0 ? remaining : 0;
> > +}
>
> This isn't a bug, but since clock_gettime(CLOCK_MONOTONIC, ...) on a
> valid stack timespec can't really fail, would it be simpler to have
> remaining_timeout_ms() just return milliseconds? That way the timeout
> check at the end of recv_device_ifindex() (which currently needs
> `return timeout < 0 ? timeout : -ETIMEDOUT;` to forward the
> clock_gettime error) could be a plain `return -ETIMEDOUT;`.
>
> > +
> > +static int recv_device_ifindex(int fd, unsigned int id, unsigned int *ifindex)
> > +{
> > + char parent_name[32], buf[16 * 1024];
> > + struct pollfd pfd = {
> > + .fd = fd,
> > + .events = POLLIN,
> > + };
> > + struct timespec deadline;
> > + struct nlmsghdr *nlh;
> > + int len, ret, timeout;
> > +
> > + snprintf(parent_name, sizeof(parent_name), "netdevsim%u", id);
> > + if (clock_gettime(CLOCK_MONOTONIC, &deadline))
> > + return -errno;
> > + deadline.tv_sec += 5;
> > +
> > + for (timeout = remaining_timeout_ms(&deadline); timeout > 0;
> > + timeout = remaining_timeout_ms(&deadline)) {
> > + ret = poll(&pfd, 1, timeout);
> > + if (ret < 0) {
> > + if (errno == EINTR)
> > + continue;
> > + return -errno;
> > + }
> > + if (!ret)
> > + return -ETIMEDOUT;
> > + if (!(pfd.revents & POLLIN))
> > + return -EIO;
> > +
> > + len = recv(fd, buf, sizeof(buf), 0);
> > + if (len < 0)
> > + return -errno;
> > +
> > + for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
> > + nlh = NLMSG_NEXT(nlh, len)) {
> > + struct ifinfomsg *ifm;
> > + struct rtattr *attr;
> > + int attr_len;
> > +
> > + if (nlh->nlmsg_type != RTM_NEWLINK)
> > + continue;
> > +
> > + ifm = NLMSG_DATA(nlh);
> > + attr = IFLA_RTA(ifm);
> > + attr_len = IFLA_PAYLOAD(nlh);
> > + for (; RTA_OK(attr, attr_len);
> > + attr = RTA_NEXT(attr, attr_len)) {
> > + if (attr->rta_type != IFLA_PARENT_DEV_NAME)
> > + continue;
> > + if (strcmp(RTA_DATA(attr), parent_name))
> > + continue;
> > +
> > + *ifindex = ifm->ifi_index;
> > + return 0;
> > + }
> > + }
> > + }
> > +
> > + return timeout < 0 ? timeout : -ETIMEDOUT;
> > +}
> > +
> > +int netdevsim_create(unsigned int *ifindex)
> > +{
> > + int fd, id, err;
> > +
> > + fd = open_link_socket();
> > + if (fd < 0)
> > + return fd;
> > +
> > + id = create_new_device();
> > + if (id < 0) {
> > + close(fd);
> > + return id;
> > + }
> > +
> > + err = recv_device_ifindex(fd, id, ifindex);
> > + close(fd);
> > + if (err) {
> > + netdevsim_destroy(id);
> > + return err;
> > + }
> > +
> > + return id;
> > +}
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook
2026-08-31 12:07 ` bot+bpf-ci
@ 2026-08-31 13:01 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 13:01 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, linux-security-module, netdev, ast, daniel, andrii, eddyz87,
memxor, kpsingh, matt, john.fastabend, brauner, paul, torvalds,
edumazet, kuba, pabeni, martin.lau, yonghong.song, mason,
ihor.solodrai
On 26/08/31 12:07PM, bot+bpf-ci@kernel.org wrote:
> > 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
>
> The enumeration lists four subtests but the patch registers five. The
> "netns" subtest is missing from this list:
>
> 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);
>
> The netns subtest is the only one exercising the net argument of
> bpf_lsm_genl_family_rcv_msg(), and it introduces target_netns_inum plus
> a filter in the BPF program, netns_new()/open_netns(), a second socket,
> and the netns_inum() helper - roughly 45 of the 242 added lines. Since
> the netns filtering path is the least obvious part of the hook contract,
> would it make sense to add a fifth bullet, for example "* netns: deny a
> command only inside a target netns, allow it elsewhere"?
Sure, thanks, will add. It was added later than first four.
> > diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
> > index 08c6e8a4e..6ca0b083a 100644
> > --- a/tools/testing/selftests/bpf/config
> > +++ b/tools/testing/selftests/bpf/config
> > @@ -30,6 +30,7 @@ CONFIG_DYNAMIC_FTRACE=y
> > CONFIG_FPROBE=y
> > CONFIG_FUNCTION_TRACER=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 000000000..9f866c4c6
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/genl_lsm.c
>
> [ ... ]
>
> > +static void test_nlmsg_flags(struct genl_lsm *skel, int fd)
> > +{
> > + int err;
> > +
> > + /* ensure denying works */
> > + skel->bss->target_cmd = CTRL_CMD_GETFAMILY;
> > + skel->bss->target_flags = NLM_F_REQUEST | NLM_F_DUMP;
> > + skel->bss->allow = false;
> > +
> > + err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, false);
> > + ASSERT_OK(err, "doit_flags_not_matched");
> > +
> > + err = nlctrl_request(fd, CTRL_CMD_GETFAMILY, true);
> > + ASSERT_EQ(err, -TEST_ERRNO, "deny_dump_flags");
> > +
> > + /* 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");
> > +}
>
> The second half reuses the assertion tags from the first half, but they
> name the opposite case. The dump request gets "doit_flags_not_matched"
> and the doit request gets "deny_dump_flags". The test logic is correct
> but when test_progs prints only the tag and __func__, a CI failure
> reporting "genl_lsm:FAIL:deny_dump_flags" cannot be attributed to either
> of the two assertions with that name, and whichever one it is, the tag
> names the wrong netlink operation. Would four distinct tags - for example
> "deny_dump_flags_matched", "allow_doit_flags_unmatched",
> "allow_dump_flags_unmatched", "deny_doit_flags_matched" - remove the
> ambiguity?
Yes, thanks.
> [ ... ]
>
> > +void test_genl_lsm(void)
> > +{
> > + struct genl_lsm *skel = NULL;
> > + int fd = -1;
> > + int err, other_id;
> > +
> > + 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;
> > + }
>
> A subsystem pattern flags this as potentially concerning: when
> CONFIG_ETHTOOL_NETLINK is absent, genl_resolve_family() returns -ENOENT
> and the test skips with no printed reason. A reader of the TAP output
> sees "#N genl_lsm:SKIP" with nothing indicating the missing prerequisite
> was the ethtool genl family, so a silently-skipped test looks identical
> to a deliberately-disabled one. All five subtests are skipped this way,
> including the four (doit, dump, nlmsg_flags, netns) that do not need
> ethtool - only test_other_family() consumes other_id.
>
> This pattern is common in bpf selftests (e.g. aggregate_ret.c:52,
> arena_spin_lock.c:57 and :66 all do exactly this), and the config hunk
> in this same patch adds CONFIG_ETHTOOL_NETLINK so the skip path should
> not be taken in a properly configured CI run. Would a one-line printf
> plus optionally deferring the ethtool resolution into test_other_family()
> so the other four subtests still run be worth addressing?
In fact, this is probably better to omit this check.
I've tried to do the same for CONFIG_SECURITY=n, but then figured out
that selftests won't even build for this case (not due to this patch).
So given I add ETHOOL, it's ok to expect that it is on.
>
> [ ... ]
>
> > 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 000000000..c4d8b4d23
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/progs/genl_lsm.c
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks
2026-08-31 12:07 ` bot+bpf-ci
@ 2026-08-31 13:12 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 13:12 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, linux-security-module, netdev, ast, daniel, andrii, eddyz87,
memxor, kpsingh, matt, john.fastabend, brauner, paul, torvalds,
edumazet, kuba, pabeni, martin.lau, yonghong.song, mason,
ihor.solodrai
On 26/08/31 12:07PM, bot+bpf-ci@kernel.org wrote:
> > This is how the list off sub-tests maps to call sites:
> >
> > linkstate_get_doit: netlink.c (ethnl_default_doit)
> > linkstate_get_dump: netlink.c (ethnl_default_dump_one)
> > cable_test_act: cabletest.c (ethnl_act_cable_test)
> > cable_test_tdr_act: cabletest.c (ethnl_act_cable_test_tdr)
> > features_set: features.c (ethnl_set_features)
> > module_fw_flash_act: module.c (ethnl_act_module_fw_flash)
> > tunnel_info_get_doit: tunnels.c (ethnl_tunnel_info_doit)
> > tunnel_info_get_dump: tunnels.c (ethnl_tunnel_info_dumpit)
> > tsinfo_get_dump: tsinfo.c (ethnl_tsinfo_dump_one_netdev)
> > rss_get_dump: rss.c (ethnl_rss_dumpit)
> > channels_set_doit: netlink.c (ethnl_default_set_doit)
> > ioctl: ioctl.c (ethtool_bpf_ioctl_hook)
> > ioctl_sub_cmd: ioctl.c (ethtool_bpf_ioctl_hook)
> > no tests: rss.c (ethnl_rss_{create,delete}_doit)
>
> The changelog's sub-tests table does not match the sub-tests the patch
> adds. The code registers 15 sub-tests, including cable_test_phy_index
> (check_doit with TEST_PHY_INDEX, exercising the phy_index argument of
> the doit hook) and strset_get_phy_index_dump (check_dump with
> TEST_PHY_INDEX, exercising the phy_index argument of the dump hook),
> neither of which appears in the 13-row table.
>
> These are the only two sub-tests that cover the phy_index parameter at
> all, i.e. the least obvious part of the new hook ABI, so their absence
> from the mapping is the part a reviewer is most likely to want
> documented.
>
> Two smaller inconsistencies in the same block: the prose names
> ethnl_tsinfo_dump_one_phydev() as unreachable via netdevsim, but the
> 'no tests' row lists only rss.c (ethnl_rss_{create,delete}_doit); and
> 'the list off sub-tests' should read 'the list of sub-tests'.
Yes, thanks, I haven't updated the commit message after the last changes.
> > diff --git a/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
> > new file mode 100644
> > index 0000000000000..f4662fb1ae3ce
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/ethtool_lsm.c
> > @@ -0,0 +1,330 @@
>
> [ ... ]
>
> > +static int ethnl_request(int fd, __u16 family_id, __u8 cmd, __u16 hdr_attr,
> > + __u16 extra_nest, __u32 ifindex, __u32 phy_index, bool dump)
> > +{
> > + static __u32 sequence = 10;
> > + struct genl_req req = {};
> > + __u32 seq = sequence++;
> > + struct rtattr *nest;
> > + int err;
> > +
> > + req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
> > + req.nlh.nlmsg_type = family_id;
> > + req.nlh.nlmsg_flags = NLM_F_REQUEST | (dump ? NLM_F_DUMP : 0);
> > + req.nlh.nlmsg_seq = seq;
> > + req.genl.cmd = cmd;
> > + req.genl.version = ETHTOOL_GENL_VERSION;
> > +
> > + nest = addattr_nest(&req.nlh, sizeof(req), hdr_attr | NLA_F_NESTED);
> > + if (ifindex && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_DEV_INDEX, ifindex))
> > + return -EMSGSIZE;
> > + if (phy_index && addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_PHY_INDEX, phy_index))
> > + return -EMSGSIZE;
> > + if (addattr32(&req.nlh, sizeof(req), ETHTOOL_A_HEADER_FLAGS, ETHTOOL_FLAG_COMPACT_BITSETS))
> > + return -EMSGSIZE;
> > + addattr_nest_end(&req.nlh, nest);
> > +
> > + if (extra_nest) {
> > + nest = addattr_nest(&req.nlh, sizeof(req), extra_nest | NLA_F_NESTED);
> > + addattr_nest_end(&req.nlh, nest);
> > + }
> > +
> > + err = genl_send(fd, &req.nlh);
> > + if (err)
> > + return err;
> > +
> > + return genl_recv(fd, seq, family_id, dump);
> > +}
>
> ethnl_request() never sets NLM_F_ACK, so a doit request that succeeds
> without producing a reply message gets no netlink response at all:
> netlink_rcv_skb() only calls netlink_ack() when
> 'nlh->nlmsg_flags & NLM_F_ACK || err'.
>
> This is exactly what happens in the allow arm of the channels_set_doit
> subtest. ETHTOOL_MSG_CHANNELS_SET is dispatched through
> ethnl_default_set_doit(); ethnl_set_channels_validate() returns 1 on
> netdevsim (both get_channels and set_channels are implemented), so the
> hook is reached and the deny arm works, but the request carries only
> the header nest, so ethnl_set_channels() takes 'if (!mod) return 0;'
> and the SET handler produces neither a reply nor an error.
>
> genl_recv() then blocks until the SO_RCVTIMEO set in genl_open()
> (struct timeval { .tv_sec = 1 }) expires and returns -EAGAIN.
> Consequences: (a) 'if (!ASSERT_NEQ(err, -TEST_ERRNO, "doit (allow)"))'
> passes vacuously - -EAGAIN would also be returned if the kernel had
> wrongly dropped the request, so the allow arm of this subtest pins
> nothing; (b) the subtest silently costs a full second of wall time, and
> would grow with each additional SET-style subtest added later.
>
> Setting NLM_F_ACK in nlmsg_flags (rtnl_talk() in netlink_helpers.c
> does this itself when no answer is requested) makes the kernel ack the
> successful set, so the allow arm returns 0 immediately and actually
> distinguishes success from 'nothing came back'.
Yes, thanks.
> [ ... ]
>
> > diff --git a/tools/testing/selftests/bpf/progs/ethtool_lsm.c b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
> > new file mode 100644
> > index 0000000000000..c6b21aeed6fec
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/progs/ethtool_lsm.c
> > @@ -0,0 +1,168 @@
>
> [ ... ]
>
> > +/*
> > + * This test is used for all hooks, just checks that it is ours,
> > + * and allows/denies based on the "allow" global variable
> > + */
> > +static int test_policy(const struct net_device *dev, __u32 cmd, __u32 sub_cmd, __u32 phy_index)
> > +{
> > + __u32 pid;
> > +
> > + pid = bpf_get_current_pid_tgid() >> 32;
> > + if (pid != monitored_pid)
> > + return 0;
> > +
> > + if (!dev || (target_ifindex && dev->ifindex != target_ifindex))
> > + return 0;
> > +
> > + if (target_cmd && cmd != target_cmd)
> > + return 0;
> > +
> > + if (target_sub_cmd && sub_cmd != target_sub_cmd)
> > + return 0;
> > +
> > + if (phy_index != target_phy_index)
> > + return -EDOTDOT;
> > +
> > + return allow ? 0 : -EDOTDOT; /* unlikely to see this errno outside this test */
> > +}
>
> The comment on test_policy() says the helper 'just checks that it is
> ours, and allows/denies based on the "allow" global variable', but the
> function has a third behaviour the comment does not mention: the
> phy_index test returns -EDOTDOT unconditionally, i.e. it denies
> regardless of the value of allow.
>
> That is not a cosmetic difference: it is the only place where the deny
> errno is produced independently of allow, and it is precisely the
> mechanism the phy_index sub-tests (cable_test_phy_index,
> strset_get_phy_index_dump in prog_tests/ethtool_lsm.c) rely on - a
> mismatched phy_index makes the allow phase return -EDOTDOT and
> therefore fail ASSERT_NEQ(err, -TEST_ERRNO, "doit (allow)").
>
> Note also that every other filter in the function bails out with
> 'return 0' (allow) when the request does not match, while this one
> bails out with a denial, so the asymmetry is easy to misread as a
> copy/paste slip.
>
> A reader who trusts the comment will assume allow == true always means
> 'permit', and a later change to the phy_index handling can silently
> turn the phy_index sub-tests into no-ops. Suggest documenting that the
> phy_index comparison is an assertion that the hook argument was
> propagated correctly (or gating it on target_phy_index like the other
> filters and asserting the value from user space instead).
Yes, thanks. I haven't updated the comment.
I will see what looks better, to refactor code or comment.
> [ ... ]
>
> > +/*
> > + * CVE-2025-21701 is an example of a bug which must be mitigated under a lock,
> > + * as access to dev->reg_state must be protected.
> > + */
> > +SEC("lsm/ethtool_netlink_doit")
> > +int BPF_PROG(cve_2025_21701, const struct net_device *dev, __u32 cmd, __u32 phy_index, int ret)
> > +{
> > + if (ret)
> > + return ret;
> > +
> > + if (!dev)
> > + return 0;
> > +
> > + return dev->reg_state >= NETREG_UNREGISTERING ? -ENODEV : 0;
> > +}
>
> The example policy for CVE-2025-21701 installs only an
> lsm/ethtool_netlink_doit program, but the hook-adding commit in this
> same series (36bc9190590e4) classifies CVE-2025-21701 under 'more than
> one hook required', in contrast to CVE-2024-46834 which is given both
> an ioctl and a doit program a few lines below.
>
> The upstream fix for that CVE (12e070eb6964b, 'net: avoid race between
> device unregistration and ethnl ops') widened the test in
> ethnl_ops_begin() to 'dev->reg_state >= NETREG_UNREGISTERING', and
> ethnl_ops_begin() is reached from the dump path as well as from doit:
>
> net/ethtool/netlink.c:ethnl_default_dump_one()
> -> ctx->ops->prepare_data()
> -> net/ethtool/channels.c:35 / net/ethtool/rss.c:158
> -> ethnl_ops_begin()
>
> A doit-only program therefore leaves the dump entry into the same
> window unpoliced, so as an 'illustration of how an actual policy might
> look' it is incomplete for the CVE it names. Either add the matching
> lsm/ethtool_netlink_dump program (as done for cve_2024_46834_*), or
> state in the comment that only the doit half is shown and why.
The actual CVE wasn't reached in .dump, only .doit. However, the commit
you've listed makes sense, as there can be similar CVEs reachable via both paths.
> Secondary nit on the same line: '? -ENODEV' contains a doubled space
> after the '?'.
yes thanks
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 2/7] net, bpf: Add a generic netlink hook on msg_rcv
2026-08-31 12:07 ` bot+bpf-ci
@ 2026-08-31 13:22 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-08-31 13:22 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, linux-security-module, netdev, ast, daniel, andrii, eddyz87,
memxor, kpsingh, matt, john.fastabend, brauner, paul, torvalds,
edumazet, kuba, pabeni, martin.lau, yonghong.song, mason,
ihor.solodrai
On 26/08/31 12:07PM, bot+bpf-ci@kernel.org wrote:
> > commit 18faf289d6ccb694a2ce1ae68f5c3cf881de2fd4
> > Author: Anton Protopopov <a.s.protopopov@gmail.com>
> >
> > net, bpf: Add a generic netlink hook on msg_rcv
> >
> > Add a new bpf_lsm_genl_family_rcv_msg() BPF hook called from within
> > the genl_family_rcv_msg() function. The hook sees the genl family,
> > net, cmd, and nlmsg_flags. As the hook receives all the information
> > pre-parsed, it is not put into the sleepable list.
> >
> > There is already an existing LSM hook sitting right at netlink_send,
> > however, it looks to be too coarse-grained to be used directly. The
> > netlink_send hook sees the struct sock and struct skb, and thus must
> > duplicate all the parsing inside BPF. The messages can actually contain
> > multiple netlink datagrams, dynamic info (see e.g., selinux_nlmsg_lookup()
> > in context of generic netlink), etc.
> >
> > The purpose of this hook is to mitigate, in a coarse-grained way,
> > CVEs reachable via the corresponding control path: genl recvmsg.
> > While looking at the recent CVEs, robots found ~300 CVEs reachable
> > via the genl_rcv. We can roughly split them as follows:
> >
> > * Around 100 are ethtool-related. For more detailed information,
> > see the following commit which adds even more finer-grained
> > ethtool hooks.
> >
> > * Around 100 of the remaining are fully dominated by the new hook.
> >
> > * Around 100 remaining aren't fully mitigatable, as other control
> > paths, besides the genl, lead to the buggy code.
> >
> > To be more precise, the following CVEs are properly mitigated by the
> > new hook:
> >
> > * CVE-2021-47518 CVE-2021-47594 CVE-2021-47612 CVE-2022-48687
> > CVE-2022-49060 CVE-2022-49085 CVE-2022-49300 CVE-2022-49862
> > CVE-2022-49959 CVE-2022-50656 CVE-2022-50834 CVE-2022-50854
> > CVE-2023-52698 CVE-2023-52702 CVE-2023-52977 CVE-2023-53113
> > CVE-2023-53298 CVE-2023-53686 CVE-2023-53843 CVE-2024-26608
> > CVE-2024-26663 CVE-2024-26754 CVE-2024-26811 CVE-2024-26950
> > CVE-2024-27025 CVE-2024-27410 CVE-2024-36941 CVE-2024-38562
> > CVE-2024-43912 CVE-2024-45010 CVE-2024-46711 CVE-2024-49937
> > CVE-2024-50085 CVE-2025-21706 CVE-2025-21787 CVE-2025-21862
> > CVE-2025-21875 CVE-2025-21947 CVE-2025-22024 CVE-2025-38184
> > CVE-2025-38443 CVE-2025-40084 CVE-2025-68263 CVE-2025-68366
> > CVE-2026-23297 CVE-2026-23436 CVE-2026-43252 CVE-2026-43394
> > CVE-2026-53128 CVE-2026-53227 CVE-2026-53233 CVE-2026-53238
> > CVE-2026-53257
> >
> > The following are mitigated as well, but, theoretically, could
> > benefit from a finer-grained hook, running after a deeper
> > parsing/taking locks (this is however out of scope for now to
> > add finer-grained hooks for each involved subsystems, as, unlike
> > ethtool, they are more-or-less evenly distributed):
> >
> > * CVE-2021-47250 CVE-2021-47257 CVE-2021-47554 CVE-2022-48915
> > CVE-2022-49374 CVE-2022-49505 CVE-2022-50042 CVE-2022-50678
> > CVE-2023-52845 CVE-2023-53141 CVE-2023-53543 CVE-2023-53652
> > CVE-2023-54031 CVE-2024-26725 CVE-2024-26951 CVE-2024-42073
> > CVE-2024-42154 CVE-2024-46676 CVE-2024-47687 CVE-2024-50028
> > CVE-2025-21973 CVE-2025-38020 CVE-2025-38628 CVE-2025-40254
> > CVE-2025-40321 CVE-2025-68785 CVE-2025-71091 CVE-2025-71297
> > CVE-2026-23321 CVE-2026-31707 CVE-2026-31744 CVE-2026-43012
> > CVE-2026-43467 CVE-2026-43481 CVE-2026-45840 CVE-2026-52978
> > CVE-2026-52979 CVE-2023-53570 CVE-2024-42114 CVE-2024-53189
> > CVE-2024-56663 CVE-2025-21909 CVE-2026-53182
> >
> > Policies for selected CVEs are added in the corresponding
> > selftests patch.
> >
> > Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
>
> This isn't a bug, but would it be worth trimming the two CVE enumerations
> or pointing at where the classification lives so the coarse-grained-vs-
> netlink_send rationale is not outnumbered roughly 3:1 by bare IDs?
>
> The selftests patch is already referenced for the policies. The tail of
> the second CVE list also breaks the ascending order it starts with
> (CVE-2026-52979 is followed by CVE-2023-53570).
Yeah, thanks, I will consider this.
>
> > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > index 1e54c7cca27ad..452b9d1d7c494 100644
> > --- a/include/linux/bpf_lsm.h
> > +++ b/include/linux/bpf_lsm.h
> > @@ -12,6 +12,8 @@
> > #include <linux/bpf_verifier.h>
> > #include <linux/lsm_hooks.h>
> >
> > +struct genl_family; /* for the bpf_lsm_genl_family_rcv_msg hook */
> > +
> > #ifdef CONFIG_BPF_LSM
> >
> > extern bool bpf_lsm_initialized __ro_after_init;
> > diff --git a/include/linux/bpf_lsm_hook_defs.h b/include/linux/bpf_lsm_hook_defs.h
> > index 29bc0b514d168..3f57022744a31 100644
> > --- a/include/linux/bpf_lsm_hook_defs.h
> > +++ b/include/linux/bpf_lsm_hook_defs.h
> > @@ -4,3 +4,10 @@
> > * This is a set of BPF LSM hooks, which are _not_ fully implemented
> > * as LSM hooks. Thus, they only can be used by BPF LSM programs.
> > */
> > +
> > +#ifdef CONFIG_NET
> > +
> > +LSM_HOOK(int, 0, genl_family_rcv_msg, const struct genl_family *family,
> > + const struct net *net, u32 cmd, u16 nlmsg_flags)
> > +
> > +#endif /* CONFIG_NET */
> > diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
> > index 0da39eaed255f..12b4f49c72534 100644
> > --- a/net/netlink/genetlink.c
> > +++ b/net/netlink/genetlink.c
> > @@ -17,6 +17,7 @@
> > #include <linux/skbuff.h>
> > #include <linux/mutex.h>
> > #include <linux/bitmap.h>
> > +#include <linux/bpf_lsm.h>
> > #include <linux/rwsem.h>
> > #include <linux/idr.h>
> > #include <net/sock.h>
> > @@ -1161,6 +1162,7 @@ static int genl_family_rcv_msg(const struct genl_family *family,
> > struct genlmsghdr *hdr = nlmsg_data(nlh);
> > struct genl_split_ops op;
> > int hdrlen;
> > + int err;
> > u8 flags;
>
> This isn't a bug, but would int err sit better after u8 flags so the
> declaration block stays longest-to-shortest, per the reverse xmas tree
> ordering in Documentation/process/maintainer-netdev.rst?
yes
>
> [ ... ]
>
> > @@ -1187,6 +1189,10 @@ static int genl_family_rcv_msg(const struct genl_family *family,
> > !netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
> > return -EPERM;
> >
> > + err = bpf_lsm_hook(genl_family_rcv_msg, family, net, hdr->cmd, nlh->nlmsg_flags);
> > + if (err)
> > + return err;
> > +
>
> This isn't a bug, but could the bpf_lsm_hook() call be wrapped the way
> the genl_family_rcv_msg_dumpit() / _doit() calls just below it are, so
> it stays under 80 columns like the rest of the file?
ok
>
> > if (flags & GENL_CMD_CAP_DUMP)
> > return genl_family_rcv_msg_dumpit(family, skb, nlh, extack,
> > &op, hdrlen, net);
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33386073074
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (6 preceding siblings ...)
2026-08-31 11:09 ` [PATCH bpf-next 7/7] selftests/bpf: Add tests for BPF ethtool hooks Anton Protopopov
@ 2026-08-31 22:34 ` Jakub Kicinski
2026-09-01 12:29 ` Anton Protopopov
2026-09-02 18:07 ` Alexei Starovoitov
8 siblings, 1 reply; 31+ messages in thread
From: Jakub Kicinski @ 2026-08-31 22:34 UTC (permalink / raw)
To: Anton Protopopov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Paolo Abeni
On Mon, 31 Aug 2026 11:09:25 +0000 Anton Protopopov wrote:
> The BPF LSM programs are allowed to attach to LSM hooks. This enables
> operators to mitigate known bugs without a need to reboot or livepatch
> machines. BPF has shown very useful to create such runtime policies.
> However, many APIs and parts of kernel aren't covered by existing LSM
> hooks and this would be beneficial to extend the coverage.
Dunno. Do you have any reason to believe that any of the CVEs your LLM
gathered for you here are actually getting exploited? Spot checking
a few they seem to be mostly driver bugs. What security model do you
have in mind? Untrusted/malicious users with physical NIC access?
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
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 22:42 ` Paul Moore
2026-09-01 13:36 ` Anton Protopopov
1 sibling, 1 reply; 31+ messages in thread
From: Paul Moore @ 2026-08-31 22:42 UTC (permalink / raw)
To: Anton Protopopov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Linus Torvalds, Eric Dumazet, Jakub Kicinski, Paolo Abeni
On Mon, Aug 31, 2026 at 6:59 AM Anton Protopopov
<a.s.protopopov@gmail.com> wrote:
>
> The BPF LSM programs are allowed to attach to LSM hooks, all of which
> are defined in the <lsm_hook_defs.h> header file. From BPF's point
> of view the set of attachment points is defined in the bpf_lsm_hooks
> BTF set. By analogy with existing code, add a new header file
> <bpf_lsm_hook_defs.h> which will also be included in the bpf_lsm_hooks
> BTF set.
>
> This change allows attaching BPF LSM programs to more functions.
> The actual hooks are added in subsequent commits.
>
> Each BPF hook calls a [__weak] noinline function each time a hook is
> reached. This may be too expensive for hot paths if a BPF program is
> not attached. A future commit will optimize this by adding a per-hook
> static key and inc/dec it on attach/detach. This way disabled hooks
> will be bypassed efficiently.
>
> Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
> ---
> MAINTAINERS | 1 +
> include/linux/bpf_lsm.h | 12 ++++++++++++
> include/linux/bpf_lsm_hook_defs.h | 6 ++++++
> kernel/bpf/bpf_lsm.c | 2 ++
> 4 files changed, 21 insertions(+)
> create mode 100644 include/linux/bpf_lsm_hook_defs.h
Adding new BPF hooks in the kernel is one thing, but adding new BPF
LSM hooks outside of the LSM framework is likely to be problematic as
these new hooks operate disconnected from the LSM framework (callback
and LSM kernel object state management). We've seen bugs in the past
caused by the BPF LSM trying to operate independently of the LSM
framework, something like this will only make that worse.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
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
0 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-09-01 12:29 UTC (permalink / raw)
To: Jakub Kicinski
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Paolo Abeni
On 26/08/31 03:34PM, Jakub Kicinski wrote:
> On Mon, 31 Aug 2026 11:09:25 +0000 Anton Protopopov wrote:
> > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > operators to mitigate known bugs without a need to reboot or livepatch
> > machines. BPF has shown very useful to create such runtime policies.
> > However, many APIs and parts of kernel aren't covered by existing LSM
> > hooks and this would be beneficial to extend the coverage.
>
> Dunno. Do you have any reason to believe that any of the CVEs your LLM
> gathered for you here are actually getting exploited? Spot checking
> a few they seem to be mostly driver bugs. What security model do you
> have in mind? Untrusted/malicious users with physical NIC access?
For the untrusted part, here are some existing examples:
* old untrusted bugs: CVE-2022-50651, CVE-2025-40255
* "namespace CAP_NET_ADMIN" bugs: CVE-2024-43836, CVE-2025-21921
Also, the recent copy-fail is a stronger example (though not generic
netlink-related).
The general idea is that we gate the common de-multiplexors such that
not only known bugs, but mainly those which will appear in future are
covered. Rough numbers for coverage: around 5% of known cves are
covered with existing LSM hooks. Another ~5-7% can be covered if we
add netlink-related hooks [this series + net/sched, nftables,
rtnetlink, others smaller]. So, statistically, we know where
bugs had appeared in the past, so we can "predict" where new
will appear. Some of them might be severe, so this would be
good to have hooks in place to be able to "mitigate" them.
Just in case, to test this patch locally, I've found around 10 new
ethtool-related bug candidates. I've sent a fix to one, d09c98a6da21
("virtio_net: Fix resize of the RX ring"), which was easy to
reproduce in a VM. Others require specific hardware, though popular,
so I can try to reproduce some, and was planning to do this later.
Of those findings one is unprivileged, it triggers a OOB read.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
2026-08-31 22:42 ` Paul Moore
@ 2026-09-01 13:36 ` Anton Protopopov
2026-09-01 22:15 ` Paul Moore
0 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-09-01 13:36 UTC (permalink / raw)
To: Paul Moore
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Linus Torvalds, Eric Dumazet, Jakub Kicinski, Paolo Abeni
On 26/08/31 06:42PM, Paul Moore wrote:
> On Mon, Aug 31, 2026 at 6:59 AM Anton Protopopov
> <a.s.protopopov@gmail.com> wrote:
> >
> > The BPF LSM programs are allowed to attach to LSM hooks, all of which
> > are defined in the <lsm_hook_defs.h> header file. From BPF's point
> > of view the set of attachment points is defined in the bpf_lsm_hooks
> > BTF set. By analogy with existing code, add a new header file
> > <bpf_lsm_hook_defs.h> which will also be included in the bpf_lsm_hooks
> > BTF set.
> >
> > This change allows attaching BPF LSM programs to more functions.
> > The actual hooks are added in subsequent commits.
> >
> > Each BPF hook calls a [__weak] noinline function each time a hook is
> > reached. This may be too expensive for hot paths if a BPF program is
> > not attached. A future commit will optimize this by adding a per-hook
> > static key and inc/dec it on attach/detach. This way disabled hooks
> > will be bypassed efficiently.
> >
> > Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
> > ---
> > MAINTAINERS | 1 +
> > include/linux/bpf_lsm.h | 12 ++++++++++++
> > include/linux/bpf_lsm_hook_defs.h | 6 ++++++
> > kernel/bpf/bpf_lsm.c | 2 ++
> > 4 files changed, 21 insertions(+)
> > create mode 100644 include/linux/bpf_lsm_hook_defs.h
>
> Adding new BPF hooks in the kernel is one thing, but adding new BPF
> LSM hooks outside of the LSM framework is likely to be problematic as
> these new hooks operate disconnected from the LSM framework (callback
> and LSM kernel object state management). We've seen bugs in the past
> caused by the BPF LSM trying to operate independently of the LSM
> framework, something like this will only make that worse.
Could you please point me to some of the bugs you mention, such that
I understand exactly what you mean? I am not really seeing how the
real LSM hooks differ from the ones added here (from BPF point of
view, and the objects it can access via kfuncs/maps). We provide the
same "sleepable", "untrusted", etc. guarantees with the new hooks,
as for normal ones.
The main reason (for now) to specifically create a new list of BPF-only LSM
hooks is (pcmoore/lsm.git/tree/README.md):
"""New LSM hooks must demonstrate their usefulness by providing a meaningful
implementation for at least one in-kernel LSM. The goal is to demonstrate the
purpose and expected semantics of the hooks. Out of tree kernel code, and pass
through implementations, such as the BPF LSM, are not eligible for LSM hook
reference implementations."""
And for the hooks added in this series
a) BPF satisfies our needs, as we can precisely analyse what the
calls are trying to do with good granularity
b) I am not sure how to actually express this in any in-tree LSMs
Can't BPF be considered enough to demonstrate usefulness?
>
> --
> paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
2026-09-01 13:36 ` Anton Protopopov
@ 2026-09-01 22:15 ` Paul Moore
2026-09-02 15:31 ` Anton Protopopov
0 siblings, 1 reply; 31+ messages in thread
From: Paul Moore @ 2026-09-01 22:15 UTC (permalink / raw)
To: Anton Protopopov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Linus Torvalds, Eric Dumazet, Jakub Kicinski, Paolo Abeni
On Tue, Sep 1, 2026 at 9:26 AM Anton Protopopov
<a.s.protopopov@gmail.com> wrote:
> On 26/08/31 06:42PM, Paul Moore wrote:
> > On Mon, Aug 31, 2026 at 6:59 AM Anton Protopopov
> > <a.s.protopopov@gmail.com> wrote:
> > >
> > > The BPF LSM programs are allowed to attach to LSM hooks, all of which
> > > are defined in the <lsm_hook_defs.h> header file. From BPF's point
> > > of view the set of attachment points is defined in the bpf_lsm_hooks
> > > BTF set. By analogy with existing code, add a new header file
> > > <bpf_lsm_hook_defs.h> which will also be included in the bpf_lsm_hooks
> > > BTF set.
> > >
> > > This change allows attaching BPF LSM programs to more functions.
> > > The actual hooks are added in subsequent commits.
> > >
> > > Each BPF hook calls a [__weak] noinline function each time a hook is
> > > reached. This may be too expensive for hot paths if a BPF program is
> > > not attached. A future commit will optimize this by adding a per-hook
> > > static key and inc/dec it on attach/detach. This way disabled hooks
> > > will be bypassed efficiently.
> > >
> > > Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
> > > ---
> > > MAINTAINERS | 1 +
> > > include/linux/bpf_lsm.h | 12 ++++++++++++
> > > include/linux/bpf_lsm_hook_defs.h | 6 ++++++
> > > kernel/bpf/bpf_lsm.c | 2 ++
> > > 4 files changed, 21 insertions(+)
> > > create mode 100644 include/linux/bpf_lsm_hook_defs.h
> >
> > Adding new BPF hooks in the kernel is one thing, but adding new BPF
> > LSM hooks outside of the LSM framework is likely to be problematic as
> > these new hooks operate disconnected from the LSM framework (callback
> > and LSM kernel object state management). We've seen bugs in the past
> > caused by the BPF LSM trying to operate independently of the LSM
> > framework, something like this will only make that worse.
>
> Could you please point me to some of the bugs you mention, such that
> I understand exactly what you mean?
The latest that I'm aware of was a few months ago:
https://lore.kernel.org/bpf/20260628201103.3624525-1-mattbobrowski@google.com
> I am not really seeing how the
> real LSM hooks differ from the ones added here (from BPF point of
> view, and the objects it can access via kfuncs/maps).
The existing LSM hooks, LSM security blobs (the 'void *security'
fields present in many kernel objects), and individual LSM callbacks
are managed by the LSM framework whereas the functions you are
proposing are not. This is important because the LSM framework
enables/disables individual LSMs at boot time which impacts both the
executed LSM callbacks and the security blob allocations.
Implementing "hooks" that operate outside the LSM framework can cause
unexpected user behavior and potentially destabilize the kernel,
leading to a system crash.
We require all LSMs (Landlock, Smack, AppArmor, SELinux, etc.) to go
through the LSM framework, the BPF LSM is no exception to this rule.
If you want to add additional BPF program call sites beyond what the
LSM framework provides, please implement them outside of the BPF LSM;
there is plenty of precendence for that already.
> The main reason (for now) to specifically create a new list of BPF-only LSM
> hooks is (pcmoore/lsm.git/tree/README.md):
>
> """New LSM hooks must demonstrate their usefulness by providing a meaningful
> implementation for at least one in-kernel LSM. The goal is to demonstrate the
> purpose and expected semantics of the hooks. Out of tree kernel code, and pass
> through implementations, such as the BPF LSM, are not eligible for LSM hook
> reference implementations."""
>
> And for the hooks added in this series
>
> a) BPF satisfies our needs, as we can precisely analyse what the
> calls are trying to do with good granularity
>
> b) I am not sure how to actually express this in any in-tree LSMs
>
> Can't BPF be considered enough to demonstrate usefulness?
The core issue has little to do with BPF, it has everything to do with
in-tree vs out-of-tree code. The BPF LSM is explicitly mentioned as a
"pass through" because some have argued that the BPF LSM is in-tree,
and while the basic BPF enablement is in-tree, the actual LSM code
that is executed has thus far always been out-of-tree.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-09-01 12:29 ` Anton Protopopov
@ 2026-09-02 0:49 ` Jakub Kicinski
2026-09-02 15:11 ` Anton Protopopov
0 siblings, 1 reply; 31+ messages in thread
From: Jakub Kicinski @ 2026-09-02 0:49 UTC (permalink / raw)
To: Anton Protopopov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Paolo Abeni
On Tue, 1 Sep 2026 12:29:15 +0000 Anton Protopopov wrote:
> On 26/08/31 03:34PM, Jakub Kicinski wrote:
> > On Mon, 31 Aug 2026 11:09:25 +0000 Anton Protopopov wrote:
> > > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > > operators to mitigate known bugs without a need to reboot or livepatch
> > > machines. BPF has shown very useful to create such runtime policies.
> > > However, many APIs and parts of kernel aren't covered by existing LSM
> > > hooks and this would be beneficial to extend the coverage.
> >
> > Dunno. Do you have any reason to believe that any of the CVEs your LLM
> > gathered for you here are actually getting exploited? Spot checking
> > a few they seem to be mostly driver bugs. What security model do you
> > have in mind? Untrusted/malicious users with physical NIC access?
>
> For the untrusted part, here are some existing examples:
>
> * old untrusted bugs: CVE-2022-50651, CVE-2025-40255
> * "namespace CAP_NET_ADMIN" bugs: CVE-2024-43836, CVE-2025-21921
These span 4 years.
> Also, the recent copy-fail is a stronger example (though not generic
> netlink-related).
Yes, if anything it proves that serious security issues are usually
on the datapath for networking, not what you're covering.
> The general idea is that we gate the common de-multiplexors such that
> not only known bugs, but mainly those which will appear in future are
> covered. Rough numbers for coverage: around 5% of known cves are
> covered with existing LSM hooks. Another ~5-7% can be covered if we
> add netlink-related hooks [this series + net/sched, nftables,
> rtnetlink, others smaller]. So, statistically, we know where
> bugs had appeared in the past, so we can "predict" where new
> will appear. Some of them might be severe, so this would be
> good to have hooks in place to be able to "mitigate" them.
Easy to PoC stuff with LLMs these days. I'd like to hear from
an end user who would find these hooks useful, in more detail.
Anyone with an ounce of gray matter will run untrusted workloads
_at least_ in a VM today.
> Just in case, to test this patch locally, I've found around 10 new
> ethtool-related bug candidates. I've sent a fix to one, d09c98a6da21
> ("virtio_net: Fix resize of the RX ring"), which was easy to
> reproduce in a VM. Others require specific hardware, though popular,
> so I can try to reproduce some, and was planning to do this later.
> Of those findings one is unprivileged, it triggers a OOB read.
Please send the fixes along. The hash you mention requires
CAP_NET_ADMIN on a real interface, not a serious security
issue.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-09-02 0:49 ` Jakub Kicinski
@ 2026-09-02 15:11 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-09-02 15:11 UTC (permalink / raw)
To: Jakub Kicinski
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Paolo Abeni,
David Fernandez Gonzalez
On 26/09/01 05:49PM, Jakub Kicinski wrote:
> On Tue, 1 Sep 2026 12:29:15 +0000 Anton Protopopov wrote:
> > On 26/08/31 03:34PM, Jakub Kicinski wrote:
> > > On Mon, 31 Aug 2026 11:09:25 +0000 Anton Protopopov wrote:
> > > > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > > > operators to mitigate known bugs without a need to reboot or livepatch
> > > > machines. BPF has shown very useful to create such runtime policies.
> > > > However, many APIs and parts of kernel aren't covered by existing LSM
> > > > hooks and this would be beneficial to extend the coverage.
> > >
> > > Dunno. Do you have any reason to believe that any of the CVEs your LLM
> > > gathered for you here are actually getting exploited? Spot checking
> > > a few they seem to be mostly driver bugs. What security model do you
> > > have in mind? Untrusted/malicious users with physical NIC access?
> >
> > For the untrusted part, here are some existing examples:
> >
> > * old untrusted bugs: CVE-2022-50651, CVE-2025-40255
> > * "namespace CAP_NET_ADMIN" bugs: CVE-2024-43836, CVE-2025-21921
>
> These span 4 years.
>
> > Also, the recent copy-fail is a stronger example (though not generic
> > netlink-related).
>
> Yes, if anything it proves that serious security issues are usually
> on the datapath for networking, not what you're covering.
I've skimmed through the kernelCTF list. The one ethtool-related CVE
which is addressed by ethtool hooks in this series is CVE-2025-21701
The more prominent producers there are net/sched (CVE-2026-23074,
14 bugs from 2025), nftables (CVE-2026-23111, CVE-2026-23231,
CVE-2026-23272, CVE-2026-23278, CVE-2026-23351, CVE-2026-23392),
rtnetlink (CVE-2026-23209). So bugs do occur in netlink-related paths,
and are used to capture flags.
The generic netlink was chosen as the first one, as the patch is only 10
lines long, but the hook would allow to block ~200 [historical] bugs.
End users might find the new hooks useful when they encounter
new bugs on their systems, which will be blockable by these hooks.
But to do this, hooks should be present before bugs occur.
> > The general idea is that we gate the common de-multiplexors such that
> > not only known bugs, but mainly those which will appear in future are
> > covered. Rough numbers for coverage: around 5% of known cves are
> > covered with existing LSM hooks. Another ~5-7% can be covered if we
> > add netlink-related hooks [this series + net/sched, nftables,
> > rtnetlink, others smaller]. So, statistically, we know where
> > bugs had appeared in the past, so we can "predict" where new
> > will appear. Some of them might be severe, so this would be
> > good to have hooks in place to be able to "mitigate" them.
>
> Easy to PoC stuff with LLMs these days. I'd like to hear from
> an end user who would find these hooks useful, in more detail.
> Anyone with an ounce of gray matter will run untrusted workloads
> _at least_ in a VM today.
>
> > Just in case, to test this patch locally, I've found around 10 new
> > ethtool-related bug candidates. I've sent a fix to one, d09c98a6da21
> > ("virtio_net: Fix resize of the RX ring"), which was easy to
> > reproduce in a VM. Others require specific hardware, though popular,
> > so I can try to reproduce some, and was planning to do this later.
> > Of those findings one is unprivileged, it triggers a OOB read.
>
> Please send the fixes along. The hash you mention requires
> CAP_NET_ADMIN on a real interface, not a serious security
> issue.
I wasn't saying it was. (It was just fun to test the patch with
a bug which wasn't already fixed/published.)
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
2026-09-01 22:15 ` Paul Moore
@ 2026-09-02 15:31 ` Anton Protopopov
2026-09-02 19:43 ` Paul Moore
0 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-09-02 15:31 UTC (permalink / raw)
To: Paul Moore
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Linus Torvalds, Eric Dumazet, Jakub Kicinski, Paolo Abeni
On 26/09/01 06:15PM, Paul Moore wrote:
> On Tue, Sep 1, 2026 at 9:26 AM Anton Protopopov
> <a.s.protopopov@gmail.com> wrote:
> > On 26/08/31 06:42PM, Paul Moore wrote:
> > > On Mon, Aug 31, 2026 at 6:59 AM Anton Protopopov
> > > <a.s.protopopov@gmail.com> wrote:
> > > >
> > > > The BPF LSM programs are allowed to attach to LSM hooks, all of which
> > > > are defined in the <lsm_hook_defs.h> header file. From BPF's point
> > > > of view the set of attachment points is defined in the bpf_lsm_hooks
> > > > BTF set. By analogy with existing code, add a new header file
> > > > <bpf_lsm_hook_defs.h> which will also be included in the bpf_lsm_hooks
> > > > BTF set.
> > > >
> > > > This change allows attaching BPF LSM programs to more functions.
> > > > The actual hooks are added in subsequent commits.
> > > >
> > > > Each BPF hook calls a [__weak] noinline function each time a hook is
> > > > reached. This may be too expensive for hot paths if a BPF program is
> > > > not attached. A future commit will optimize this by adding a per-hook
> > > > static key and inc/dec it on attach/detach. This way disabled hooks
> > > > will be bypassed efficiently.
> > > >
> > > > Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
> > > > ---
> > > > MAINTAINERS | 1 +
> > > > include/linux/bpf_lsm.h | 12 ++++++++++++
> > > > include/linux/bpf_lsm_hook_defs.h | 6 ++++++
> > > > kernel/bpf/bpf_lsm.c | 2 ++
> > > > 4 files changed, 21 insertions(+)
> > > > create mode 100644 include/linux/bpf_lsm_hook_defs.h
> > >
> > > Adding new BPF hooks in the kernel is one thing, but adding new BPF
> > > LSM hooks outside of the LSM framework is likely to be problematic as
> > > these new hooks operate disconnected from the LSM framework (callback
> > > and LSM kernel object state management). We've seen bugs in the past
> > > caused by the BPF LSM trying to operate independently of the LSM
> > > framework, something like this will only make that worse.
> >
> > Could you please point me to some of the bugs you mention, such that
> > I understand exactly what you mean?
>
> The latest that I'm aware of was a few months ago:
>
> https://lore.kernel.org/bpf/20260628201103.3624525-1-mattbobrowski@google.com
So, for this one patch specifically uses the same fix.
> > I am not really seeing how the
> > real LSM hooks differ from the ones added here (from BPF point of
> > view, and the objects it can access via kfuncs/maps).
>
> The existing LSM hooks, LSM security blobs (the 'void *security'
> fields present in many kernel objects), and individual LSM callbacks
> are managed by the LSM framework whereas the functions you are
> proposing are not. This is important because the LSM framework
> enables/disables individual LSMs at boot time which impacts both the
> executed LSM callbacks and the security blob allocations.
> Implementing "hooks" that operate outside the LSM framework can cause
> unexpected user behavior and potentially destabilize the kernel,
> leading to a system crash.
>
> We require all LSMs (Landlock, Smack, AppArmor, SELinux, etc.) to go
> through the LSM framework, the BPF LSM is no exception to this rule.
> If you want to add additional BPF program call sites beyond what the
> LSM framework provides, please implement them outside of the BPF LSM;
> there is plenty of precendence for that already.
Thanks, this makes sense.
> > The main reason (for now) to specifically create a new list of BPF-only LSM
> > hooks is (pcmoore/lsm.git/tree/README.md):
> >
> > """New LSM hooks must demonstrate their usefulness by providing a meaningful
> > implementation for at least one in-kernel LSM. The goal is to demonstrate the
> > purpose and expected semantics of the hooks. Out of tree kernel code, and pass
> > through implementations, such as the BPF LSM, are not eligible for LSM hook
> > reference implementations."""
> >
> > And for the hooks added in this series
> >
> > a) BPF satisfies our needs, as we can precisely analyse what the
> > calls are trying to do with good granularity
> >
> > b) I am not sure how to actually express this in any in-tree LSMs
> >
> > Can't BPF be considered enough to demonstrate usefulness?
>
> The core issue has little to do with BPF, it has everything to do with
> in-tree vs out-of-tree code. The BPF LSM is explicitly mentioned as a
> "pass through" because some have argued that the BPF LSM is in-tree,
> and while the basic BPF enablement is in-tree, the actual LSM code
> that is executed has thus far always been out-of-tree.
Yes, the actual code isn't, but all the building blocks are.
The BPF is just a sophisticated policy language, in which,
given, say, ethtool hooks are present, one can say "block
all ethtool ETHTOOL_TEST_CMD calls for the XXX driver" or so.
Is there any way into turning BPF first-class citizen from LSMs
point of view?
> --
> paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-08-31 11:09 [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Anton Protopopov
` (7 preceding siblings ...)
2026-08-31 22:34 ` [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks Jakub Kicinski
@ 2026-09-02 18:07 ` Alexei Starovoitov
2026-09-02 19:31 ` Anton Protopopov
8 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-02 18:07 UTC (permalink / raw)
To: Anton Protopopov, bpf, lsm, netdev, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, KP Singh, Matt Bobrowski, John Fastabend,
Christian Brauner, Paul Moore, Linus Torvalds, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
On Mon Aug 31, 2026 at 4:09 AM PDT, Anton Protopopov wrote:
> The BPF LSM programs are allowed to attach to LSM hooks. This enables
> operators to mitigate known bugs without a need to reboot or livepatch
> machines. BPF has shown very useful to create such runtime policies.
> However, many APIs and parts of kernel aren't covered by existing LSM
> hooks and this would be beneficial to extend the coverage.
>
> To simplify the process of adding new hooks this patch series enables
> BPF to attach policy programs to hooks defined outside of the
> official LSM list.
>
> One of the reasons to add a new mechanism is that in order to add a
> new LSM hook an implementation, at least one in-kernel LSM must be
> added, such as SELinux or AppArmor, and BPF is specifically not
> considered as a reference implementation [1]. This is, however, not
> feasible for the use cases and capabilities covered by BPF LSMs,
> which are not directly comparable to those of traditional LSMs.
This is no go.
bpf-lsm can attach to lsm hooks, but the machinery has nothing to
do with LSM. bpf-lsm is exactly the same as bpf-mod-ret.
So calling everything LSM is actively misleading and wrong.
Patch 1 starts this misleading naming convention
and then later patches add fake LSM hooks. Sorry, but no.
pw-bot: cr
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-09-02 18:07 ` Alexei Starovoitov
@ 2026-09-02 19:31 ` Anton Protopopov
2026-09-03 12:16 ` Justin Suess
0 siblings, 1 reply; 31+ messages in thread
From: Anton Protopopov @ 2026-09-02 19:31 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Paul Moore, Linus Torvalds, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
On Wed, Sep 2, 2026 at 8:07 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Mon Aug 31, 2026 at 4:09 AM PDT, Anton Protopopov wrote:
> > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > operators to mitigate known bugs without a need to reboot or livepatch
> > machines. BPF has shown very useful to create such runtime policies.
> > However, many APIs and parts of kernel aren't covered by existing LSM
> > hooks and this would be beneficial to extend the coverage.
> >
> > To simplify the process of adding new hooks this patch series enables
> > BPF to attach policy programs to hooks defined outside of the
> > official LSM list.
> >
> > One of the reasons to add a new mechanism is that in order to add a
> > new LSM hook an implementation, at least one in-kernel LSM must be
> > added, such as SELinux or AppArmor, and BPF is specifically not
> > considered as a reference implementation [1]. This is, however, not
> > feasible for the use cases and capabilities covered by BPF LSMs,
> > which are not directly comparable to those of traditional LSMs.
>
> This is no go.
> bpf-lsm can attach to lsm hooks, but the machinery has nothing to
> do with LSM. bpf-lsm is exactly the same as bpf-mod-ret.
> So calling everything LSM is actively misleading and wrong.
> Patch 1 starts this misleading naming convention
> and then later patches add fake LSM hooks. Sorry, but no.
Got it, thank you.
>
> pw-bot: cr
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/7] bpf: Allow BPF LSM programs to attach to more hooks
2026-09-02 15:31 ` Anton Protopopov
@ 2026-09-02 19:43 ` Paul Moore
0 siblings, 0 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-02 19:43 UTC (permalink / raw)
To: Anton Protopopov
Cc: bpf, lsm, netdev, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
KP Singh, Matt Bobrowski, John Fastabend, Christian Brauner,
Linus Torvalds, Eric Dumazet, Jakub Kicinski, Paolo Abeni
On Wed, Sep 2, 2026 at 11:20 AM Anton Protopopov
<a.s.protopopov@gmail.com> wrote:
> On 26/09/01 06:15PM, Paul Moore wrote:
...
> > The core issue has little to do with BPF, it has everything to do with
> > in-tree vs out-of-tree code. The BPF LSM is explicitly mentioned as a
> > "pass through" because some have argued that the BPF LSM is in-tree,
> > and while the basic BPF enablement is in-tree, the actual LSM code
> > that is executed has thus far always been out-of-tree.
>
> Yes, the actual code isn't, but all the building blocks are.
> The BPF is just a sophisticated policy language, in which,
> given, say, ethtool hooks are present, one can say "block
> all ethtool ETHTOOL_TEST_CMD calls for the XXX driver" or so.
>
> Is there any way into turning BPF first-class citizen from LSMs
> point of view?
As I said earlier, the important part is in-tree vs out-of-tree code.
We are not going to add new LSM hooks only for code that lives outside
the kernel sources, regardless of whether that code is written in C,
Rust, or BPF.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-09-02 19:31 ` Anton Protopopov
@ 2026-09-03 12:16 ` Justin Suess
2026-09-03 13:23 ` Anton Protopopov
0 siblings, 1 reply; 31+ messages in thread
From: Justin Suess @ 2026-09-03 12:16 UTC (permalink / raw)
To: Anton Protopopov
Cc: Alexei Starovoitov, bpf, lsm, netdev, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, KP Singh, Matt Bobrowski, John Fastabend,
Christian Brauner, Paul Moore, Linus Torvalds, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
On Wed, Sep 02, 2026 at 09:31:36PM +0200, Anton Protopopov wrote:
> On Wed, Sep 2, 2026 at 8:07 PM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> > On Mon Aug 31, 2026 at 4:09 AM PDT, Anton Protopopov wrote:
> > > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > > operators to mitigate known bugs without a need to reboot or livepatch
> > > machines. BPF has shown very useful to create such runtime policies.
> > > However, many APIs and parts of kernel aren't covered by existing LSM
> > > hooks and this would be beneficial to extend the coverage.
> > >
> > > To simplify the process of adding new hooks this patch series enables
> > > BPF to attach policy programs to hooks defined outside of the
> > > official LSM list.
> > >
> > > One of the reasons to add a new mechanism is that in order to add a
> > > new LSM hook an implementation, at least one in-kernel LSM must be
> > > added, such as SELinux or AppArmor, and BPF is specifically not
> > > considered as a reference implementation [1]. This is, however, not
> > > feasible for the use cases and capabilities covered by BPF LSMs,
> > > which are not directly comparable to those of traditional LSMs.
> >
> > This is no go.
> > bpf-lsm can attach to lsm hooks, but the machinery has nothing to
> > do with LSM. bpf-lsm is exactly the same as bpf-mod-ret.
> > So calling everything LSM is actively misleading and wrong.
> > Patch 1 starts this misleading naming convention
> > and then later patches add fake LSM hooks. Sorry, but no.
>
> Got it, thank you.
>
Adding to this, there was the previously proposed "killswitch" mechanism
which may be closer to what you want. [1] Unsure of the current status of
this work, but it's a better idea than creating BPF-only LSM hooks...
(like a bpf_override_return helper that doesn't care about
ALLOW_ERROR_INJECTION? unsure what would be best here)
Justin
[1] https://lore.kernel.org/all/20260508195749.1885522-1-sashal@kernel.org/
> >
> > pw-bot: cr
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/7] Add new way to add BPF LSM hooks
2026-09-03 12:16 ` Justin Suess
@ 2026-09-03 13:23 ` Anton Protopopov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Protopopov @ 2026-09-03 13:23 UTC (permalink / raw)
To: Justin Suess
Cc: Alexei Starovoitov, bpf, lsm, netdev, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, KP Singh, Matt Bobrowski, John Fastabend,
Christian Brauner, Paul Moore, Linus Torvalds, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
On 26/09/03 08:16AM, Justin Suess wrote:
> On Wed, Sep 02, 2026 at 09:31:36PM +0200, Anton Protopopov wrote:
> > On Wed, Sep 2, 2026 at 8:07 PM Alexei Starovoitov
> > <alexei.starovoitov@gmail.com> wrote:
> > > On Mon Aug 31, 2026 at 4:09 AM PDT, Anton Protopopov wrote:
> > > > The BPF LSM programs are allowed to attach to LSM hooks. This enables
> > > > operators to mitigate known bugs without a need to reboot or livepatch
> > > > machines. BPF has shown very useful to create such runtime policies.
> > > > However, many APIs and parts of kernel aren't covered by existing LSM
> > > > hooks and this would be beneficial to extend the coverage.
> > > >
> > > > To simplify the process of adding new hooks this patch series enables
> > > > BPF to attach policy programs to hooks defined outside of the
> > > > official LSM list.
> > > >
> > > > One of the reasons to add a new mechanism is that in order to add a
> > > > new LSM hook an implementation, at least one in-kernel LSM must be
> > > > added, such as SELinux or AppArmor, and BPF is specifically not
> > > > considered as a reference implementation [1]. This is, however, not
> > > > feasible for the use cases and capabilities covered by BPF LSMs,
> > > > which are not directly comparable to those of traditional LSMs.
> > >
> > > This is no go.
> > > bpf-lsm can attach to lsm hooks, but the machinery has nothing to
> > > do with LSM. bpf-lsm is exactly the same as bpf-mod-ret.
> > > So calling everything LSM is actively misleading and wrong.
> > > Patch 1 starts this misleading naming convention
> > > and then later patches add fake LSM hooks. Sorry, but no.
> >
> > Got it, thank you.
> >
>
> Adding to this, there was the previously proposed "killswitch" mechanism
> which may be closer to what you want. [1] Unsure of the current status of
> this work, but it's a better idea than creating BPF-only LSM hooks...
Yes, thanks, I've seen it. In the proposed form it is not realistic.
I was going to discuss in my slot at lpc how we possibly can
approach this in a more safe way, powered by BPF.
> (like a bpf_override_return helper that doesn't care about
> ALLOW_ERROR_INJECTION? unsure what would be best here)
>
> Justin
>
> [1] https://lore.kernel.org/all/20260508195749.1885522-1-sashal@kernel.org/
>
>
> > >
> > > pw-bot: cr
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2026-09-03 13:13 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-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-08-31 11:09 ` [PATCH bpf-next 6/7] selftests/bpf: Add tests for the generic netlink BPF hook Anton Protopopov
2026-08-31 12:07 ` 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox