[PATCH 1/4] ns: add bpf hooks

Christian Brauner posted 4 patches 1 month, 1 week ago
[PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month, 1 week ago
Add the three namespace lifecycle hooks and make them available to bpf
lsm program types. This allows bpf to supervise namespace creation. I'm
in the process of adding various "universal truth" bpf programs to
systemd that will make use of this. This e.g., allows to lock in a
program into a given set of namespaces.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
 kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
 kernel/nscommon.c       |  9 ++++++++-
 kernel/nsproxy.c        |  7 +++++++
 4 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index 643809cc78c3..5ae438fdf567 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -12,6 +12,9 @@
 #include <linux/bpf_verifier.h>
 #include <linux/lsm_hooks.h>
 
+struct ns_common;
+struct nsset;
+
 #ifdef CONFIG_BPF_LSM
 
 #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
@@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
 
 int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
 			     struct bpf_retval_range *range);
+
+int bpf_lsm_namespace_alloc(struct ns_common *ns);
+void bpf_lsm_namespace_free(struct ns_common *ns);
+int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
+
 int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
 				const struct bpf_dynptr *value_p, int flags);
 int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
@@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
 {
 	return false;
 }
+
+static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
+{
+	return 0;
+}
+static inline void bpf_lsm_namespace_free(struct ns_common *ns)
+{
+}
+static inline int bpf_lsm_namespace_install(struct nsset *nsset,
+					    struct ns_common *ns)
+{
+	return 0;
+}
 #endif /* CONFIG_BPF_LSM */
 
 #endif /* _LINUX_BPF_LSM_H */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 0c4a0c8e6f70..f6378db46220 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
 #include <linux/lsm_hook_defs.h>
 #undef LSM_HOOK
 
+__bpf_hook_start();
+
+__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
+{
+	return 0;
+}
+
+__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
+{
+}
+
+__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
+					  struct ns_common *ns)
+{
+	return 0;
+}
+
+__bpf_hook_end();
+
 #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
 BTF_SET_START(bpf_lsm_hooks)
 #include <linux/lsm_hook_defs.h>
 #undef LSM_HOOK
+BTF_ID(func, bpf_lsm_namespace_alloc)
+BTF_ID(func, bpf_lsm_namespace_free)
+BTF_ID(func, bpf_lsm_namespace_install)
 BTF_SET_END(bpf_lsm_hooks)
 
 BTF_SET_START(bpf_lsm_disabled_hooks)
@@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
 BTF_ID(func, bpf_lsm_task_setscheduler)
 BTF_ID(func, bpf_lsm_task_to_inode)
 BTF_ID(func, bpf_lsm_userns_create)
+BTF_ID(func, bpf_lsm_namespace_alloc)
+BTF_ID(func, bpf_lsm_namespace_install)
 BTF_SET_END(sleepable_lsm_hooks)
 
 BTF_SET_START(untrusted_lsm_hooks)
@@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
 BTF_ID(func, bpf_lsm_sk_free_security)
 #endif /* CONFIG_SECURITY_NETWORK */
 BTF_ID(func, bpf_lsm_task_free)
+BTF_ID(func, bpf_lsm_namespace_free)
 BTF_SET_END(untrusted_lsm_hooks)
 
 bool bpf_lsm_is_sleepable_hook(u32 btf_id)
diff --git a/kernel/nscommon.c b/kernel/nscommon.c
index bdc3c86231d3..c3613cab3d41 100644
--- a/kernel/nscommon.c
+++ b/kernel/nscommon.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
 
+#include <linux/bpf_lsm.h>
 #include <linux/ns_common.h>
 #include <linux/nstree.h>
 #include <linux/proc_ns.h>
@@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 		ret = proc_alloc_inum(&ns->inum);
 	if (ret)
 		return ret;
+
 	/*
 	 * Tree ref starts at 0. It's incremented when namespace enters
 	 * active use (installed in nsproxy) and decremented when all
@@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 		atomic_set(&ns->__ns_ref_active, 1);
 	else
 		atomic_set(&ns->__ns_ref_active, 0);
-	return 0;
+
+	ret = bpf_lsm_namespace_alloc(ns);
+	if (ret && !inum)
+		proc_free_inum(ns->inum);
+	return ret;
 }
 
 void __ns_common_free(struct ns_common *ns)
 {
+	bpf_lsm_namespace_free(ns);
 	proc_free_inum(ns->inum);
 }
 
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index 259c4b4f1eeb..5742f9664dbb 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -9,6 +9,7 @@
  *             Pavel Emelianov <xemul@openvz.org>
  */
 
+#include <linux/bpf_lsm.h>
 #include <linux/slab.h>
 #include <linux/export.h>
 #include <linux/nsproxy.h>
@@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
 
 static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
 {
+	int ret;
+
+	ret = bpf_lsm_namespace_install(nsset, ns);
+	if (ret)
+		return ret;
+
 	return ns->ops->install(nsset, ns);
 }
 

-- 
2.47.3
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Song Liu 1 month, 1 week ago
On Thu, Feb 19, 2026 at 4:38 PM Christian Brauner <brauner@kernel.org> wrote:
[...]
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
>
> +#include <linux/bpf_lsm.h>
>  #include <linux/ns_common.h>
>  #include <linux/nstree.h>
>  #include <linux/proc_ns.h>
> @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>                 ret = proc_alloc_inum(&ns->inum);
>         if (ret)
>                 return ret;
> +
>         /*
>          * Tree ref starts at 0. It's incremented when namespace enters
>          * active use (installed in nsproxy) and decremented when all
> @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>                 atomic_set(&ns->__ns_ref_active, 1);
>         else
>                 atomic_set(&ns->__ns_ref_active, 0);
> -       return 0;
> +
> +       ret = bpf_lsm_namespace_alloc(ns);
> +       if (ret && !inum)
> +               proc_free_inum(ns->inum);
> +       return ret;
>  }

If we change the hook as

   bpf_lsm_namespace_alloc(ns, inum);

We can move it to the beginning of __ns_common_init().
This change allows blocking __ns_common_init() before
it makes any changes to the ns. Is this a better approach?

Thanks,
Song

[...]
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month ago
On Tue, Feb 24, 2026 at 03:04:43PM -0800, Song Liu wrote:
> On Thu, Feb 19, 2026 at 4:38 PM Christian Brauner <brauner@kernel.org> wrote:
> [...]
> > @@ -1,6 +1,7 @@
> >  // SPDX-License-Identifier: GPL-2.0-only
> >  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
> >
> > +#include <linux/bpf_lsm.h>
> >  #include <linux/ns_common.h>
> >  #include <linux/nstree.h>
> >  #include <linux/proc_ns.h>
> > @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> >                 ret = proc_alloc_inum(&ns->inum);
> >         if (ret)
> >                 return ret;
> > +
> >         /*
> >          * Tree ref starts at 0. It's incremented when namespace enters
> >          * active use (installed in nsproxy) and decremented when all
> > @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> >                 atomic_set(&ns->__ns_ref_active, 1);
> >         else
> >                 atomic_set(&ns->__ns_ref_active, 0);
> > -       return 0;
> > +
> > +       ret = bpf_lsm_namespace_alloc(ns);
> > +       if (ret && !inum)
> > +               proc_free_inum(ns->inum);
> > +       return ret;
> >  }
> 
> If we change the hook as
> 
>    bpf_lsm_namespace_alloc(ns, inum);
> 
> We can move it to the beginning of __ns_common_init().
> This change allows blocking __ns_common_init() before
> it makes any changes to the ns. Is this a better approach?

I don't think it matters tbh. We have no control when exactly
__ns_common_init() is called. That's up to the containing namespace. We
can't rely on the namespace to have been correctly set up at this time.
My main goal was to have struct ns_common to be fully initialized
already so that direct access to it's field already makes sense.

The containing namespace my already have to rollback a bunch of stuff
anyway.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Song Liu 1 month ago
On Fri, Feb 27, 2026 at 2:28 AM Christian Brauner <brauner@kernel.org> wrote:
[...]
> >
> > If we change the hook as
> >
> >    bpf_lsm_namespace_alloc(ns, inum);
> >
> > We can move it to the beginning of __ns_common_init().
> > This change allows blocking __ns_common_init() before
> > it makes any changes to the ns. Is this a better approach?
>
> I don't think it matters tbh. We have no control when exactly
> __ns_common_init() is called. That's up to the containing namespace. We
> can't rely on the namespace to have been correctly set up at this time.
> My main goal was to have struct ns_common to be fully initialized
> already so that direct access to it's field already makes sense.

Good point on having ns_common initialized. Besides inum, we
should also pass ns_type and ops into the hook.

OTOH, shall we have the hook before proc_alloc_inum()? With
this change, the hook can block the operation before it causes
any contention on proc_inum_ida. IOW, how about we have:

@@ -71,6 +71,10 @@ int __ns_common_init(struct ns_common *ns, u32
ns_type, const struct proc_ns_ope
        ns_debug(ns, ops);
 #endif

+       ret = bpf_lsm_namespace_alloc(ns, inum);
+       if (ret)
+               return ret;
+
        if (inum)
                ns->inum = inum;
        else

With this change, ns is already initialized, except the inum.

WDYT?

Thanks,
Song

> The containing namespace my already have to rollback a bunch of stuff
> anyway.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month ago
On Fri, Feb 27, 2026 at 08:38:48AM -0800, Song Liu wrote:
> On Fri, Feb 27, 2026 at 2:28 AM Christian Brauner <brauner@kernel.org> wrote:
> [...]
> > >
> > > If we change the hook as
> > >
> > >    bpf_lsm_namespace_alloc(ns, inum);
> > >
> > > We can move it to the beginning of __ns_common_init().
> > > This change allows blocking __ns_common_init() before
> > > it makes any changes to the ns. Is this a better approach?
> >
> > I don't think it matters tbh. We have no control when exactly
> > __ns_common_init() is called. That's up to the containing namespace. We
> > can't rely on the namespace to have been correctly set up at this time.
> > My main goal was to have struct ns_common to be fully initialized
> > already so that direct access to it's field already makes sense.
> 
> Good point on having ns_common initialized. Besides inum, we
> should also pass ns_type and ops into the hook.

But why? The struct ns_common is already fully initialized when it is
passed to bpf_lsm_namespace_alloc() including ops, inum, ns_type etc.

> 
> OTOH, shall we have the hook before proc_alloc_inum()? With
> this change, the hook can block the operation before it causes
> any contention on proc_inum_ida. IOW, how about we have:

I think that contention is meaningless and I'd rather have struct
ns_common fully set up so that all fields can be accessed.

> 
> @@ -71,6 +71,10 @@ int __ns_common_init(struct ns_common *ns, u32
> ns_type, const struct proc_ns_ope
>         ns_debug(ns, ops);
>  #endif
> 
> +       ret = bpf_lsm_namespace_alloc(ns, inum);
> +       if (ret)
> +               return ret;
> +
>         if (inum)
>                 ns->inum = inum;
>         else
> 
> With this change, ns is already initialized, except the inum.
> 
> WDYT?
> 
> Thanks,
> Song
> 
> > The containing namespace my already have to rollback a bunch of stuff
> > anyway.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Song Liu 1 month ago
On Mon, Mar 2, 2026 at 1:46 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Fri, Feb 27, 2026 at 08:38:48AM -0800, Song Liu wrote:
> > On Fri, Feb 27, 2026 at 2:28 AM Christian Brauner <brauner@kernel.org> wrote:
> > [...]
> > > >
> > > > If we change the hook as
> > > >
> > > >    bpf_lsm_namespace_alloc(ns, inum);
> > > >
> > > > We can move it to the beginning of __ns_common_init().
> > > > This change allows blocking __ns_common_init() before
> > > > it makes any changes to the ns. Is this a better approach?
> > >
> > > I don't think it matters tbh. We have no control when exactly
> > > __ns_common_init() is called. That's up to the containing namespace. We
> > > can't rely on the namespace to have been correctly set up at this time.
> > > My main goal was to have struct ns_common to be fully initialized
> > > already so that direct access to it's field already makes sense.
> >
> > Good point on having ns_common initialized. Besides inum, we
> > should also pass ns_type and ops into the hook.
>
> But why? The struct ns_common is already fully initialized when it is
> passed to bpf_lsm_namespace_alloc() including ops, inum, ns_type etc.

I meant if we pull bpf_lsm_namespace_alloc() to the beginning of
__ns_common_init(), we need ns_type etc. because ns_common
is not fully initialized. IOW, I agree with your early comment.

> >
> > OTOH, shall we have the hook before proc_alloc_inum()? With
> > this change, the hook can block the operation before it causes
> > any contention on proc_inum_ida. IOW, how about we have:
>
> I think that contention is meaningless and I'd rather have struct
> ns_common fully set up so that all fields can be accessed.

If contention is not a concern, which I believe you know better
than I do, I think this patch works fine. So

Acked-by: Song Liu <song@kernel.org>

Thanks,
Song
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 month, 1 week ago
On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> Add the three namespace lifecycle hooks and make them available to bpf
> lsm program types. This allows bpf to supervise namespace creation. I'm
> in the process of adding various "universal truth" bpf programs to
> systemd that will make use of this. This e.g., allows to lock in a
> program into a given set of namespaces.
> 
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
>  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
>  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
>  kernel/nscommon.c       |  9 ++++++++-
>  kernel/nsproxy.c        |  7 +++++++
>  4 files changed, 61 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 643809cc78c3..5ae438fdf567 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -12,6 +12,9 @@
>  #include <linux/bpf_verifier.h>
>  #include <linux/lsm_hooks.h>
>  
> +struct ns_common;
> +struct nsset;
> +
>  #ifdef CONFIG_BPF_LSM
>  
>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
>  
>  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
>  			     struct bpf_retval_range *range);
> +
> +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> +void bpf_lsm_namespace_free(struct ns_common *ns);
> +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> +
>  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
>  				const struct bpf_dynptr *value_p, int flags);
>  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
>  {
>  	return false;
>  }
> +
> +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					    struct ns_common *ns)
> +{
> +	return 0;
> +}
>  #endif /* CONFIG_BPF_LSM */
>  
>  #endif /* _LINUX_BPF_LSM_H */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 0c4a0c8e6f70..f6378db46220 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
>  
> +__bpf_hook_start();
> +
> +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}

I'm wondering how you foresee this hook functioning in a scenario
where the BPF LSM program is attached to this new hook point, although
with its attachment type being set to BPF_LSM_CGROUP instead of
BPF_LSM_MAC? You probably wouldn't want to utilize something like
BPF_LSM_CGROUP for your specific use case, but as things stand
currently I don't believe there's anyhthing preventing you from using
BPF_LSM_CGROUP with a hook like bpf_lsm_namespace_free().

Notably, the BPF_LSM_CGROUP infrastructure is designed to execute BPF
programs based on the cgroup of the currently executing task. There
could be some surprises if the bpf_lsm_namespace_free() hook were to
ever be called from a context (e.g, kworker) other than the one
specified whilst attaching the BPF LSM program with type
BPF_LSM_CGROUP.

> +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					  struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__bpf_hook_end();
> +
>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
>  BTF_SET_START(bpf_lsm_hooks)
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_free)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(bpf_lsm_hooks)
>  
>  BTF_SET_START(bpf_lsm_disabled_hooks)
> @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
>  BTF_ID(func, bpf_lsm_task_setscheduler)
>  BTF_ID(func, bpf_lsm_task_to_inode)
>  BTF_ID(func, bpf_lsm_userns_create)
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(sleepable_lsm_hooks)
>  
>  BTF_SET_START(untrusted_lsm_hooks)
> @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
>  BTF_ID(func, bpf_lsm_sk_free_security)
>  #endif /* CONFIG_SECURITY_NETWORK */
>  BTF_ID(func, bpf_lsm_task_free)
> +BTF_ID(func, bpf_lsm_namespace_free)
>  BTF_SET_END(untrusted_lsm_hooks)
>  
>  bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> index bdc3c86231d3..c3613cab3d41 100644
> --- a/kernel/nscommon.c
> +++ b/kernel/nscommon.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/ns_common.h>
>  #include <linux/nstree.h>
>  #include <linux/proc_ns.h>
> @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		ret = proc_alloc_inum(&ns->inum);
>  	if (ret)
>  		return ret;
> +
>  	/*
>  	 * Tree ref starts at 0. It's incremented when namespace enters
>  	 * active use (installed in nsproxy) and decremented when all
> @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		atomic_set(&ns->__ns_ref_active, 1);
>  	else
>  		atomic_set(&ns->__ns_ref_active, 0);
> -	return 0;
> +
> +	ret = bpf_lsm_namespace_alloc(ns);
> +	if (ret && !inum)
> +		proc_free_inum(ns->inum);
> +	return ret;
>  }
>  
>  void __ns_common_free(struct ns_common *ns)
>  {
> +	bpf_lsm_namespace_free(ns);
>  	proc_free_inum(ns->inum);
>  }
>  
> diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> index 259c4b4f1eeb..5742f9664dbb 100644
> --- a/kernel/nsproxy.c
> +++ b/kernel/nsproxy.c
> @@ -9,6 +9,7 @@
>   *             Pavel Emelianov <xemul@openvz.org>
>   */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/slab.h>
>  #include <linux/export.h>
>  #include <linux/nsproxy.h>
> @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
>  
>  static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
>  {
> +	int ret;
> +
> +	ret = bpf_lsm_namespace_install(nsset, ns);
> +	if (ret)
> +		return ret;
> +
>  	return ns->ops->install(nsset, ns);
>  }
>  
> 
> -- 
> 2.47.3
>
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month ago
On Tue, Feb 24, 2026 at 01:35:11PM +0000, Matt Bobrowski wrote:
> On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > Add the three namespace lifecycle hooks and make them available to bpf
> > lsm program types. This allows bpf to supervise namespace creation. I'm
> > in the process of adding various "universal truth" bpf programs to
> > systemd that will make use of this. This e.g., allows to lock in a
> > program into a given set of namespaces.
> > 
> > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > ---
> >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> >  kernel/nscommon.c       |  9 ++++++++-
> >  kernel/nsproxy.c        |  7 +++++++
> >  4 files changed, 61 insertions(+), 1 deletion(-)
> > 
> > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > index 643809cc78c3..5ae438fdf567 100644
> > --- a/include/linux/bpf_lsm.h
> > +++ b/include/linux/bpf_lsm.h
> > @@ -12,6 +12,9 @@
> >  #include <linux/bpf_verifier.h>
> >  #include <linux/lsm_hooks.h>
> >  
> > +struct ns_common;
> > +struct nsset;
> > +
> >  #ifdef CONFIG_BPF_LSM
> >  
> >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> >  
> >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> >  			     struct bpf_retval_range *range);
> > +
> > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > +
> >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> >  				const struct bpf_dynptr *value_p, int flags);
> >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> >  {
> >  	return false;
> >  }
> > +
> > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > +					    struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> >  #endif /* CONFIG_BPF_LSM */
> >  
> >  #endif /* _LINUX_BPF_LSM_H */
> > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > index 0c4a0c8e6f70..f6378db46220 100644
> > --- a/kernel/bpf/bpf_lsm.c
> > +++ b/kernel/bpf/bpf_lsm.c
> > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> >  #include <linux/lsm_hook_defs.h>
> >  #undef LSM_HOOK
> >  
> > +__bpf_hook_start();
> > +
> > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +
> > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> 
> I'm wondering how you foresee this hook functioning in a scenario
> where the BPF LSM program is attached to this new hook point, although
> with its attachment type being set to BPF_LSM_CGROUP instead of
> BPF_LSM_MAC? You probably wouldn't want to utilize something like
> BPF_LSM_CGROUP for your specific use case, but as things stand
> currently I don't believe there's anyhthing preventing you from using
> BPF_LSM_CGROUP with a hook like bpf_lsm_namespace_free().

Oh, I very much would like this to be attachable to cgroups.

> Notably, the BPF_LSM_CGROUP infrastructure is designed to execute BPF
> programs based on the cgroup of the currently executing task. There
> could be some surprises if the bpf_lsm_namespace_free() hook were to
> ever be called from a context (e.g, kworker) other than the one
> specified whilst attaching the BPF LSM program with type
> BPF_LSM_CGROUP.

But isn't this then a generic problem? What about:

# RCU callbacks
security_cred_free
security_task_free
security_inode_free_security_rcu
security_bpf_prog_free
security_xfrm_policy_free_security
security_msg_queue_free_security
security_shm_free_security
security_sem_free_security
security_audit_rule_free
security_bdev_free_security
security_sk_free_security

# Workqueues
security_bpf_map_free
security_bpf_token_free
security_sb_free_security
security_file_free_security
security_file_release
security_xfrm_state_free_security

ignoring sofirq/hardirq for now.

So the only real problem I can see is that someone wants to do something
from a *_free() hook that isn't actually freeing but actual policy based
on the cgroup of @current? I find that hard to believe tbh. Fwiw,
bpf_lsm_namespace_free() is classified as untrusted because at that
point the outer namespace might already be blown away partially.
Effectively alloc() and free() hooks are mostly notification mechanisms
of creation/destructions. If you want to do actual policy you might have
to defer it until an actual operation is done.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 week, 3 days ago
On Fri, Feb 27, 2026 at 03:33:21PM +0100, Christian Brauner wrote:
> On Tue, Feb 24, 2026 at 01:35:11PM +0000, Matt Bobrowski wrote:
> > On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > > Add the three namespace lifecycle hooks and make them available to bpf
> > > lsm program types. This allows bpf to supervise namespace creation. I'm
> > > in the process of adding various "universal truth" bpf programs to
> > > systemd that will make use of this. This e.g., allows to lock in a
> > > program into a given set of namespaces.
> > > 
> > > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > > ---
> > >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> > >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> > >  kernel/nscommon.c       |  9 ++++++++-
> > >  kernel/nsproxy.c        |  7 +++++++
> > >  4 files changed, 61 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > > index 643809cc78c3..5ae438fdf567 100644
> > > --- a/include/linux/bpf_lsm.h
> > > +++ b/include/linux/bpf_lsm.h
> > > @@ -12,6 +12,9 @@
> > >  #include <linux/bpf_verifier.h>
> > >  #include <linux/lsm_hooks.h>
> > >  
> > > +struct ns_common;
> > > +struct nsset;
> > > +
> > >  #ifdef CONFIG_BPF_LSM
> > >  
> > >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> > >  
> > >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> > >  			     struct bpf_retval_range *range);
> > > +
> > > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > > +
> > >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> > >  				const struct bpf_dynptr *value_p, int flags);
> > >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> > >  {
> > >  	return false;
> > >  }
> > > +
> > > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > > +					    struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > >  #endif /* CONFIG_BPF_LSM */
> > >  
> > >  #endif /* _LINUX_BPF_LSM_H */
> > > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > > index 0c4a0c8e6f70..f6378db46220 100644
> > > --- a/kernel/bpf/bpf_lsm.c
> > > +++ b/kernel/bpf/bpf_lsm.c
> > > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> > >  #include <linux/lsm_hook_defs.h>
> > >  #undef LSM_HOOK
> > >  
> > > +__bpf_hook_start();
> > > +
> > > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > 
> > I'm wondering how you foresee this hook functioning in a scenario
> > where the BPF LSM program is attached to this new hook point, although
> > with its attachment type being set to BPF_LSM_CGROUP instead of
> > BPF_LSM_MAC? You probably wouldn't want to utilize something like
> > BPF_LSM_CGROUP for your specific use case, but as things stand
> > currently I don't believe there's anyhthing preventing you from using
> > BPF_LSM_CGROUP with a hook like bpf_lsm_namespace_free().
> 
> Oh, I very much would like this to be attachable to cgroups.
> 
> > Notably, the BPF_LSM_CGROUP infrastructure is designed to execute BPF
> > programs based on the cgroup of the currently executing task. There
> > could be some surprises if the bpf_lsm_namespace_free() hook were to
> > ever be called from a context (e.g, kworker) other than the one
> > specified whilst attaching the BPF LSM program with type
> > BPF_LSM_CGROUP.
> 
> But isn't this then a generic problem? What about:
> 
> # RCU callbacks
> security_cred_free
> security_task_free
> security_inode_free_security_rcu
> security_bpf_prog_free
> security_xfrm_policy_free_security
> security_msg_queue_free_security
> security_shm_free_security
> security_sem_free_security
> security_audit_rule_free
> security_bdev_free_security
> security_sk_free_security
> 
> # Workqueues
> security_bpf_map_free
> security_bpf_token_free
> security_sb_free_security
> security_file_free_security
> security_file_release
> security_xfrm_state_free_security
> 
> ignoring sofirq/hardirq for now.

I'd need to take a another deep look, but yeah, from what I can tell
this is a broader general issue for BPF LSM programs which happen to
also make use of the BPF_LSM_CGROUP attachment type.

> So the only real problem I can see is that someone wants to do something
> from a *_free() hook that isn't actually freeing but actual policy based
> on the cgroup of @current? I find that hard to believe tbh.
> Fwiw, bpf_lsm_namespace_free() is classified as untrusted because at
> that point the outer namespace might already be blown away
> partially.  Effectively alloc() and free() hooks are mostly
> notification mechanisms of creation/destructions. If you want to do
> actual policy you might have to defer it until an actual operation
> is done.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 month, 1 week ago
On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> Add the three namespace lifecycle hooks and make them available to bpf
> lsm program types. This allows bpf to supervise namespace creation. I'm
> in the process of adding various "universal truth" bpf programs to
> systemd that will make use of this. This e.g., allows to lock in a
> program into a given set of namespaces.
> 
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
>  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
>  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
>  kernel/nscommon.c       |  9 ++++++++-
>  kernel/nsproxy.c        |  7 +++++++
>  4 files changed, 61 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 643809cc78c3..5ae438fdf567 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -12,6 +12,9 @@
>  #include <linux/bpf_verifier.h>
>  #include <linux/lsm_hooks.h>
>  
> +struct ns_common;
> +struct nsset;
> +
>  #ifdef CONFIG_BPF_LSM
>  
>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
>  
>  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
>  			     struct bpf_retval_range *range);
> +
> +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> +void bpf_lsm_namespace_free(struct ns_common *ns);
> +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> +
>  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
>  				const struct bpf_dynptr *value_p, int flags);
>  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
>  {
>  	return false;
>  }
> +
> +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					    struct ns_common *ns)
> +{
> +	return 0;
> +}
>  #endif /* CONFIG_BPF_LSM */
>  
>  #endif /* _LINUX_BPF_LSM_H */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 0c4a0c8e6f70..f6378db46220 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
>  
> +__bpf_hook_start();
> +
> +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +
> +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					  struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__bpf_hook_end();

Is the usage of __bpf_hook_start()/__bpf_hook_end() strictly necessary
here? If so, why is that? My understanding was that they're only
needed in situations where public function prototypes don't exist
(e.g., BPF kfuncs).

>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
>  BTF_SET_START(bpf_lsm_hooks)
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_free)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(bpf_lsm_hooks)
>  
>  BTF_SET_START(bpf_lsm_disabled_hooks)
> @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
>  BTF_ID(func, bpf_lsm_task_setscheduler)
>  BTF_ID(func, bpf_lsm_task_to_inode)
>  BTF_ID(func, bpf_lsm_userns_create)
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(sleepable_lsm_hooks)
>  
>  BTF_SET_START(untrusted_lsm_hooks)
> @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
>  BTF_ID(func, bpf_lsm_sk_free_security)
>  #endif /* CONFIG_SECURITY_NETWORK */
>  BTF_ID(func, bpf_lsm_task_free)
> +BTF_ID(func, bpf_lsm_namespace_free)
>  BTF_SET_END(untrusted_lsm_hooks)
>  
>  bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> index bdc3c86231d3..c3613cab3d41 100644
> --- a/kernel/nscommon.c
> +++ b/kernel/nscommon.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/ns_common.h>
>  #include <linux/nstree.h>
>  #include <linux/proc_ns.h>
> @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		ret = proc_alloc_inum(&ns->inum);
>  	if (ret)
>  		return ret;
> +
>  	/*
>  	 * Tree ref starts at 0. It's incremented when namespace enters
>  	 * active use (installed in nsproxy) and decremented when all
> @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		atomic_set(&ns->__ns_ref_active, 1);
>  	else
>  		atomic_set(&ns->__ns_ref_active, 0);
> -	return 0;
> +
> +	ret = bpf_lsm_namespace_alloc(ns);
> +	if (ret && !inum)
> +		proc_free_inum(ns->inum);
> +	return ret;
>  }
>  
>  void __ns_common_free(struct ns_common *ns)
>  {
> +	bpf_lsm_namespace_free(ns);
>  	proc_free_inum(ns->inum);
>  }
>  
> diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> index 259c4b4f1eeb..5742f9664dbb 100644
> --- a/kernel/nsproxy.c
> +++ b/kernel/nsproxy.c
> @@ -9,6 +9,7 @@
>   *             Pavel Emelianov <xemul@openvz.org>
>   */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/slab.h>
>  #include <linux/export.h>
>  #include <linux/nsproxy.h>
> @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
>  
>  static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
>  {
> +	int ret;
> +
> +	ret = bpf_lsm_namespace_install(nsset, ns);
> +	if (ret)
> +		return ret;
> +
>  	return ns->ops->install(nsset, ns);
>  }
>  
> 
> -- 
> 2.47.3
>
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month ago
On Tue, Feb 24, 2026 at 01:16:01AM +0000, Matt Bobrowski wrote:
> On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > Add the three namespace lifecycle hooks and make them available to bpf
> > lsm program types. This allows bpf to supervise namespace creation. I'm
> > in the process of adding various "universal truth" bpf programs to
> > systemd that will make use of this. This e.g., allows to lock in a
> > program into a given set of namespaces.
> > 
> > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > ---
> >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> >  kernel/nscommon.c       |  9 ++++++++-
> >  kernel/nsproxy.c        |  7 +++++++
> >  4 files changed, 61 insertions(+), 1 deletion(-)
> > 
> > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > index 643809cc78c3..5ae438fdf567 100644
> > --- a/include/linux/bpf_lsm.h
> > +++ b/include/linux/bpf_lsm.h
> > @@ -12,6 +12,9 @@
> >  #include <linux/bpf_verifier.h>
> >  #include <linux/lsm_hooks.h>
> >  
> > +struct ns_common;
> > +struct nsset;
> > +
> >  #ifdef CONFIG_BPF_LSM
> >  
> >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> >  
> >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> >  			     struct bpf_retval_range *range);
> > +
> > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > +
> >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> >  				const struct bpf_dynptr *value_p, int flags);
> >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> >  {
> >  	return false;
> >  }
> > +
> > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > +					    struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> >  #endif /* CONFIG_BPF_LSM */
> >  
> >  #endif /* _LINUX_BPF_LSM_H */
> > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > index 0c4a0c8e6f70..f6378db46220 100644
> > --- a/kernel/bpf/bpf_lsm.c
> > +++ b/kernel/bpf/bpf_lsm.c
> > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> >  #include <linux/lsm_hook_defs.h>
> >  #undef LSM_HOOK
> >  
> > +__bpf_hook_start();
> > +
> > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +
> > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> > +
> > +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> > +					  struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +
> > +__bpf_hook_end();
> 
> Is the usage of __bpf_hook_start()/__bpf_hook_end() strictly necessary
> here? If so, why is that? My understanding was that they're only
> needed in situations where public function prototypes don't exist
> (e.g., BPF kfuncs).

I don't know. I just went by other sites that added bpf specific
functions. Seems like bpf specific functions I'm adding so I used the
hook annotation. If unneeded I happily drop it. I just need someone to
tell whether that's right and I can't infer from your "my understanding
[...]" phrasing whether that's an authoritative statement or an
expression of doubt.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 week, 3 days ago
On Fri, Feb 27, 2026 at 11:33:56AM +0100, Christian Brauner wrote:
> On Tue, Feb 24, 2026 at 01:16:01AM +0000, Matt Bobrowski wrote:
> > On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > > Add the three namespace lifecycle hooks and make them available to bpf
> > > lsm program types. This allows bpf to supervise namespace creation. I'm
> > > in the process of adding various "universal truth" bpf programs to
> > > systemd that will make use of this. This e.g., allows to lock in a
> > > program into a given set of namespaces.
> > > 
> > > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > > ---
> > >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> > >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> > >  kernel/nscommon.c       |  9 ++++++++-
> > >  kernel/nsproxy.c        |  7 +++++++
> > >  4 files changed, 61 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > > index 643809cc78c3..5ae438fdf567 100644
> > > --- a/include/linux/bpf_lsm.h
> > > +++ b/include/linux/bpf_lsm.h
> > > @@ -12,6 +12,9 @@
> > >  #include <linux/bpf_verifier.h>
> > >  #include <linux/lsm_hooks.h>
> > >  
> > > +struct ns_common;
> > > +struct nsset;
> > > +
> > >  #ifdef CONFIG_BPF_LSM
> > >  
> > >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> > >  
> > >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> > >  			     struct bpf_retval_range *range);
> > > +
> > > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > > +
> > >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> > >  				const struct bpf_dynptr *value_p, int flags);
> > >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> > >  {
> > >  	return false;
> > >  }
> > > +
> > > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > > +					    struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > >  #endif /* CONFIG_BPF_LSM */
> > >  
> > >  #endif /* _LINUX_BPF_LSM_H */
> > > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > > index 0c4a0c8e6f70..f6378db46220 100644
> > > --- a/kernel/bpf/bpf_lsm.c
> > > +++ b/kernel/bpf/bpf_lsm.c
> > > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> > >  #include <linux/lsm_hook_defs.h>
> > >  #undef LSM_HOOK
> > >  
> > > +__bpf_hook_start();
> > > +
> > > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > > +
> > > +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> > > +					  struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +__bpf_hook_end();
> > 
> > Is the usage of __bpf_hook_start()/__bpf_hook_end() strictly necessary
> > here? If so, why is that? My understanding was that they're only
> > needed in situations where public function prototypes don't exist
> > (e.g., BPF kfuncs).
> 
> I don't know. I just went by other sites that added bpf specific
> functions. Seems like bpf specific functions I'm adding so I used the
> hook annotation. If unneeded I happily drop it. I just need someone to
> tell whether that's right and I can't infer from your "my understanding
> [...]" phrasing whether that's an authoritative statement or an
> expression of doubt.

Truly apologies about the delay here Christian, I've been out of
office the last few weeks.

Initially an expression of doubt, but now an authoritative
statement. You do not need your new BPF LSM specific hooks wrapped
within __bpf_hook_start() and __bpf_hook_end(). Those are technically
for BPF kfuncs which are global functions, but are often only called
from a BPF program. The default BPF LSM hook definitions provided by
the LSM_HOOK() macro also aren't wrapped in __bpf_hook_start() and
__bpf_hook_end().
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Djalal Harouni 1 month, 1 week ago
On 2/20/26 01:38, Christian Brauner wrote:
> Add the three namespace lifecycle hooks and make them available to bpf
> lsm program types. This allows bpf to supervise namespace creation. I'm
> in the process of adding various "universal truth" bpf programs to
> systemd that will make use of this. This e.g., allows to lock in a
> program into a given set of namespaces.

Thank you Christian, so if this feature is added we will also
use it.

The commit log says lock in a given set of namespaces where I see
only setns path am I right? would it make sense to also have the
check around some callers of create_new_namespaces() where
appropriate befor nsproxy switch if we don't want to go deep, but
allow a bit of control or easy checks around
CLONE_NEWNS/mount/pivot_root fs combinations?

Or defering the combination checks to userspace makes more sense?

The other clone flags are presumably nested so safe, for userns
there is already a check, and cgroup+sb you added in the other
patch is great!

Thank you!


> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
>   include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
>   kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
>   kernel/nscommon.c       |  9 ++++++++-
>   kernel/nsproxy.c        |  7 +++++++
>   4 files changed, 61 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 643809cc78c3..5ae438fdf567 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -12,6 +12,9 @@
>   #include <linux/bpf_verifier.h>
>   #include <linux/lsm_hooks.h>
>   
> +struct ns_common;
> +struct nsset;
> +
>   #ifdef CONFIG_BPF_LSM
>   
>   #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
>   
>   int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
>   			     struct bpf_retval_range *range);
> +
> +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> +void bpf_lsm_namespace_free(struct ns_common *ns);
> +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> +
>   int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
>   				const struct bpf_dynptr *value_p, int flags);
>   int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
>   {
>   	return false;
>   }
> +
> +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					    struct ns_common *ns)
> +{
> +	return 0;
> +}
>   #endif /* CONFIG_BPF_LSM */
>   
>   #endif /* _LINUX_BPF_LSM_H */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 0c4a0c8e6f70..f6378db46220 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
>   #include <linux/lsm_hook_defs.h>
>   #undef LSM_HOOK
>   
> +__bpf_hook_start();
> +
> +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +
> +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					  struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__bpf_hook_end();
> +
>   #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
>   BTF_SET_START(bpf_lsm_hooks)
>   #include <linux/lsm_hook_defs.h>
>   #undef LSM_HOOK
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_free)
> +BTF_ID(func, bpf_lsm_namespace_install)
>   BTF_SET_END(bpf_lsm_hooks)
>   
>   BTF_SET_START(bpf_lsm_disabled_hooks)
> @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
>   BTF_ID(func, bpf_lsm_task_setscheduler)
>   BTF_ID(func, bpf_lsm_task_to_inode)
>   BTF_ID(func, bpf_lsm_userns_create)
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_install)
>   BTF_SET_END(sleepable_lsm_hooks)
>   
>   BTF_SET_START(untrusted_lsm_hooks)
> @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
>   BTF_ID(func, bpf_lsm_sk_free_security)
>   #endif /* CONFIG_SECURITY_NETWORK */
>   BTF_ID(func, bpf_lsm_task_free)
> +BTF_ID(func, bpf_lsm_namespace_free)
>   BTF_SET_END(untrusted_lsm_hooks)
>   
>   bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> index bdc3c86231d3..c3613cab3d41 100644
> --- a/kernel/nscommon.c
> +++ b/kernel/nscommon.c
> @@ -1,6 +1,7 @@
>   // SPDX-License-Identifier: GPL-2.0-only
>   /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
>   
> +#include <linux/bpf_lsm.h>
>   #include <linux/ns_common.h>
>   #include <linux/nstree.h>
>   #include <linux/proc_ns.h>
> @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>   		ret = proc_alloc_inum(&ns->inum);
>   	if (ret)
>   		return ret;
> +
>   	/*
>   	 * Tree ref starts at 0. It's incremented when namespace enters
>   	 * active use (installed in nsproxy) and decremented when all
> @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>   		atomic_set(&ns->__ns_ref_active, 1);
>   	else
>   		atomic_set(&ns->__ns_ref_active, 0);
> -	return 0;
> +
> +	ret = bpf_lsm_namespace_alloc(ns);
> +	if (ret && !inum)
> +		proc_free_inum(ns->inum);
> +	return ret;
>   }
>   
>   void __ns_common_free(struct ns_common *ns)
>   {
> +	bpf_lsm_namespace_free(ns);
>   	proc_free_inum(ns->inum);
>   }
>   
> diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> index 259c4b4f1eeb..5742f9664dbb 100644
> --- a/kernel/nsproxy.c
> +++ b/kernel/nsproxy.c
> @@ -9,6 +9,7 @@
>    *             Pavel Emelianov <xemul@openvz.org>
>    */
>   
> +#include <linux/bpf_lsm.h>
>   #include <linux/slab.h>
>   #include <linux/export.h>
>   #include <linux/nsproxy.h>
> @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
>   
>   static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
>   {
> +	int ret;
> +
> +	ret = bpf_lsm_namespace_install(nsset, ns);
> +	if (ret)
> +		return ret;
> +
>   	return ns->ops->install(nsset, ns);
>   }
>   
>
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month ago
On Mon, Feb 23, 2026 at 01:44:23PM +0100, Djalal Harouni wrote:
> On 2/20/26 01:38, Christian Brauner wrote:
> > Add the three namespace lifecycle hooks and make them available to bpf
> > lsm program types. This allows bpf to supervise namespace creation. I'm
> > in the process of adding various "universal truth" bpf programs to
> > systemd that will make use of this. This e.g., allows to lock in a
> > program into a given set of namespaces.
> 
> Thank you Christian, so if this feature is added we will also
> use it.
> 
> The commit log says lock in a given set of namespaces where I see
> only setns path am I right? would it make sense to also have the

Yes.

> check around some callers of create_new_namespaces() where
> appropriate befor nsproxy switch if we don't want to go deep, but
> allow a bit of control or easy checks around
> CLONE_NEWNS/mount/pivot_root fs combinations?

Yes, I have planned that but we will massage that codepath quite a bit
this cycle to deal with some races so I'd rather push this out for this
reason and also...

... I need to think about how exactly we should hook into that. Probably
when we already have assembled the new namespace set but then I want to
pass it to the hook in a way that I can guarantee KF_TRUSTED_ARGS so
callers can use the macros I have to cast from struct ns_common to
actual namespace type.

We will need additional per-ns type hooks in the future as well. Like,
One would very likely want to supervise writes of idmappings to a userns
and so we need to add hooks for that into /proc/<pid>/{g,u}id_map as
well... and setgroups now come to think of it.

An fwiw, I'm replacing pivot_root() this cycle and I expect userspace to
fade it out eventually. It's an insane system call that holds tasklist
lock to walk _all task_ on the system each time you switch the
container's rootfs just to mess with the pwd and root. That creates all
kinds of races and no container setup actually needs to do the pwd/root
replacement.

So it's really unneeded unless you do weird stuff like switching out the
rootfs in init_mnt_ns post early boot. Which is insane and can't work
for a lot of other reasons and the pwd/root rewrite doesn't solve
pinning via fds anyway so really that all needs to be Michael Myers'ed.

Next release MOVE_MOUNT_BENEATH will take over that job by making it
work with locked mounts and the rootfs.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 month, 1 week ago
On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> Add the three namespace lifecycle hooks and make them available to bpf
> lsm program types. This allows bpf to supervise namespace creation. I'm
> in the process of adding various "universal truth" bpf programs to
> systemd that will make use of this. This e.g., allows to lock in a
> program into a given set of namespaces.
> 
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
>  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
>  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
>  kernel/nscommon.c       |  9 ++++++++-
>  kernel/nsproxy.c        |  7 +++++++
>  4 files changed, 61 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 643809cc78c3..5ae438fdf567 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -12,6 +12,9 @@
>  #include <linux/bpf_verifier.h>
>  #include <linux/lsm_hooks.h>
>  
> +struct ns_common;
> +struct nsset;
> +
>  #ifdef CONFIG_BPF_LSM
>  
>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
>  
>  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
>  			     struct bpf_retval_range *range);
> +
> +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> +void bpf_lsm_namespace_free(struct ns_common *ns);
> +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> +
>  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
>  				const struct bpf_dynptr *value_p, int flags);
>  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
>  {
>  	return false;
>  }
> +
> +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					    struct ns_common *ns)
> +{
> +	return 0;
> +}
>  #endif /* CONFIG_BPF_LSM */
>  
>  #endif /* _LINUX_BPF_LSM_H */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 0c4a0c8e6f70..f6378db46220 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
>  
> +__bpf_hook_start();
> +
> +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> +{
> +}
> +
> +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> +					  struct ns_common *ns)
> +{
> +	return 0;
> +}
> +
> +__bpf_hook_end();
> +
>  #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
>  BTF_SET_START(bpf_lsm_hooks)
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_free)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(bpf_lsm_hooks)
>  
>  BTF_SET_START(bpf_lsm_disabled_hooks)
> @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
>  BTF_ID(func, bpf_lsm_task_setscheduler)
>  BTF_ID(func, bpf_lsm_task_to_inode)
>  BTF_ID(func, bpf_lsm_userns_create)
> +BTF_ID(func, bpf_lsm_namespace_alloc)
> +BTF_ID(func, bpf_lsm_namespace_install)
>  BTF_SET_END(sleepable_lsm_hooks)
>  
>  BTF_SET_START(untrusted_lsm_hooks)
> @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
>  BTF_ID(func, bpf_lsm_sk_free_security)
>  #endif /* CONFIG_SECURITY_NETWORK */
>  BTF_ID(func, bpf_lsm_task_free)
> +BTF_ID(func, bpf_lsm_namespace_free)
>  BTF_SET_END(untrusted_lsm_hooks)
>  
>  bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> index bdc3c86231d3..c3613cab3d41 100644
> --- a/kernel/nscommon.c
> +++ b/kernel/nscommon.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/ns_common.h>
>  #include <linux/nstree.h>
>  #include <linux/proc_ns.h>
> @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		ret = proc_alloc_inum(&ns->inum);
>  	if (ret)
>  		return ret;
> +
>  	/*
>  	 * Tree ref starts at 0. It's incremented when namespace enters
>  	 * active use (installed in nsproxy) and decremented when all
> @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>  		atomic_set(&ns->__ns_ref_active, 1);
>  	else
>  		atomic_set(&ns->__ns_ref_active, 0);
> -	return 0;
> +
> +	ret = bpf_lsm_namespace_alloc(ns);
> +	if (ret && !inum)
> +		proc_free_inum(ns->inum);
> +	return ret;
>  }
>  
>  void __ns_common_free(struct ns_common *ns)
>  {
> +	bpf_lsm_namespace_free(ns);
>  	proc_free_inum(ns->inum);
>  }
>  
> diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> index 259c4b4f1eeb..5742f9664dbb 100644
> --- a/kernel/nsproxy.c
> +++ b/kernel/nsproxy.c
> @@ -9,6 +9,7 @@
>   *             Pavel Emelianov <xemul@openvz.org>
>   */
>  
> +#include <linux/bpf_lsm.h>
>  #include <linux/slab.h>
>  #include <linux/export.h>
>  #include <linux/nsproxy.h>
> @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
>  
>  static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
>  {
> +	int ret;
> +
> +	ret = bpf_lsm_namespace_install(nsset, ns);
> +	if (ret)
> +		return ret;
> +
>  	return ns->ops->install(nsset, ns);
>  }

What's the reason for not adding these new hook points to the generic
set of hooks that are currently being exposed directly by the LSM
framework? Honestly, it seems a little odd to be providing
declarations/definitions for a set of new hook points which are to be
exclusively siloed to BPF LSM implementations only. I'd argue that
some other LSM implementations could very well find namespace
lifecycle events possibly interesting.
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Christian Brauner 1 month, 1 week ago
On Mon, Feb 23, 2026 at 10:36:19AM +0000, Matt Bobrowski wrote:
> On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > Add the three namespace lifecycle hooks and make them available to bpf
> > lsm program types. This allows bpf to supervise namespace creation. I'm
> > in the process of adding various "universal truth" bpf programs to
> > systemd that will make use of this. This e.g., allows to lock in a
> > program into a given set of namespaces.
> > 
> > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > ---
> >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> >  kernel/nscommon.c       |  9 ++++++++-
> >  kernel/nsproxy.c        |  7 +++++++
> >  4 files changed, 61 insertions(+), 1 deletion(-)
> > 
> > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > index 643809cc78c3..5ae438fdf567 100644
> > --- a/include/linux/bpf_lsm.h
> > +++ b/include/linux/bpf_lsm.h
> > @@ -12,6 +12,9 @@
> >  #include <linux/bpf_verifier.h>
> >  #include <linux/lsm_hooks.h>
> >  
> > +struct ns_common;
> > +struct nsset;
> > +
> >  #ifdef CONFIG_BPF_LSM
> >  
> >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> >  
> >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> >  			     struct bpf_retval_range *range);
> > +
> > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > +
> >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> >  				const struct bpf_dynptr *value_p, int flags);
> >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> >  {
> >  	return false;
> >  }
> > +
> > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > +					    struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> >  #endif /* CONFIG_BPF_LSM */
> >  
> >  #endif /* _LINUX_BPF_LSM_H */
> > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > index 0c4a0c8e6f70..f6378db46220 100644
> > --- a/kernel/bpf/bpf_lsm.c
> > +++ b/kernel/bpf/bpf_lsm.c
> > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> >  #include <linux/lsm_hook_defs.h>
> >  #undef LSM_HOOK
> >  
> > +__bpf_hook_start();
> > +
> > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +
> > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > +{
> > +}
> > +
> > +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> > +					  struct ns_common *ns)
> > +{
> > +	return 0;
> > +}
> > +
> > +__bpf_hook_end();
> > +
> >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
> >  BTF_SET_START(bpf_lsm_hooks)
> >  #include <linux/lsm_hook_defs.h>
> >  #undef LSM_HOOK
> > +BTF_ID(func, bpf_lsm_namespace_alloc)
> > +BTF_ID(func, bpf_lsm_namespace_free)
> > +BTF_ID(func, bpf_lsm_namespace_install)
> >  BTF_SET_END(bpf_lsm_hooks)
> >  
> >  BTF_SET_START(bpf_lsm_disabled_hooks)
> > @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
> >  BTF_ID(func, bpf_lsm_task_setscheduler)
> >  BTF_ID(func, bpf_lsm_task_to_inode)
> >  BTF_ID(func, bpf_lsm_userns_create)
> > +BTF_ID(func, bpf_lsm_namespace_alloc)
> > +BTF_ID(func, bpf_lsm_namespace_install)
> >  BTF_SET_END(sleepable_lsm_hooks)
> >  
> >  BTF_SET_START(untrusted_lsm_hooks)
> > @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
> >  BTF_ID(func, bpf_lsm_sk_free_security)
> >  #endif /* CONFIG_SECURITY_NETWORK */
> >  BTF_ID(func, bpf_lsm_task_free)
> > +BTF_ID(func, bpf_lsm_namespace_free)
> >  BTF_SET_END(untrusted_lsm_hooks)
> >  
> >  bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> > diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> > index bdc3c86231d3..c3613cab3d41 100644
> > --- a/kernel/nscommon.c
> > +++ b/kernel/nscommon.c
> > @@ -1,6 +1,7 @@
> >  // SPDX-License-Identifier: GPL-2.0-only
> >  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
> >  
> > +#include <linux/bpf_lsm.h>
> >  #include <linux/ns_common.h>
> >  #include <linux/nstree.h>
> >  #include <linux/proc_ns.h>
> > @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> >  		ret = proc_alloc_inum(&ns->inum);
> >  	if (ret)
> >  		return ret;
> > +
> >  	/*
> >  	 * Tree ref starts at 0. It's incremented when namespace enters
> >  	 * active use (installed in nsproxy) and decremented when all
> > @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> >  		atomic_set(&ns->__ns_ref_active, 1);
> >  	else
> >  		atomic_set(&ns->__ns_ref_active, 0);
> > -	return 0;
> > +
> > +	ret = bpf_lsm_namespace_alloc(ns);
> > +	if (ret && !inum)
> > +		proc_free_inum(ns->inum);
> > +	return ret;
> >  }
> >  
> >  void __ns_common_free(struct ns_common *ns)
> >  {
> > +	bpf_lsm_namespace_free(ns);
> >  	proc_free_inum(ns->inum);
> >  }
> >  
> > diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> > index 259c4b4f1eeb..5742f9664dbb 100644
> > --- a/kernel/nsproxy.c
> > +++ b/kernel/nsproxy.c
> > @@ -9,6 +9,7 @@
> >   *             Pavel Emelianov <xemul@openvz.org>
> >   */
> >  
> > +#include <linux/bpf_lsm.h>
> >  #include <linux/slab.h>
> >  #include <linux/export.h>
> >  #include <linux/nsproxy.h>
> > @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
> >  
> >  static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
> >  {
> > +	int ret;
> > +
> > +	ret = bpf_lsm_namespace_install(nsset, ns);
> > +	if (ret)
> > +		return ret;
> > +
> >  	return ns->ops->install(nsset, ns);
> >  }
> 
> What's the reason for not adding these new hook points to the generic
> set of hooks that are currently being exposed directly by the LSM
> framework? Honestly, it seems a little odd to be providing
> declarations/definitions for a set of new hook points which are to be
> exclusively siloed to BPF LSM implementations only. I'd argue that
> some other LSM implementations could very well find namespace
> lifecycle events possibly interesting.

The LSM layer is of the opinion that adding new security hooks is only
acceptable if an implementation for an in-tree LSM is provided alongside
it (cf. [1]). IOW, your bpf lsm needs are not sufficient justification
for adding new security hooks. So if you want to add security hooks that
a bpf lsm makes use of then you need to come up with an implementation
for another in-tree LSM.

However, a subsystem is free to add as much bpf support as it wants:
none, some, flamethrower mode. Cgroupfs has traditionally been very bpf
friendly. I maintain namespaces and rewrote the infra allowing me to
manage them uniformly now. bpf literally just needs an attach point. I
could also just add fmodret tracepoints and achieve the same result.

The same way you add bpf kfuncs to support access to functionality that
put you way past what an in-tree use would be able do. The question is
whether you want such capabilities to be bounded by in-tree users as
well.

Either a bpf lsm is an inextensible fixture bound to the scope of
security_* or you allow subsystems open to it to add functionality just
like adding a kfuncs is.

[1]: https://patch.msgid.link/20260216-work-security-namespace-v1-1-075c28758e1f@kernel.org
Re: [PATCH 1/4] ns: add bpf hooks
Posted by Matt Bobrowski 1 month, 1 week ago
On Mon, Feb 23, 2026 at 12:12:28PM +0100, Christian Brauner wrote:
> On Mon, Feb 23, 2026 at 10:36:19AM +0000, Matt Bobrowski wrote:
> > On Fri, Feb 20, 2026 at 01:38:29AM +0100, Christian Brauner wrote:
> > > Add the three namespace lifecycle hooks and make them available to bpf
> > > lsm program types. This allows bpf to supervise namespace creation. I'm
> > > in the process of adding various "universal truth" bpf programs to
> > > systemd that will make use of this. This e.g., allows to lock in a
> > > program into a given set of namespaces.
> > > 
> > > Signed-off-by: Christian Brauner <brauner@kernel.org>
> > > ---
> > >  include/linux/bpf_lsm.h | 21 +++++++++++++++++++++
> > >  kernel/bpf/bpf_lsm.c    | 25 +++++++++++++++++++++++++
> > >  kernel/nscommon.c       |  9 ++++++++-
> > >  kernel/nsproxy.c        |  7 +++++++
> > >  4 files changed, 61 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> > > index 643809cc78c3..5ae438fdf567 100644
> > > --- a/include/linux/bpf_lsm.h
> > > +++ b/include/linux/bpf_lsm.h
> > > @@ -12,6 +12,9 @@
> > >  #include <linux/bpf_verifier.h>
> > >  #include <linux/lsm_hooks.h>
> > >  
> > > +struct ns_common;
> > > +struct nsset;
> > > +
> > >  #ifdef CONFIG_BPF_LSM
> > >  
> > >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
> > > @@ -48,6 +51,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, bpf_func_t *bpf_func)
> > >  
> > >  int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
> > >  			     struct bpf_retval_range *range);
> > > +
> > > +int bpf_lsm_namespace_alloc(struct ns_common *ns);
> > > +void bpf_lsm_namespace_free(struct ns_common *ns);
> > > +int bpf_lsm_namespace_install(struct nsset *nsset, struct ns_common *ns);
> > > +
> > >  int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
> > >  				const struct bpf_dynptr *value_p, int flags);
> > >  int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str);
> > > @@ -104,6 +112,19 @@ static inline bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
> > >  {
> > >  	return false;
> > >  }
> > > +
> > > +static inline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +static inline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > > +static inline int bpf_lsm_namespace_install(struct nsset *nsset,
> > > +					    struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > >  #endif /* CONFIG_BPF_LSM */
> > >  
> > >  #endif /* _LINUX_BPF_LSM_H */
> > > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > > index 0c4a0c8e6f70..f6378db46220 100644
> > > --- a/kernel/bpf/bpf_lsm.c
> > > +++ b/kernel/bpf/bpf_lsm.c
> > > @@ -30,10 +30,32 @@ __weak noinline RET bpf_lsm_##NAME(__VA_ARGS__)	\
> > >  #include <linux/lsm_hook_defs.h>
> > >  #undef LSM_HOOK
> > >  
> > > +__bpf_hook_start();
> > > +
> > > +__weak noinline int bpf_lsm_namespace_alloc(struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +__weak noinline void bpf_lsm_namespace_free(struct ns_common *ns)
> > > +{
> > > +}
> > > +
> > > +__weak noinline int bpf_lsm_namespace_install(struct nsset *nsset,
> > > +					  struct ns_common *ns)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +__bpf_hook_end();
> > > +
> > >  #define LSM_HOOK(RET, DEFAULT, NAME, ...) BTF_ID(func, bpf_lsm_##NAME)
> > >  BTF_SET_START(bpf_lsm_hooks)
> > >  #include <linux/lsm_hook_defs.h>
> > >  #undef LSM_HOOK
> > > +BTF_ID(func, bpf_lsm_namespace_alloc)
> > > +BTF_ID(func, bpf_lsm_namespace_free)
> > > +BTF_ID(func, bpf_lsm_namespace_install)
> > >  BTF_SET_END(bpf_lsm_hooks)
> > >  
> > >  BTF_SET_START(bpf_lsm_disabled_hooks)
> > > @@ -383,6 +405,8 @@ BTF_ID(func, bpf_lsm_task_prctl)
> > >  BTF_ID(func, bpf_lsm_task_setscheduler)
> > >  BTF_ID(func, bpf_lsm_task_to_inode)
> > >  BTF_ID(func, bpf_lsm_userns_create)
> > > +BTF_ID(func, bpf_lsm_namespace_alloc)
> > > +BTF_ID(func, bpf_lsm_namespace_install)
> > >  BTF_SET_END(sleepable_lsm_hooks)
> > >  
> > >  BTF_SET_START(untrusted_lsm_hooks)
> > > @@ -395,6 +419,7 @@ BTF_ID(func, bpf_lsm_sk_alloc_security)
> > >  BTF_ID(func, bpf_lsm_sk_free_security)
> > >  #endif /* CONFIG_SECURITY_NETWORK */
> > >  BTF_ID(func, bpf_lsm_task_free)
> > > +BTF_ID(func, bpf_lsm_namespace_free)
> > >  BTF_SET_END(untrusted_lsm_hooks)
> > >  
> > >  bool bpf_lsm_is_sleepable_hook(u32 btf_id)
> > > diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> > > index bdc3c86231d3..c3613cab3d41 100644
> > > --- a/kernel/nscommon.c
> > > +++ b/kernel/nscommon.c
> > > @@ -1,6 +1,7 @@
> > >  // SPDX-License-Identifier: GPL-2.0-only
> > >  /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
> > >  
> > > +#include <linux/bpf_lsm.h>
> > >  #include <linux/ns_common.h>
> > >  #include <linux/nstree.h>
> > >  #include <linux/proc_ns.h>
> > > @@ -77,6 +78,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> > >  		ret = proc_alloc_inum(&ns->inum);
> > >  	if (ret)
> > >  		return ret;
> > > +
> > >  	/*
> > >  	 * Tree ref starts at 0. It's incremented when namespace enters
> > >  	 * active use (installed in nsproxy) and decremented when all
> > > @@ -86,11 +88,16 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> > >  		atomic_set(&ns->__ns_ref_active, 1);
> > >  	else
> > >  		atomic_set(&ns->__ns_ref_active, 0);
> > > -	return 0;
> > > +
> > > +	ret = bpf_lsm_namespace_alloc(ns);
> > > +	if (ret && !inum)
> > > +		proc_free_inum(ns->inum);
> > > +	return ret;
> > >  }
> > >  
> > >  void __ns_common_free(struct ns_common *ns)
> > >  {
> > > +	bpf_lsm_namespace_free(ns);
> > >  	proc_free_inum(ns->inum);
> > >  }
> > >  
> > > diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> > > index 259c4b4f1eeb..5742f9664dbb 100644
> > > --- a/kernel/nsproxy.c
> > > +++ b/kernel/nsproxy.c
> > > @@ -9,6 +9,7 @@
> > >   *             Pavel Emelianov <xemul@openvz.org>
> > >   */
> > >  
> > > +#include <linux/bpf_lsm.h>
> > >  #include <linux/slab.h>
> > >  #include <linux/export.h>
> > >  #include <linux/nsproxy.h>
> > > @@ -379,6 +380,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
> > >  
> > >  static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
> > >  {
> > > +	int ret;
> > > +
> > > +	ret = bpf_lsm_namespace_install(nsset, ns);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > >  	return ns->ops->install(nsset, ns);
> > >  }
> > 
> > What's the reason for not adding these new hook points to the generic
> > set of hooks that are currently being exposed directly by the LSM
> > framework? Honestly, it seems a little odd to be providing
> > declarations/definitions for a set of new hook points which are to be
> > exclusively siloed to BPF LSM implementations only. I'd argue that
> > some other LSM implementations could very well find namespace
> > lifecycle events possibly interesting.
> 
> The LSM layer is of the opinion that adding new security hooks is only
> acceptable if an implementation for an in-tree LSM is provided alongside
> it (cf. [1]). IOW, your bpf lsm needs are not sufficient justification
> for adding new security hooks. So if you want to add security hooks that
> a bpf lsm makes use of then you need to come up with an implementation
> for another in-tree LSM.

I apologize. I didn't realize that adding these as new generic LSM
hooks points had already been proposed and discussed with the LSM
maintainers. I just wanted to make sure that we weren't
unintentionally side-stepping.

> However, a subsystem is free to add as much bpf support as it wants:
> none, some, flamethrower mode. Cgroupfs has traditionally been very bpf
> friendly. I maintain namespaces and rewrote the infra allowing me to
> manage them uniformly now. bpf literally just needs an attach point. I
> could also just add fmodret tracepoints and achieve the same result.
> 
> The same way you add bpf kfuncs to support access to functionality that
> put you way past what an in-tree use would be able do. The question is
> whether you want such capabilities to be bounded by in-tree users as
> well.
>
> Either a bpf lsm is an inextensible fixture bound to the scope of
> security_* or you allow subsystems open to it to add functionality just
> like adding a kfuncs is.

Adding these dedicated BPF LSM hooks is OK with me, especially knowing
that I have agreement from you that you'll also be maintaining their
call sites.