* [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers
2026-03-11 21:31 [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Frederick Lawler
@ 2026-03-11 21:31 ` Frederick Lawler
2026-03-11 22:08 ` bot+bpf-ci
2026-03-17 2:04 ` Kumar Kartikeya Dwivedi
2026-03-11 21:31 ` [PATCH RFC bpf-next 2/4] audit/security: Enable audit BPF kfuncs Frederick Lawler
` (3 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: Frederick Lawler @ 2026-03-11 21:31 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack
Cc: linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team, Frederick Lawler
The primary use case is to provide LSM designers a direct API to report
access allow/denies through the audit subsystem similar to how LSM's
traditionally log their accesses.
Left out from this API are functions that are potentially abuseable such as
audit_log_format() where users may fill any field=value pair. Instead, the
API mostly follows what is exposed through security/lsm_audit.c for
consistency with user space audit expectations. Further calls to functions
report once to avoid repeated-call abuse.
Lastly, each audit record corresponds to the loaded BPF program's ID to
track which program reported the log entry. This helps remove
ambiguity in the event multiple programs are registered to the same
security hook.
Exposed functions:
bpf_audit_log_start()
bpf_audit_log_end()
bpf_audit_log_cause()
bpf_audit_log_cap()
bpf_audit_log_path()
bpf_audit_log_file()
bpf_audit_log_ioctl_op()
bpf_audit_log_dentry()
bpf_audit_log_inode()
bpf_audit_log_task()
bpf_audit_log_net_sock()
bpf_audit_log_net_sockaddr()
Signed-off-by: Frederick Lawler <fred@cloudflare.com>
---
include/linux/lsm_audit.h | 1 +
include/uapi/linux/audit.h | 1 +
security/lsm_audit_kfuncs.c | 306 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 308 insertions(+)
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 382c56a97bba1d0e5efe082553338229d541e267..859f51590de417ac246309eb75a760b8632224be 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -78,6 +78,7 @@ struct common_audit_data {
#define LSM_AUDIT_DATA_NOTIFICATION 16
#define LSM_AUDIT_DATA_ANONINODE 17
#define LSM_AUDIT_DATA_NLMSGTYPE 18
+#define LSM_AUDIT_DATA_CAUSE 19 /* unused */
union {
struct path path;
struct dentry *dentry;
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 14a1c1fe013acecb12ea6bf81690965421baa7ff..7a22e214fe3e421decfc4109d2e6a3cee996fe51 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -150,6 +150,7 @@
#define AUDIT_LANDLOCK_DOMAIN 1424 /* Landlock domain status */
#define AUDIT_MAC_TASK_CONTEXTS 1425 /* Multiple LSM task contexts */
#define AUDIT_MAC_OBJ_CONTEXTS 1426 /* Multiple LSM objext contexts */
+#define AUDIT_BPF_LSM_ACCESS 1427 /* LSM BPF MAC events */
#define AUDIT_FIRST_KERN_ANOM_MSG 1700
#define AUDIT_LAST_KERN_ANOM_MSG 1799
diff --git a/security/lsm_audit_kfuncs.c b/security/lsm_audit_kfuncs.c
new file mode 100644
index 0000000000000000000000000000000000000000..0d4fb20be34a61db29aa2c48d2aefc39131e73bf
--- /dev/null
+++ b/security/lsm_audit_kfuncs.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Cloudflare */
+
+#include <linux/audit.h>
+#include <linux/bpf_mem_alloc.h>
+#include <linux/gfp_types.h>
+#include <linux/in6.h>
+#include <linux/lsm_audit.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+
+struct bpf_audit_context {
+ struct audit_buffer *ab;
+ u64 log_once_mask;
+};
+
+static struct bpf_mem_alloc bpf_audit_context_ma;
+
+static inline u64 log_once(struct bpf_audit_context *ac, u64 mask)
+{
+ u64 set = (ac->log_once_mask & mask);
+
+ ac->log_once_mask |= mask;
+ return set;
+}
+
+static inline int __audit_log_lsm_data(struct bpf_audit_context *ac,
+ struct common_audit_data *ad)
+{
+ if (log_once(ac, BIT_ULL(ad->type)))
+ return -EINVAL;
+
+ audit_log_lsm_data(ac->ab, ad);
+ return 0;
+}
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc
+struct bpf_audit_context *bpf_audit_log_start(struct bpf_prog_aux *aux)
+{
+ char comm[sizeof(current->comm)];
+ struct bpf_audit_context *ac;
+
+ ac = bpf_mem_cache_alloc(&bpf_audit_context_ma);
+ if (!ac)
+ return NULL;
+
+ memset(ac, 0, sizeof(*ac));
+ ac->ab = audit_log_start(audit_context(),
+ (aux->might_sleep) ? GFP_KERNEL : GFP_ATOMIC,
+ AUDIT_BPF_LSM_ACCESS);
+ if (!ac->ab) {
+ bpf_mem_cache_free(&bpf_audit_context_ma, ac);
+ return NULL;
+ }
+
+ audit_log_format(ac->ab, "prog-id=%d", aux->id);
+
+ /* Audit may not have a filter configured for syscalls. Include
+ * potentionally redundant pid & comm information
+ */
+ audit_log_format(ac->ab, " pid=%d comm=", task_tgid_nr(current));
+ audit_log_untrustedstring(ac->ab, get_task_comm(comm, current));
+
+ return ac;
+}
+
+__bpf_kfunc void bpf_audit_log_end(struct bpf_audit_context *ac)
+{
+ audit_log_end(ac->ab);
+ bpf_mem_cache_free(&bpf_audit_context_ma, ac);
+}
+
+__bpf_kfunc int bpf_audit_log_cause(struct bpf_audit_context *ac,
+ const char *cause__str)
+{
+ if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_CAUSE)))
+ return -EINVAL;
+
+ audit_log_format(ac->ab, " cause=");
+ audit_log_untrustedstring(ac->ab, cause__str);
+ return 0;
+}
+
+__bpf_kfunc int bpf_audit_log_cap(struct bpf_audit_context *ac, int cap)
+{
+ struct common_audit_data ad;
+
+ ad.type = LSM_AUDIT_DATA_CAP;
+ ad.u.cap = cap;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_path(struct bpf_audit_context *ac,
+ const struct path *path)
+{
+ struct common_audit_data ad;
+
+ /* DATA_PATH prints similar to DATA_FILE */
+ if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_FILE)))
+ return -EINVAL;
+
+ ad.type = LSM_AUDIT_DATA_PATH;
+ ad.u.path = *path;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_file(struct bpf_audit_context *ac,
+ struct file *file)
+{
+ struct common_audit_data ad;
+
+ /* DATA_PATH prints similar to DATA_FILE */
+ if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_PATH)))
+ return -EINVAL;
+
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_ioctl_op(struct bpf_audit_context *ac,
+ struct file *file, u16 cmd)
+{
+ struct lsm_ioctlop_audit op = { .path = file->f_path, .cmd = cmd };
+ struct common_audit_data ad;
+
+ ad.type = LSM_AUDIT_DATA_IOCTL_OP;
+ ad.u.op = &op;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_dentry(struct bpf_audit_context *ac,
+ struct dentry *dentry)
+{
+ struct common_audit_data ad;
+
+ /* DATA_DENTRY prints similar to DATA_INODE */
+ if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_INODE)))
+ return -EINVAL;
+
+ ad.type = LSM_AUDIT_DATA_DENTRY;
+ ad.u.dentry = dentry;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_inode(struct bpf_audit_context *ac,
+ struct inode *inode)
+{
+ struct common_audit_data ad;
+
+ /* DATA_DENTRY prints similar to DATA_INODE */
+ if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_DENTRY)))
+ return -EINVAL;
+
+ ad.type = LSM_AUDIT_DATA_INODE;
+ ad.u.inode = inode;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_task(struct bpf_audit_context *ac,
+ struct task_struct *tsk)
+{
+ struct common_audit_data ad;
+
+ ad.type = LSM_AUDIT_DATA_TASK;
+ ad.u.tsk = tsk;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int bpf_audit_log_net_sock(struct bpf_audit_context *ac, int netif,
+ const struct socket *sock)
+{
+ struct lsm_network_audit net = { .sk = sock->sk, .netif = netif };
+ struct common_audit_data ad;
+
+ ad.type = LSM_AUDIT_DATA_NET;
+ ad.u.net = &net;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc int
+bpf_audit_log_net_sockaddr(struct bpf_audit_context *ac, int netif,
+ const struct sockaddr *saddr__nullable,
+ const struct sockaddr *daddr__nullable, int addrlen)
+{
+ struct lsm_network_audit net;
+ struct common_audit_data ad;
+
+ net.netif = netif;
+
+ if (!saddr__nullable && !daddr__nullable)
+ return -EINVAL;
+
+ if (saddr__nullable && daddr__nullable &&
+ saddr__nullable->sa_family != daddr__nullable->sa_family)
+ return -EINVAL;
+
+ if (saddr__nullable)
+ net.family = saddr__nullable->sa_family;
+ else
+ net.family = daddr__nullable->sa_family;
+
+ switch (net.family) {
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ if (saddr__nullable) {
+ struct sockaddr_in6 *saddr =
+ (struct sockaddr_in6 *)saddr__nullable;
+ net.fam.v6.saddr = saddr->sin6_addr;
+ net.sport = saddr->sin6_port;
+ }
+
+ if (daddr__nullable) {
+ struct sockaddr_in6 *daddr =
+ (struct sockaddr_in6 *)daddr__nullable;
+ net.fam.v6.daddr = daddr->sin6_addr;
+ net.dport = daddr->sin6_port;
+ }
+ break;
+#endif
+ case AF_INET:
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ if (saddr__nullable) {
+ struct sockaddr_in *saddr =
+ (struct sockaddr_in *)saddr__nullable;
+ net.fam.v4.saddr = saddr->sin_addr.s_addr;
+ net.sport = saddr->sin_port;
+ }
+
+ if (daddr__nullable) {
+ struct sockaddr_in *daddr =
+ (struct sockaddr_in *)daddr__nullable;
+ net.fam.v4.daddr = daddr->sin_addr.s_addr;
+ net.dport = daddr->sin_port;
+ }
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ ad.type = LSM_AUDIT_DATA_NET;
+ ad.u.net = &net;
+ return __audit_log_lsm_data(ac, &ad);
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(lsm_audit_set_ids)
+
+BTF_ID_FLAGS(func, bpf_audit_log_start,
+ KF_ACQUIRE | KF_DESTRUCTIVE | KF_IMPLICIT_ARGS | KF_RET_NULL);
+
+BTF_ID_FLAGS(func, bpf_audit_log_end, KF_DESTRUCTIVE | KF_RELEASE);
+
+/* The following have a recursion opportunity if a LSM is attached to any of
+ * the following functions, and a bpf_audit_log_*() is called.
+ * security_current_getlsmprop_subj,
+ * security_lsmprop_to_secctx, or
+ * security_release_secctx
+ */
+BTF_ID_FLAGS(func, bpf_audit_log_cause, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_cap, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_path, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_file, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_ioctl_op, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_dentry, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_inode, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_task, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_net_sock, KF_DESTRUCTIVE);
+BTF_ID_FLAGS(func, bpf_audit_log_net_sockaddr, KF_DESTRUCTIVE);
+
+BTF_KFUNCS_END(lsm_audit_set_ids)
+
+static int bpf_lsm_audit_kfuncs_filter(const struct bpf_prog *prog,
+ u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&lsm_audit_set_ids, kfunc_id))
+ return 0;
+
+ return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
+}
+
+static const struct btf_kfunc_id_set bpf_lsm_audit_set = {
+ .owner = THIS_MODULE,
+ .set = &lsm_audit_set_ids,
+ .filter = bpf_lsm_audit_kfuncs_filter,
+};
+
+static int lsm_audit_init_bpf(void)
+{
+ int ret;
+
+ ret = bpf_mem_alloc_init(&bpf_audit_context_ma,
+ sizeof(struct bpf_audit_context), false);
+ return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+ &bpf_lsm_audit_set);
+}
+
+late_initcall(lsm_audit_init_bpf)
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers
2026-03-11 21:31 ` [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers Frederick Lawler
@ 2026-03-11 22:08 ` bot+bpf-ci
2026-03-17 2:04 ` Kumar Kartikeya Dwivedi
1 sibling, 0 replies; 12+ messages in thread
From: bot+bpf-ci @ 2026-03-11 22:08 UTC (permalink / raw)
To: fred, paul, jmorris, serge, eparis, ast, daniel, andrii,
martin.lau, eddyz87, song, yonghong.song, john.fastabend, kpsingh,
sdf, haoluo, jolsa, shuah, mic, gnoack
Cc: linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team, fred, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2839 bytes --]
> diff --git a/security/lsm_audit_kfuncs.c b/security/lsm_audit_kfuncs.c
> --- /dev/null
> +++ b/security/lsm_audit_kfuncs.c
[ ... ]
> +__bpf_kfunc
> +struct bpf_audit_context *bpf_audit_log_start(struct bpf_prog_aux *aux)
> +{
> + char comm[sizeof(current->comm)];
> + struct bpf_audit_context *ac;
> +
> + ac = bpf_mem_cache_alloc(&bpf_audit_context_ma);
> + if (!ac)
> + return NULL;
> +
> + memset(ac, 0, sizeof(*ac));
> + ac->ab = audit_log_start(audit_context(),
> + (aux->might_sleep) ? GFP_KERNEL : GFP_ATOMIC,
> + AUDIT_BPF_LSM_ACCESS);
> + if (!ac->ab) {
> + bpf_mem_cache_free(&bpf_audit_context_ma, ac);
> + return NULL;
> + }
> +
> + audit_log_format(ac->ab, "prog-id=%d", aux->id);
aux->id is u32 -- should this be %u?
> +
> + /* Audit may not have a filter configured for syscalls. Include
> + * potentionally redundant pid & comm information
Minor typo: "potentionally" -> "potentially".
> + */
[ ... ]
> +__bpf_kfunc int bpf_audit_log_net_sock(struct bpf_audit_context *ac, int netif,
> + const struct socket *sock)
> +{
> + struct lsm_network_audit net = { .sk = sock->sk, .netif = netif };
> + struct common_audit_data ad;
> +
> + ad.type = LSM_AUDIT_DATA_NET;
> + ad.u.net = &net;
> + return __audit_log_lsm_data(ac, &ad);
> +}
> +
> +__bpf_kfunc int
> +bpf_audit_log_net_sockaddr(struct bpf_audit_context *ac, int netif,
> + const struct sockaddr *saddr__nullable,
> + const struct sockaddr *daddr__nullable, int addrlen)
> +{
> + struct lsm_network_audit net;
^^^
Unlike bpf_audit_log_net_sock() above which uses a designated
initializer that zero-fills the struct, this declaration leaves
net uninitialized.
In particular, net.sk is never assigned anywhere in this function.
When audit_log_lsm_data() handles LSM_AUDIT_DATA_NET, it does:
if (a->u.net->sk) {
const struct sock *sk = a->u.net->sk;
switch (sk->sk_family) {
Since net.sk contains stack garbage, this will likely dereference
an invalid pointer and crash.
Additionally, when only one of saddr__nullable or daddr__nullable
is provided, the corresponding sport/dport and address fields for
the missing side remain uninitialized, and audit_log_lsm_data()
unconditionally prints both sides:
print_ipv4_addr(ab, a->u.net->v4info.saddr, a->u.net->sport, ...);
print_ipv4_addr(ab, a->u.net->v4info.daddr, a->u.net->dport, ...);
Would initializing with something like
struct lsm_network_audit net = { .netif = netif }; address both
issues?
> + struct common_audit_data ad;
> +
> + net.netif = netif;
[ ... ]
---
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/22976062351
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers
2026-03-11 21:31 ` [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers Frederick Lawler
2026-03-11 22:08 ` bot+bpf-ci
@ 2026-03-17 2:04 ` Kumar Kartikeya Dwivedi
1 sibling, 0 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 2:04 UTC (permalink / raw)
To: Frederick Lawler
Cc: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack,
linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team
On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
>
> The primary use case is to provide LSM designers a direct API to report
> access allow/denies through the audit subsystem similar to how LSM's
> traditionally log their accesses.
>
> Left out from this API are functions that are potentially abuseable such as
> audit_log_format() where users may fill any field=value pair. Instead, the
> API mostly follows what is exposed through security/lsm_audit.c for
> consistency with user space audit expectations. Further calls to functions
> report once to avoid repeated-call abuse.
>
> Lastly, each audit record corresponds to the loaded BPF program's ID to
> track which program reported the log entry. This helps remove
> ambiguity in the event multiple programs are registered to the same
> security hook.
>
> Exposed functions:
>
> bpf_audit_log_start()
> bpf_audit_log_end()
> bpf_audit_log_cause()
> bpf_audit_log_cap()
> bpf_audit_log_path()
> bpf_audit_log_file()
> bpf_audit_log_ioctl_op()
> bpf_audit_log_dentry()
> bpf_audit_log_inode()
> bpf_audit_log_task()
> bpf_audit_log_net_sock()
> bpf_audit_log_net_sockaddr()
>
> Signed-off-by: Frederick Lawler <fred@cloudflare.com>
> ---
> include/linux/lsm_audit.h | 1 +
> include/uapi/linux/audit.h | 1 +
> security/lsm_audit_kfuncs.c | 306 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 308 insertions(+)
>
> diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
> index 382c56a97bba1d0e5efe082553338229d541e267..859f51590de417ac246309eb75a760b8632224be 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -78,6 +78,7 @@ struct common_audit_data {
> #define LSM_AUDIT_DATA_NOTIFICATION 16
> #define LSM_AUDIT_DATA_ANONINODE 17
> #define LSM_AUDIT_DATA_NLMSGTYPE 18
> +#define LSM_AUDIT_DATA_CAUSE 19 /* unused */
> union {
> struct path path;
> struct dentry *dentry;
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 14a1c1fe013acecb12ea6bf81690965421baa7ff..7a22e214fe3e421decfc4109d2e6a3cee996fe51 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -150,6 +150,7 @@
> #define AUDIT_LANDLOCK_DOMAIN 1424 /* Landlock domain status */
> #define AUDIT_MAC_TASK_CONTEXTS 1425 /* Multiple LSM task contexts */
> #define AUDIT_MAC_OBJ_CONTEXTS 1426 /* Multiple LSM objext contexts */
> +#define AUDIT_BPF_LSM_ACCESS 1427 /* LSM BPF MAC events */
>
> #define AUDIT_FIRST_KERN_ANOM_MSG 1700
> #define AUDIT_LAST_KERN_ANOM_MSG 1799
> diff --git a/security/lsm_audit_kfuncs.c b/security/lsm_audit_kfuncs.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..0d4fb20be34a61db29aa2c48d2aefc39131e73bf
> --- /dev/null
> +++ b/security/lsm_audit_kfuncs.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (c) 2026 Cloudflare */
> +
> +#include <linux/audit.h>
> +#include <linux/bpf_mem_alloc.h>
> +#include <linux/gfp_types.h>
> +#include <linux/in6.h>
> +#include <linux/lsm_audit.h>
> +#include <linux/socket.h>
> +#include <linux/types.h>
> +
> +struct bpf_audit_context {
> + struct audit_buffer *ab;
> + u64 log_once_mask;
> +};
> +
> +static struct bpf_mem_alloc bpf_audit_context_ma;
bpf_mem_alloc will be deprecated, please use kmalloc_nolock().
> +
> +static inline u64 log_once(struct bpf_audit_context *ac, u64 mask)
> +{
> + u64 set = (ac->log_once_mask & mask);
> +
> + ac->log_once_mask |= mask;
> + return set;
> +}
> +
> + [...]
> +
> +__bpf_kfunc int
> +bpf_audit_log_net_sockaddr(struct bpf_audit_context *ac, int netif,
> + const struct sockaddr *saddr__nullable,
> + const struct sockaddr *daddr__nullable, int addrlen)
> +{
> + struct lsm_network_audit net;
> + struct common_audit_data ad;
> +
> + net.netif = netif;
> +
> + if (!saddr__nullable && !daddr__nullable)
> + return -EINVAL;
Code look a lot cleaner if you just stashed these locally in a better
named variable.
> +
> + if (saddr__nullable && daddr__nullable &&
> + saddr__nullable->sa_family != daddr__nullable->sa_family)
> + return -EINVAL;
> +
> [...]
> +
> +/* The following have a recursion opportunity if a LSM is attached to any of
> + * the following functions, and a bpf_audit_log_*() is called.
> + * security_current_getlsmprop_subj,
> + * security_lsmprop_to_secctx, or
> + * security_release_secctx
> + */
> +BTF_ID_FLAGS(func, bpf_audit_log_cause, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_cap, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_path, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_file, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_ioctl_op, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_dentry, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_inode, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_task, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_net_sock, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_net_sockaddr, KF_DESTRUCTIVE);
> +
> +BTF_KFUNCS_END(lsm_audit_set_ids)
> +
> +static int bpf_lsm_audit_kfuncs_filter(const struct bpf_prog *prog,
> + u32 kfunc_id)
> +{
> + if (!btf_id_set8_contains(&lsm_audit_set_ids, kfunc_id))
> + return 0;
> +
> + return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
> +}
> +
> +static const struct btf_kfunc_id_set bpf_lsm_audit_set = {
> + .owner = THIS_MODULE,
> + .set = &lsm_audit_set_ids,
> + .filter = bpf_lsm_audit_kfuncs_filter,
> +};
> +
> +static int lsm_audit_init_bpf(void)
> +{
> + int ret;
> +
> + ret = bpf_mem_alloc_init(&bpf_audit_context_ma,
> + sizeof(struct bpf_audit_context), false);
One you switch to kmalloc_nolock you can drop this too.
> + return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
> + &bpf_lsm_audit_set);
> +}
> +
> +late_initcall(lsm_audit_init_bpf)
>
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC bpf-next 2/4] audit/security: Enable audit BPF kfuncs
2026-03-11 21:31 [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Frederick Lawler
2026-03-11 21:31 ` [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers Frederick Lawler
@ 2026-03-11 21:31 ` Frederick Lawler
2026-03-11 21:31 ` [PATCH RFC bpf-next 3/4] selftests/bpf: Add audit helpers for BPF tests Frederick Lawler
` (2 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Frederick Lawler @ 2026-03-11 21:31 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack
Cc: linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team, Frederick Lawler
Enable audit BPF kfuncs.
Signed-off-by: Frederick Lawler <fred@cloudflare.com>
---
security/Makefile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/security/Makefile b/security/Makefile
index 4601230ba442a1bcedc3f999b74a7796ac72894d..de980b2797c1f8f8d0eaeb1be949c41e6ecb8fc1 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -16,6 +16,8 @@ obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
obj-$(CONFIG_HAS_SECURITY_AUDIT) += lsm_audit.o
+lsm_audit-y += lsm_audit.o
+lsm_audit-$(CONFIG_BPF_LSM) += lsm_audit_kfuncs.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC bpf-next 3/4] selftests/bpf: Add audit helpers for BPF tests
2026-03-11 21:31 [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Frederick Lawler
2026-03-11 21:31 ` [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers Frederick Lawler
2026-03-11 21:31 ` [PATCH RFC bpf-next 2/4] audit/security: Enable audit BPF kfuncs Frederick Lawler
@ 2026-03-11 21:31 ` Frederick Lawler
2026-03-11 21:31 ` [PATCH RFC bpf-next 4/4] selftests/bpf: Add lsm_audit_kfuncs tests Frederick Lawler
2026-03-17 2:43 ` [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Kumar Kartikeya Dwivedi
4 siblings, 0 replies; 12+ messages in thread
From: Frederick Lawler @ 2026-03-11 21:31 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack
Cc: linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team, Frederick Lawler
Add audit helper utilities for reading and parsing audit messages
in BPF selftests.
Assisted-by: Claude:claude-4.5-opus
Signed-off-by: Frederick Lawler <fred@cloudflare.com>
---
tools/testing/selftests/bpf/Makefile | 3 +-
tools/testing/selftests/bpf/audit_helpers.c | 281 ++++++++++++++++++++++++++++
tools/testing/selftests/bpf/audit_helpers.h | 55 ++++++
3 files changed, 338 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 869b582b1d1ff496fb07736597708487be3438ed..76a428539add5e03fe3811b41c55005c22f5cead 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -754,7 +754,8 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
flow_dissector_load.h \
ip_check_defrag_frags.h \
bpftool_helpers.c \
- usdt_1.c usdt_2.c
+ usdt_1.c usdt_2.c \
+ audit_helpers.c
TRUNNER_LIB_SOURCES := find_bit.c
TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
$(OUTPUT)/liburandom_read.so \
diff --git a/tools/testing/selftests/bpf/audit_helpers.c b/tools/testing/selftests/bpf/audit_helpers.c
new file mode 100644
index 0000000000000000000000000000000000000000..a105136a581f92a1af73b9456b1e85dc88176678
--- /dev/null
+++ b/tools/testing/selftests/bpf/audit_helpers.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF audit helpers
+ *
+ * Borrowed code from tools/selftests/landlock/audit.h
+ *
+ * Copyright (C) 2024-2025 Microsoft Corporation
+ * Copyright (c) 2026 Cloudflare
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include "audit_helpers.h"
+
+static __u32 seq;
+
+int audit_init(void)
+{
+ int bufsize = 1024 * 1024; /* 1MB receive buffer */
+ struct audit_message msg;
+ int fd, err;
+
+ fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+ if (fd < 0)
+ return -errno;
+
+ /*
+ * Increase receive buffer to reduce kernel-side queueing.
+ * When the socket buffer fills up, audit records get queued in
+ * the kernel's hold/retry queues and delivered on subsequent runs.
+ */
+ setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
+
+ seq = 0;
+ err = audit_send(fd, AUDIT_SET, AUDIT_STATUS_ENABLED, 1);
+ if (err)
+ goto out_close;
+
+ do {
+ err = audit_recv(fd, &msg, 0);
+ if (err < 0)
+ goto out_close;
+ } while (msg.nlh.nlmsg_type != NLMSG_ERROR);
+
+ if (msg.err.error)
+ goto out_close;
+
+ err = audit_send(fd, AUDIT_SET, AUDIT_STATUS_PID, getpid());
+ if (err)
+ goto out_close;
+
+ do {
+ err = audit_recv(fd, &msg, 0);
+ if (err < 0)
+ goto out_close;
+ } while (msg.nlh.nlmsg_type != NLMSG_ERROR);
+
+ if (msg.err.error)
+ goto out_close;
+
+ return fd;
+
+out_close:
+ close(fd);
+ return err;
+}
+
+void audit_cleanup(int fd)
+{
+ if (fd > 0)
+ close(fd);
+}
+
+int audit_send(int fd, __u16 type, __u32 key, __u32 val)
+{
+ struct audit_message msg = {
+ .nlh = {
+ .nlmsg_len = NLMSG_SPACE(sizeof(msg.status)),
+ .nlmsg_type = type,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ .nlmsg_seq = ++seq,
+ },
+ .status = {
+ .mask = key,
+ .enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
+ .pid = key == AUDIT_STATUS_PID ? val : 0,
+ },
+ };
+ struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
+ int ret;
+
+ do {
+ ret = sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
+ (struct sockaddr *)&addr, sizeof(addr));
+ } while (ret < 0 && errno == EINTR);
+
+ return ret == msg.nlh.nlmsg_len ? 0 : -errno;
+}
+
+/*
+ * Receive an audit message from the netlink socket.
+ * Returns:
+ * > 0: message type on success
+ * 0: ACK received (NLMSG_ERROR with error=0)
+ * < 0: negative errno on error
+ */
+int audit_recv(int fd, struct audit_message *msg, int flags)
+{
+ struct sockaddr_nl addr;
+ socklen_t addrlen = sizeof(addr);
+ int ret;
+
+ do {
+ ret = recvfrom(fd, msg, sizeof(*msg), flags,
+ (struct sockaddr *)&addr, &addrlen);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ return -errno;
+
+ /* Must be from kernel (pid 0) */
+ if (addrlen != sizeof(addr) || addr.nl_pid != 0)
+ return -EINVAL;
+
+ /*
+ * NLMSG_ERROR with error=0 is an ACK. The kernel sends this in
+ * response to messages with NLM_F_ACK flag set.
+ */
+ if (msg->nlh.nlmsg_type == NLMSG_ERROR) {
+ if (msg->err.error == 0)
+ return 0; /* ACK */
+ return msg->err.error;
+ }
+
+ return msg->nlh.nlmsg_type;
+}
+
+__printf(2, 3) static inline void
+debug(struct audit_observer *obs, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!obs || !obs->log)
+ return;
+
+ va_start(args, fmt);
+ vfprintf(obs->log, fmt, args);
+ va_end(args);
+}
+
+void audit_observer_init(struct audit_observer *obs, int audit_fd, FILE *log,
+ int wait_timeout_ms)
+{
+ obs->audit_fd = audit_fd;
+ obs->wait_timeout = wait_timeout_ms;
+
+ if (log)
+ obs->log = log;
+
+ audit_observer_reset(obs);
+}
+
+void audit_observer_reset(struct audit_observer *obs)
+{
+ memset(obs->expects, 0, sizeof(obs->expects));
+ obs->num_expects = 0;
+}
+
+int audit_observer_expect(struct audit_observer *obs, int audit_type,
+ const char *pattern, int count)
+{
+ struct audit_expectation *exp;
+
+ if (obs->num_expects >= AUDIT_EXPECT_MAX)
+ return -EINVAL;
+
+ exp = &obs->expects[obs->num_expects++];
+ exp->type = audit_type;
+ exp->pattern = pattern;
+ exp->expected_count = count;
+ exp->matched_count = 0;
+ return 0;
+}
+
+/*
+ * Check if a message matches any pending expectation.
+ * Returns 1 if all expectations are satisfied, 0 otherwise.
+ */
+static int audit_observer_match(struct audit_observer *obs,
+ struct audit_message *msg)
+{
+ int all_satisfied = 1;
+
+ for (int i = 0; i < obs->num_expects; i++) {
+ struct audit_expectation *exp = &obs->expects[i];
+
+ if (exp->matched_count >= exp->expected_count)
+ continue;
+
+ /* Check if this message matches */
+ if (exp->type && msg->nlh.nlmsg_type != exp->type)
+ goto check_satisfied;
+
+ if (strstr(msg->data, exp->pattern)) {
+ exp->matched_count++;
+ debug(obs, "%s: matched [%d/%d] %s\n", __func__,
+ exp->matched_count, exp->expected_count,
+ exp->pattern);
+ }
+
+check_satisfied:
+ if (exp->matched_count < exp->expected_count)
+ all_satisfied = 0;
+ }
+
+ return all_satisfied;
+}
+
+/*
+ * Wait for all expected audit messages to arrive.
+ * Returns 0 on success (all expectations met), -ETIMEDOUT on timeout.
+ */
+int audit_observer_wait(struct audit_observer *obs)
+{
+ struct pollfd pfd = { .fd = obs->audit_fd, .events = POLLIN };
+ struct audit_message msg;
+ int ret;
+
+ while (1) {
+ ret = poll(&pfd, 1, obs->wait_timeout);
+ if (ret < 0)
+ return -errno;
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ memset(&msg, 0, sizeof(msg));
+ ret = audit_recv(obs->audit_fd, &msg, MSG_DONTWAIT);
+
+ if (ret == -EAGAIN || ret == -EWOULDBLOCK)
+ continue;
+
+ if (ret <= 0)
+ continue;
+
+ debug(obs, "%s: recv type=%d %s\n", __func__,
+ msg.nlh.nlmsg_type, msg.data);
+
+ if (audit_observer_match(obs, &msg))
+ return 0;
+ }
+}
+
+int audit_observer_check_satisfied(struct audit_observer *obs)
+{
+ for (int i = 0; i < obs->num_expects; i++) {
+ struct audit_expectation *exp = &obs->expects[i];
+
+ if (exp->matched_count < exp->expected_count) {
+ debug(obs, "%s: FAILED pattern '%s' got %d/%d\n",
+ __func__, exp->pattern, exp->matched_count,
+ exp->expected_count);
+ return 0;
+ }
+ }
+
+ return 1;
+}
diff --git a/tools/testing/selftests/bpf/audit_helpers.h b/tools/testing/selftests/bpf/audit_helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..40f3d20635bb25c305067756897593f34d54531e
--- /dev/null
+++ b/tools/testing/selftests/bpf/audit_helpers.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Cloudflare */
+#pragma once
+
+#include <linux/audit.h>
+#include <linux/netlink.h>
+#include <stdio.h>
+
+#define MAX_AUDIT_MESSAGE_LENGTH 8970
+
+struct audit_message {
+ struct nlmsghdr nlh;
+ union {
+ struct audit_status status;
+ struct nlmsgerr err;
+ char data[MAX_AUDIT_MESSAGE_LENGTH];
+ };
+};
+
+/*
+ * Observer-based audit message matching.
+ * Tests register expected patterns before triggering events, then
+ * wait for matches. Messages that don't match any pattern are skipped.
+ */
+#define AUDIT_EXPECT_MAX 32
+
+struct audit_expectation {
+ __u16 type;
+ const char *pattern;
+ int expected_count;
+ int matched_count;
+};
+
+struct audit_observer {
+ struct audit_expectation expects[AUDIT_EXPECT_MAX];
+ int num_expects;
+ FILE *log;
+ int wait_timeout;
+ int audit_fd;
+};
+
+int audit_init(void);
+void audit_cleanup(int fd);
+int audit_wait_ack(int fd);
+int audit_send(int fd, __u16 type, __u32 key, __u32 val);
+int audit_recv(int fd, struct audit_message *msg, int flags);
+int audit_wait_ack(int fd);
+
+void audit_observer_init(struct audit_observer *obs, int audit_fd, FILE *log,
+ int wait_timeout);
+void audit_observer_reset(struct audit_observer *obs);
+int audit_observer_expect(struct audit_observer *obs, int audit_type,
+ const char *pattern, int count);
+int audit_observer_wait(struct audit_observer *obs);
+int audit_observer_check_satisfied(struct audit_observer *obs);
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC bpf-next 4/4] selftests/bpf: Add lsm_audit_kfuncs tests
2026-03-11 21:31 [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Frederick Lawler
` (2 preceding siblings ...)
2026-03-11 21:31 ` [PATCH RFC bpf-next 3/4] selftests/bpf: Add audit helpers for BPF tests Frederick Lawler
@ 2026-03-11 21:31 ` Frederick Lawler
2026-03-17 2:43 ` [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Kumar Kartikeya Dwivedi
4 siblings, 0 replies; 12+ messages in thread
From: Frederick Lawler @ 2026-03-11 21:31 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack
Cc: linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team, Frederick Lawler
Add selftests for the audit kfunc BPF LSM functionality including
both the test program and BPF progs.
Assisted-by: Claude:claude-4.5-opus
Signed-off-by: Frederick Lawler <fred@cloudflare.com>
---
.../selftests/bpf/prog_tests/lsm_audit_kfuncs.c | 598 +++++++++++++++++++++
.../selftests/bpf/progs/test_lsm_audit_kfuncs.c | 263 +++++++++
2 files changed, 861 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_audit_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/lsm_audit_kfuncs.c
new file mode 100644
index 0000000000000000000000000000000000000000..de18e1a3c79578d4151a12a029f2a9e6cc7648e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_audit_kfuncs.c
@@ -0,0 +1,598 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Cloudflare */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include "audit_helpers.h"
+#include "test_lsm_audit_kfuncs.skel.h"
+#include "test_progs.h"
+
+#ifndef AUDIT_BPF_LSM_ACCESS
+#define AUDIT_BPF_LSM_ACCESS 1427
+#endif
+
+static inline struct sockaddr_in addr4(void)
+{
+ return (struct sockaddr_in){
+ .sin_family = AF_INET,
+ .sin_port = htons(1234),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+}
+
+static inline struct sockaddr_in6 addr6(void)
+{
+ return (struct sockaddr_in6){
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(1234),
+ .sin6_addr = in6addr_loopback,
+ };
+}
+
+static int bind_connect(const struct sockaddr *addr, int addrlen)
+{
+ int err;
+ int sock;
+ int opt = 1;
+ socklen_t optlen = sizeof(opt);
+
+ sock = socket(addr->sa_family, SOCK_STREAM, 0);
+ if (!ASSERT_OK_FD(sock, "socket"))
+ return 1;
+
+ err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, optlen);
+ if (!ASSERT_OK(err, "setsockopt"))
+ goto done;
+
+ err = bind(sock, addr, addrlen);
+ if (!ASSERT_OK(err, "bind"))
+ goto done;
+
+ err = connect(sock, addr, addrlen);
+ ASSERT_OK(err, "connect");
+
+ err = getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, &optlen);
+ ASSERT_OK(err, "getsockopt");
+
+done:
+ close(sock);
+ return err;
+}
+
+static void test_audit_log_sockaddr_src(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_in sin = addr4();
+ struct sockaddr_in6 sin6 = addr6();
+ struct bpf_link *link;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sockaddr_src);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"bind4\" saddr=127.0.0.1 src=1234 netif=lo",
+ 1);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"bind6\" saddr=::1 src=1234 netif=lo", 1);
+
+ if (bind_connect((const struct sockaddr *)&sin, sizeof(sin)))
+ goto done;
+
+ if (bind_connect((const struct sockaddr *)&sin6, sizeof(sin6)))
+ goto done;
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+done:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_sockaddr_dest(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_in sin = addr4();
+ struct sockaddr_in6 sin6 = addr6();
+ struct bpf_link *link;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sockaddr_dest);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"connect4\" daddr=127.0.0.1 dest=1234 netif=lo",
+ 1);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"connect6\" daddr=::1 dest=1234 netif=lo",
+ 1);
+
+ if (bind_connect((const struct sockaddr *)&sin, sizeof(sin)))
+ goto out;
+
+ if (bind_connect((const struct sockaddr *)&sin6, sizeof(sin6)))
+ goto out;
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_sock(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_in sin = addr4();
+ struct sockaddr_in6 sin6 = addr6();
+ struct bpf_link *link;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sock);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"sock4\" laddr=127.0.0.1 lport=1234 faddr=127.0.0.1 fport=1234 netif=lo",
+ 1);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"sock6\" laddr=::1 lport=1234 faddr=::1 fport=1234 netif=lo",
+ 1);
+
+ if (bind_connect((const struct sockaddr *)&sin, sizeof(sin)))
+ goto out;
+
+ if (bind_connect((const struct sockaddr *)&sin6, sizeof(sin6)))
+ goto out;
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_sock_unix(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_un addr;
+ struct bpf_link *link;
+ char expected[256];
+ char sun_path[108];
+ int server_fd = -1;
+ int opt = 1;
+ socklen_t optlen = sizeof(opt);
+ int err;
+
+ snprintf(sun_path, sizeof(sun_path), "/root/tmp/bpf_audit_test_%d.sock",
+ getpid());
+
+ /* Ensure directory exists */
+ mkdir("/root/tmp", 0755);
+ unlink(sun_path);
+
+ link = bpf_program__attach_lsm(skel->progs.test_sock_unix);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ snprintf(expected, sizeof(expected), "cause=\"sock_unix\" path=\"%s\"",
+ sun_path);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS, expected, 1);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sun_path, sizeof(addr.sun_path) - 1);
+
+ server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (!ASSERT_OK_FD(server_fd, "socket"))
+ goto out;
+
+ err = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (!ASSERT_OK(err, "bind"))
+ goto out;
+
+ err = getsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, &optlen);
+ ASSERT_OK(err, "getsockopt");
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ if (server_fd >= 0)
+ close(server_fd);
+ unlink(sun_path);
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_file(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ int err;
+ int fd;
+
+ link = bpf_program__attach_lsm(skel->progs.test_file);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"file\" path=\"/dev/null\" dev=\"devtmpfs\" ino=4",
+ 1);
+
+ fd = open("/dev/null", O_RDONLY);
+ close(fd);
+ if (!ASSERT_OK_FD(fd, "open(/dev/null)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_path(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ int err;
+ int fd;
+
+ link = bpf_program__attach_lsm(skel->progs.test_file_path);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"path\" path=\"/dev/null\" dev=\"devtmpfs\" ino=4",
+ 1);
+
+ fd = open("/dev/null", O_RDONLY);
+ close(fd);
+ if (!ASSERT_OK_FD(fd, "open(/dev/null)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_dentry(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ char expected[128];
+ char buf[64];
+ int err;
+
+ link = bpf_program__attach_lsm(skel->progs.test_dentry);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ snprintf(expected, sizeof(expected),
+ "cause=\"dentry\" name=\"exe\" dev=");
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS, expected, 1);
+
+ /* readlink triggers inode_readlink hook */
+ err = readlink("/proc/self/exe", buf, sizeof(buf));
+ if (!ASSERT_GT(err, 0, "readlink(/proc/self/exe)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_inode(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ char expected[128];
+ struct stat st;
+ int err;
+ int fd;
+
+ if (!ASSERT_OK(stat("/dev/null", &st), "stat(/dev/null)"))
+ return;
+
+ link = bpf_program__attach_lsm(skel->progs.test_inode);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ snprintf(expected, sizeof(expected),
+ "cause=\"inode\" name=\"null\" dev=\"devtmpfs\" ino=%lu",
+ st.st_ino);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS, expected, 1);
+
+ fd = open("/dev/null", O_RDONLY);
+ close(fd);
+ if (!ASSERT_OK_FD(fd, "open(/dev/null)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_task(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ char expected[128];
+ pid_t pid;
+ int err;
+
+ pid = getpid();
+
+ link = bpf_program__attach_lsm(skel->progs.test_task);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ snprintf(expected, sizeof(expected),
+ "cause=\"task\" opid=%d ocomm=\"test_progs\"", pid);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS, expected, 1);
+
+ err = getpgid(pid);
+ if (!ASSERT_GT(err, -1, "pid pgid match"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_cap(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ int err;
+ int fd;
+
+ link = bpf_program__attach_lsm(skel->progs.test_cap);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"cap\" capability=", 1);
+
+ fd = open("/proc/kallsyms", O_RDONLY);
+ close(fd);
+ if (!ASSERT_OK_FD(fd, "open(/proc/kallsyms)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_ioctl_op(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ char expected[128];
+ struct stat st;
+ int err;
+ int fd;
+
+ if (!ASSERT_OK(stat("/dev/null", &st), "stat(/dev/null)"))
+ return;
+
+ link = bpf_program__attach_lsm(skel->progs.test_ioctl_op);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ snprintf(expected, sizeof(expected),
+ "cause=\"ioctl_op\" path=\"/dev/null\" dev=\"devtmpfs\" ino=%lu ioctlcmd=0x%x",
+ st.st_ino, TCGETS);
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS, expected, 1);
+
+ fd = open("/dev/null", O_RDONLY);
+ if (!ASSERT_OK_FD(fd, "open(/dev/null)"))
+ goto out;
+
+ /* ioctl will fail with ENOTTY but the LSM hook fires regardless */
+ ioctl(fd, TCGETS, NULL);
+ close(fd);
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void test_audit_log_sleepable(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct bpf_link *link;
+ int err;
+ int fd;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sleepable);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"sleepable\" path=\"/dev/null\" dev=\"devtmpfs\" ino=4",
+ 1);
+
+ fd = open("/dev/null", O_RDONLY);
+ close(fd);
+ if (!ASSERT_OK_FD(fd, "open(/dev/null)"))
+ goto out;
+
+ err = audit_observer_wait(obs);
+ ASSERT_OK(err, "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+out:
+ bpf_link__destroy(link);
+}
+
+static void
+test_audit_log_sockaddr_both_null(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_in sin = addr4();
+ struct bpf_link *link;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sockaddr_both_null);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ /* Should see cause but no saddr/daddr since both were NULL */
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"sockaddr_both_null\"", 1);
+
+ bind_connect((const struct sockaddr *)&sin, sizeof(sin));
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+ bpf_link__destroy(link);
+}
+
+static void
+test_audit_log_sockaddr_small_addrlen(struct audit_observer *obs,
+ struct test_lsm_audit_kfuncs *skel)
+{
+ struct sockaddr_in sin = addr4();
+ struct bpf_link *link;
+
+ link = bpf_program__attach_lsm(skel->progs.test_sockaddr_small_addrlen);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ return;
+
+ audit_observer_reset(obs);
+
+ /* Should see cause but no saddr since addrlen was too small */
+ audit_observer_expect(obs, AUDIT_BPF_LSM_ACCESS,
+ "cause=\"sockaddr_small_addrlen\"", 1);
+
+ bind_connect((const struct sockaddr *)&sin, sizeof(sin));
+
+ ASSERT_OK(audit_observer_wait(obs), "audit_observer_wait");
+ ASSERT_TRUE(audit_observer_check_satisfied(obs),
+ "all expectations met");
+
+ bpf_link__destroy(link);
+}
+
+void test_lsm_audit_kfuncs(void)
+{
+ struct test_lsm_audit_kfuncs *skel = NULL;
+ struct audit_observer obs;
+ FILE *log = NULL;
+ int audit_fd;
+
+ audit_fd = audit_init();
+ if (!ASSERT_GE(audit_fd, 0, "audit_init"))
+ return;
+
+ if (env.verbosity > VERBOSE_NONE)
+ log = env.stdout_saved;
+
+ audit_observer_init(&obs, audit_fd, log, 500);
+
+ skel = test_lsm_audit_kfuncs__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel load"))
+ goto close_prog;
+
+ if (test__start_subtest("net")) {
+ test_audit_log_sockaddr_src(&obs, skel);
+ test_audit_log_sockaddr_dest(&obs, skel);
+ test_audit_log_sockaddr_both_null(&obs, skel);
+ test_audit_log_sockaddr_small_addrlen(&obs, skel);
+ test_audit_log_sock(&obs, skel);
+ test_audit_log_sock_unix(&obs, skel);
+ }
+
+ if (test__start_subtest("file")) {
+ test_audit_log_file(&obs, skel);
+ test_audit_log_path(&obs, skel);
+ test_audit_log_dentry(&obs, skel);
+ test_audit_log_inode(&obs, skel);
+ }
+
+ if (test__start_subtest("task")) {
+ test_audit_log_task(&obs, skel);
+ test_audit_log_cap(&obs, skel);
+ }
+
+ if (test__start_subtest("ioctl"))
+ test_audit_log_ioctl_op(&obs, skel);
+
+ if (test__start_subtest("sleepable"))
+ test_audit_log_sleepable(&obs, skel);
+
+close_prog:
+ test_lsm_audit_kfuncs__destroy(skel);
+ audit_cleanup(audit_fd);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_lsm_audit_kfuncs.c b/tools/testing/selftests/bpf/progs/test_lsm_audit_kfuncs.c
new file mode 100644
index 0000000000000000000000000000000000000000..952ba09fce638f3bd14c18060a5baa3ccaec19ca
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_lsm_audit_kfuncs.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Cloudflare */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include <errno.h>
+
+#define AF_UNIX 1
+#define AF_INET 2
+#define AF_INET6 10
+
+char _license[] SEC("license") = "GPL";
+
+SEC("lsm/socket_bind")
+int BPF_PROG(test_sockaddr_src, struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ switch (address->sa_family) {
+ case AF_INET:
+ bpf_audit_log_cause(ac, "bind4");
+ break;
+ case AF_INET6:
+ bpf_audit_log_cause(ac, "bind6");
+ }
+
+ bpf_audit_log_net_sockaddr(ac, 1, address, NULL, addrlen);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/socket_connect")
+int BPF_PROG(test_sockaddr_dest, struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ switch (address->sa_family) {
+ case AF_INET:
+ bpf_audit_log_cause(ac, "connect4");
+ break;
+ case AF_INET6:
+ bpf_audit_log_cause(ac, "connect6");
+ }
+
+ bpf_audit_log_net_sockaddr(ac, 1, NULL, address, addrlen);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/socket_bind")
+int BPF_PROG(test_sockaddr_both_null, struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "sockaddr_both_null");
+ bpf_audit_log_net_sockaddr(ac, 1, NULL, NULL, addrlen);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/socket_bind")
+int BPF_PROG(test_sockaddr_small_addrlen, struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct bpf_audit_context *ac;
+
+ if (address->sa_family != AF_INET)
+ return -EINVAL;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "sockaddr_small_addrlen");
+ bpf_audit_log_net_sockaddr(ac, 1, address, NULL, 1);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/socket_getsockopt")
+int BPF_PROG(test_sock, struct socket *sock, int level, int optname)
+{
+ struct bpf_audit_context *ac;
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return -EINVAL;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ switch (sk->__sk_common.skc_family) {
+ case AF_INET:
+ bpf_audit_log_cause(ac, "sock4");
+ break;
+ case AF_INET6:
+ bpf_audit_log_cause(ac, "sock6");
+ }
+
+ bpf_audit_log_net_sock(ac, 1, sock);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/socket_getsockopt")
+int BPF_PROG(test_sock_unix, struct socket *sock, int level, int optname)
+{
+ struct bpf_audit_context *ac;
+ struct sock *sk = sock->sk;
+
+ if (!sk || sk->__sk_common.skc_family != AF_UNIX)
+ return -EINVAL;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "sock_unix");
+ bpf_audit_log_net_sock(ac, 0, sock);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/file_open")
+int BPF_PROG(test_file, struct file *file)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "file");
+ bpf_audit_log_file(ac, file);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/file_open")
+int BPF_PROG(test_file_path, struct file *file)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "path");
+ bpf_audit_log_path(ac, &file->f_path);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/inode_readlink")
+int BPF_PROG(test_dentry, struct dentry *dentry)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "dentry");
+ bpf_audit_log_dentry(ac, dentry);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/file_open")
+int BPF_PROG(test_inode, struct file *file)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "inode");
+ bpf_audit_log_inode(ac, file->f_inode);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/task_getpgid")
+int BPF_PROG(test_task, struct task_struct *task)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "task");
+ bpf_audit_log_task(ac, task);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/capable")
+int BPF_PROG(test_cap, const struct cred *cred, struct user_namespace *ns,
+ int cap, unsigned int opts)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "cap");
+ bpf_audit_log_cap(ac, cap);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm/file_ioctl")
+int BPF_PROG(test_ioctl_op, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "ioctl_op");
+ bpf_audit_log_ioctl_op(ac, file, cmd);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
+SEC("lsm.s/file_open")
+int BPF_PROG(test_sleepable, struct file *file)
+{
+ struct bpf_audit_context *ac;
+
+ ac = bpf_audit_log_start();
+ if (!ac)
+ return -ENOMEM;
+
+ bpf_audit_log_cause(ac, "sleepable");
+ bpf_audit_log_file(ac, file);
+ bpf_audit_log_end(ac);
+ return 0;
+}
+
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
2026-03-11 21:31 [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Frederick Lawler
` (3 preceding siblings ...)
2026-03-11 21:31 ` [PATCH RFC bpf-next 4/4] selftests/bpf: Add lsm_audit_kfuncs tests Frederick Lawler
@ 2026-03-17 2:43 ` Kumar Kartikeya Dwivedi
2026-03-18 1:15 ` Alexei Starovoitov
2026-03-18 17:34 ` Frederick Lawler
4 siblings, 2 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 2:43 UTC (permalink / raw)
To: Frederick Lawler
Cc: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack,
linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team
On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
>
> The motivation behind the change is to give BPF LSM developers the
> ability to report accesses via the audit subsystem much like how LSMs
> operate today.
>
> Series:
>
> Patch 1: Introduces bpf_audit_*() kfuncs
> Patch 2: Enables bpf_audit_*() kfuns
> Patch 3: Prepares audit helpers used for testing
> Patch 4: Adds self tests
>
> Documentation will be added when this becomes a versioned series.
>
> Key features:
>
> 1. Audit logs include type=AUDIT_BPF_LSM_ACCESS, BPF program ID, and comm
> that triggered the hook by default
>
> We wanted audit log consumers to be able to track who and what created
> the entry. prog-id=%d is already used for BPF LOAD/UNLOAD logs, thus
> is reused here for this distinction. Though, it may be better to use
> the tag instead to capture which _specific_ version of the program
> made the log, since prog-id can be reused.
I think just id is fine. Whoever is watching the prog can also watch
for LOAD/UNLOAD events and detect any recycling.
>
> 2. Leverages BPF KF_AQUIRE/KF_RELEASE semantics to force use of
> bpf_audit_log_end().
>
> One side effect of this decision is that the BPF documentation states
> that these flags allow the pointer to struct bpf_audit_context to be
> stored in a map, and then exchanged through bpf_kptr_xchg(). However,
Yes, it is only allowed if you give a dtor. No dtor, then no kptr_xchg.
> there's prior work with net/netfilter/nf_conntrack_bpf.c such that the
> struct is not exposed as a kptr to support that functionality nor is
> that supplying a dtor function. The verifier will not allow this use case
> due to not exposing the __kptr. Ideally, we don't want the pointer to
> be exchanged anyway because the reporting program can become ambiguous.
> I am sure there are other edge cases WRT to keeping the audit buffer in a
> strange state too that I cannot think of at this moment.
>
> 3. All bpf_audit_log_*() functions are destructive
>
> The audit subsystem allows for AUDIT_FAIL_PANIC to be set when the
> subsystem can detect that missing events. Further, some call paths may
> invoke a BUG_ON(). Therefore all the functions are marked destructive.
I think the first part makes sense (i.e., the policy simply configured
the system to panic on failure).
However, in the second case, if the program is somehow able to trigger
BUG_ON() relied upon for internal invariants, it would be considered
broken.
I tried grepping through and didn't find anything that would cause
this, hence the whole thing about BUG_ON() in the cover letter only
adds to confusion.
Please drop it or describe cases which you were concerned about.
>
> 4. Functions are callable once per bpf_audit_context
>
> The rationale for this was to prevent abuse. Logs with repeated fields
> are not helpful, and may not be handled by user space audit coherently.
>
This rationale feels weak. What abuse are we talking about?
The LSM program is already written by a trusted entity.
> This is in the same vein as not providing a audit_format() wrapper.
>
I think the format() helper would allow for more flexibility.
I would like to hear what others think, but IMO we may not even want
to hardcode any fields by default and let the program decide what to
report.
prog-id, pid, comm, everything can be logged by the program itself, in
whatever order.
> Similarly, some functions such as bpf_audit_log_path() and
> bpf_audit_log_file() report the same information, thus can be
> interchangeable in use.
>
> 5. API wraps security/lsm_audit.c
>
> lsm_audit.c functions are multiplexed and not handled by BPF verifier
> very well, thus the wrapped functions are isolated to their sole
> purpose for use within hooks.
This part makes sense. I don't think we can (or should) support the
same multiplexing.
>
> Key considerations:
>
> 1. Audit field ordering
>
> AFAIK, user space audit is particular about what fields are
> present and their order. This patch series does not address ordering.
>
> My assumption is that the first three fields: type, prog-id, pid, comm
> are well known, and user space can make an assumption that other
> fields after those can appear in any order.
>
> If that is not acceptable, I would propose that we leverage the struct
> common_audit_data type order to be the order--much like how the type is
> used for log_once() functionality.
>
> I am open to other ideas.
>
> Signed-off-by: Frederick Lawler <fred@cloudflare.com>
> ---
> Frederick Lawler (4):
> audit: Implement bpf_audit_log_*() wrappers
> audit/security: Enable audit BPF kfuncs
> selftests/bpf: Add audit helpers for BPF tests
> selftests/bpf: Add lsm_audit_kfuncs tests
>
> include/linux/lsm_audit.h | 1 +
> include/uapi/linux/audit.h | 1 +
> security/Makefile | 2 +
> security/lsm_audit_kfuncs.c | 306 +++++++++++
> tools/testing/selftests/bpf/Makefile | 3 +-
> tools/testing/selftests/bpf/audit_helpers.c | 281 ++++++++++
> tools/testing/selftests/bpf/audit_helpers.h | 55 ++
> .../selftests/bpf/prog_tests/lsm_audit_kfuncs.c | 598 +++++++++++++++++++++
> .../selftests/bpf/progs/test_lsm_audit_kfuncs.c | 263 +++++++++
> 9 files changed, 1509 insertions(+), 1 deletion(-)
> ---
> base-commit: ca0f39a369c5f927c3d004e63a5a778b08a9df94
> change-id: 20260105-bpf-auditd-send-message-4a883067aab8
>
> Best regards,
> --
> Frederick Lawler <fred@cloudflare.com>
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
2026-03-17 2:43 ` [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Kumar Kartikeya Dwivedi
@ 2026-03-18 1:15 ` Alexei Starovoitov
2026-03-18 17:49 ` Frederick Lawler
2026-03-18 17:34 ` Frederick Lawler
1 sibling, 1 reply; 12+ messages in thread
From: Alexei Starovoitov @ 2026-03-18 1:15 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: Frederick Lawler, Paul Moore, James Morris, Serge E. Hallyn,
Eric Paris, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack, LKML,
LSM List, audit, bpf, open list:KERNEL SELFTEST FRAMEWORK,
kernel-team
On Mon, Mar 16, 2026 at 7:44 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
> >
> > The motivation behind the change is to give BPF LSM developers the
> > ability to report accesses via the audit subsystem much like how LSMs
> > operate today.
Sure, but bpf lsm-s don't need to follow such conventions.
audit is nothing but a message passing from kernel to user space
and done in a very inefficient way by wrapping strings into skb/netlink.
bpf progs can do this message passing already via various ways:
perfbuf, ringbuf, streams.
Teach your user space to consume one of them.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
2026-03-18 1:15 ` Alexei Starovoitov
@ 2026-03-18 17:49 ` Frederick Lawler
2026-03-18 20:55 ` Alexei Starovoitov
0 siblings, 1 reply; 12+ messages in thread
From: Frederick Lawler @ 2026-03-18 17:49 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Kumar Kartikeya Dwivedi, Paul Moore, James Morris,
Serge E. Hallyn, Eric Paris, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev,
Hao Luo, Jiri Olsa, Shuah Khan, Mickaël Salaün,
Günther Noack, LKML, LSM List, audit, bpf,
open list:KERNEL SELFTEST FRAMEWORK, kernel-team
Hi Alexei,
On Tue, Mar 17, 2026 at 06:15:59PM -0700, Alexei Starovoitov wrote:
> On Mon, Mar 16, 2026 at 7:44 PM Kumar Kartikeya Dwivedi
> <memxor@gmail.com> wrote:
> >
> > On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
> > >
> > > The motivation behind the change is to give BPF LSM developers the
> > > ability to report accesses via the audit subsystem much like how LSMs
> > > operate today.
>
> Sure, but bpf lsm-s don't need to follow such conventions.
> audit is nothing but a message passing from kernel to user space
> and done in a very inefficient way by wrapping strings into skb/netlink.
> bpf progs can do this message passing already via various ways:
> perfbuf, ringbuf, streams.
> Teach your user space to consume one of them.
Audit provides additional functionality by keeping messages close to the
syscall, and in-line with other messages for that syscall. Aside from
that, BPF is arguably too flexible. I'm sure it's already understood,
but the idea here is to reduce bespoke logging implementations and reduce
attack surface for violation reporting. Audit is already provided by the
kernel and a well leveraged pipeline.
WRT to performance characteristics, would you rather see this
implementation leverage those maps in a way to reduce that, background
workers, or something else? This is something we've considered, but left out
of RFC to collect more opinion on perf considerations.
Thanks for bring this up. I forgot to include that in the RFC cover, sorry.
Best,
Fred
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
2026-03-18 17:49 ` Frederick Lawler
@ 2026-03-18 20:55 ` Alexei Starovoitov
0 siblings, 0 replies; 12+ messages in thread
From: Alexei Starovoitov @ 2026-03-18 20:55 UTC (permalink / raw)
To: Frederick Lawler
Cc: Kumar Kartikeya Dwivedi, Paul Moore, James Morris,
Serge E. Hallyn, Eric Paris, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Yonghong Song, John Fastabend, KP Singh, Stanislav Fomichev,
Hao Luo, Jiri Olsa, Shuah Khan, Mickaël Salaün,
Günther Noack, LKML, LSM List, audit, bpf,
open list:KERNEL SELFTEST FRAMEWORK, kernel-team
On Wed, Mar 18, 2026 at 10:49 AM Frederick Lawler <fred@cloudflare.com> wrote:
>
> Hi Alexei,
>
> On Tue, Mar 17, 2026 at 06:15:59PM -0700, Alexei Starovoitov wrote:
> > On Mon, Mar 16, 2026 at 7:44 PM Kumar Kartikeya Dwivedi
> > <memxor@gmail.com> wrote:
> > >
> > > On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
> > > >
> > > > The motivation behind the change is to give BPF LSM developers the
> > > > ability to report accesses via the audit subsystem much like how LSMs
> > > > operate today.
> >
> > Sure, but bpf lsm-s don't need to follow such conventions.
> > audit is nothing but a message passing from kernel to user space
> > and done in a very inefficient way by wrapping strings into skb/netlink.
> > bpf progs can do this message passing already via various ways:
> > perfbuf, ringbuf, streams.
> > Teach your user space to consume one of them.
>
> Audit provides additional functionality by keeping messages close to the
> syscall, and in-line with other messages for that syscall. Aside from
> that, BPF is arguably too flexible. I'm sure it's already understood,
> but the idea here is to reduce bespoke logging implementations and reduce
> attack surface for violation reporting. Audit is already provided by the
> kernel and a well leveraged pipeline.
Sorry, I don't buy any of these arguments.
Nack.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
2026-03-17 2:43 ` [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs Kumar Kartikeya Dwivedi
2026-03-18 1:15 ` Alexei Starovoitov
@ 2026-03-18 17:34 ` Frederick Lawler
1 sibling, 0 replies; 12+ messages in thread
From: Frederick Lawler @ 2026-03-18 17:34 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Shuah Khan, Mickaël Salaün, Günther Noack,
linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
kernel-team
Hi Kumar,
On Tue, Mar 17, 2026 at 03:43:36AM +0100, Kumar Kartikeya Dwivedi wrote:
> On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
> > 3. All bpf_audit_log_*() functions are destructive
> >
> > The audit subsystem allows for AUDIT_FAIL_PANIC to be set when the
> > subsystem can detect that missing events. Further, some call paths may
> > invoke a BUG_ON(). Therefore all the functions are marked destructive.
>
> I think the first part makes sense (i.e., the policy simply configured
> the system to panic on failure).
> However, in the second case, if the program is somehow able to trigger
> BUG_ON() relied upon for internal invariants, it would be considered
> broken.
> I tried grepping through and didn't find anything that would cause
> this, hence the whole thing about BUG_ON() in the cover letter only
> adds to confusion.
> Please drop it or describe cases which you were concerned about.
>
bpf_audit_log_cause()
-> audit_log_untrustedstring()
-> audit_log_n_untrustedstring()
-> audit_log_n_hex()
Was the primary call chain I was referring to, and yes this relies on
internal invariant of the SKB existing. I can remove from cover.
> >
> > 4. Functions are callable once per bpf_audit_context
> >
> > The rationale for this was to prevent abuse. Logs with repeated fields
> > are not helpful, and may not be handled by user space audit coherently.
> >
>
> This rationale feels weak. What abuse are we talking about?
> The LSM program is already written by a trusted entity.
>
I learned through off-list discussions that there's "undocumented" or
"unspoken" rules of log formatting and how auditd expects logs. Without
knowing exactly what these rules are, I can't provide any stronger
arguments other than to reduce duplication of fields in messages, or
exclude wrappers that supply unverified data.
WRT to unverified data, the socket wrappers accepting a netinf int.
In that case, the variable is at least verified via lookup to
report the interface in which it belongs. I left out wrapping
LSM_AUDIT_DATA_ANONINODE due to the passing of arbitrary string,
LSM_AUDIT_DATA_IPC arbitrary int. Others like for infiniband,
lockdown, nlmsgtype, were left out due to specificity, and not general
purpose usage.
Some of the rationale behind this is _not_ trusting BPF LSM authors as
much due to BPF LSM flexibility and accommodating user space audit.
Best,
Fred
^ permalink raw reply [flat|nested] 12+ messages in thread