* [PATCH v2 1/7] lsm: always call the namespace_free hook
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
@ 2026-10-29 16:40 ` Christian Brauner
2026-10-29 16:48 ` sashiko-bot
2026-10-29 16:40 ` [PATCH v2 2/7] bpf: don't mark namespace_init and namespace_free arguments as trusted Christian Brauner
` (5 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:40 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable), stable
security_namespace_free() returns early if ns->ns_security is NULL and
skips the namespace_free hook. But ns->ns_security is only allocated if
an LSM requests a namespace blob via lbs_ns and currently no LSM does.
So the namespace_free hook is never called at all.
Landlock only uses namespace_init and namespace_install so it doesn't
care. But the bpf LSM attaches to every hook and a program attached to
namespace_free never runs. We want to use it in systemd-nsresourced to
get notified when a user namespace goes away. Right now it has to kprobe
retire_userns_sysctls() for that.
Note that a bpf program can't work around this. The size of the
namespace blob is fixed at boot and the bpf LSM doesn't request one.
And security_namespace_init() allocates the blob before the
namespace_init hooks are called anyway.
The check isn't needed. __ns_common_free() is only ever called after
__ns_common_init() succeeded and security_namespace_init() only calls
security_namespace_free() after the blob has been allocated. So if an
LSM requests a namespace blob it is always there when the hook runs.
Call the hook unconditionally like security_sb_free(),
security_file_free() and security_bpf_map_free() do.
The bpf LSM attaches programs to its bpf_lsm_*() stubs so fentry on the
stubs shows what a bpf LSM program sees. Booted with lsm.debug, which
prints "LSM: blob(ns) size 0", and created 100 user and uts namespaces:
bpftrace -e 'fentry:bpf_lsm_namespace_init { @init = count(); }
fentry:bpf_lsm_namespace_free { @free = count(); }' &
for i in $(seq 100); do unshare -Uu true; done
before: @init: 200 @free: 0
after: @init: 200 @free: 200
Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Cc: <stable@kernel.org> # mainline only
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
security/security.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/security/security.c b/security/security.c
index f6583d507395..1e76c097b28d 100644
--- a/security/security.c
+++ b/security/security.c
@@ -3412,9 +3412,6 @@ int security_namespace_init(struct ns_common *ns)
*/
void security_namespace_free(struct ns_common *ns)
{
- if (!ns->ns_security)
- return;
-
call_void_hook(namespace_free, ns);
kfree(ns->ns_security);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v2 1/7] lsm: always call the namespace_free hook
2026-10-29 16:40 ` [PATCH v2 1/7] lsm: always call the namespace_free hook Christian Brauner
@ 2026-10-29 16:48 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-29 16:48 UTC (permalink / raw)
Cc: linux-security-module
> security_namespace_free() returns early if ns->ns_security is NULL and
> skips the namespace_free hook. But ns->ns_security is only allocated if
> an LSM requests a namespace blob via lbs_ns and currently no LSM does.
> So the namespace_free hook is never called at all.
>
> Landlock only uses namespace_init and namespace_install so it doesn't
> care. But the bpf LSM attaches to every hook and a program attached to
> namespace_free never runs. We want to use it in systemd-nsresourced to
> get notified when a user namespace goes away. Right now it has to kprobe
> retire_userns_sysctls() for that.
>
> Note that a bpf program can't work around this. The size of the
> namespace blob is fixed at boot and the bpf LSM doesn't request one.
> And security_namespace_init() allocates the blob before the
> namespace_init hooks are called anyway.
> [ ... ]
> after: @init: 200 @free: 200
>
> Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
> Cc: <stable@kernel.org> # mainline only
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-work-security-namespace_free-v2-0-65591dbba7b3@kernel.org?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 2/7] bpf: don't mark namespace_init and namespace_free arguments as trusted
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
2026-10-29 16:40 ` [PATCH v2 1/7] lsm: always call the namespace_free hook Christian Brauner
@ 2026-10-29 16:40 ` Christian Brauner
2026-10-29 16:47 ` sashiko-bot
2026-10-29 16:40 ` [PATCH v2 3/7] selftests/bpf: add verifier tests for the namespace LSM hooks Christian Brauner
` (4 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:40 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable), stable
The bpf LSM marks the arguments of all hooks as trusted unless the hook
is listed in untrusted_lsm_hooks. That list was added in commit
c0c852dd1876 ("bpf: Do not mark certain LSM hook arguments as trusted")
for hooks that are called on objects that are only partially set up or
that are already on their way out. The namespace_init and namespace_free
hooks are exactly that but they aren't listed.
namespace_init is called from __ns_common_init() before the namespace
has been set up. It isn't in the namespace trees yet and for all types
but network namespaces even the owning user namespace isn't set yet. If
any LSM rejects the namespace or if anything after ns_common_init()
fails the namespace is freed right away without looking at its reference
count.
namespace_free is called from __ns_common_free() when the namespace is
torn down or when its initialization failed.
Currently there's no kfunc that takes a namespace. But once there is
one, e.g., for namespace local storage, a program could take a reference
to or create storage on a namespace that is about to be freed. So don't
mark the arguments of these hooks as trusted. namespace_install is
called with a reference to a fully set up namespace and stays trusted.
Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Cc: <stable@kernel.org> # mainline only
Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Tested-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
kernel/bpf/bpf_lsm.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index f58dce888ff4..468e63dbd922 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -411,6 +411,8 @@ BTF_ID(func, bpf_lsm_bpf_map_free)
BTF_ID(func, bpf_lsm_bpf_prog_free)
BTF_ID(func, bpf_lsm_file_alloc_security)
BTF_ID(func, bpf_lsm_file_free_security)
+BTF_ID(func, bpf_lsm_namespace_init)
+BTF_ID(func, bpf_lsm_namespace_free)
#ifdef CONFIG_SECURITY_NETWORK
BTF_ID(func, bpf_lsm_sk_alloc_security)
BTF_ID(func, bpf_lsm_sk_free_security)
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/7] bpf: don't mark namespace_init and namespace_free arguments as trusted
2026-10-29 16:40 ` [PATCH v2 2/7] bpf: don't mark namespace_init and namespace_free arguments as trusted Christian Brauner
@ 2026-10-29 16:47 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-29 16:47 UTC (permalink / raw)
Cc: linux-security-module
> The bpf LSM marks the arguments of all hooks as trusted unless the hook
> is listed in untrusted_lsm_hooks. That list was added in commit
> c0c852dd1876 ("bpf: Do not mark certain LSM hook arguments as trusted")
> for hooks that are called on objects that are only partially set up or
> that are already on their way out. The namespace_init and namespace_free
> hooks are exactly that but they aren't listed.
>
> namespace_init is called from __ns_common_init() before the namespace
> has been set up. It isn't in the namespace trees yet and for all types
> but network namespaces even the owning user namespace isn't set yet. If
> any LSM rejects the namespace or if anything after ns_common_init()
> fails the namespace is freed right away without looking at its reference
> count.
>
> namespace_free is called from __ns_common_free() when the namespace is
> torn down or when its initialization failed.
>
> Currently there's no kfunc that takes a namespace. But once there is
> one, e.g., for namespace local storage, a program could take a reference
> to or create storage on a namespace that is about to be freed. So don't
> mark the arguments of these hooks as trusted. namespace_install is
> called with a reference to a fully set up namespace and stays trusted.
>
> Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
> Cc: <stable@kernel.org> # mainline only
> Acked-by: Alexei Starovoitov <ast@kernel.org>
> Acked-by: Daniel Borkmann <daniel@iogearbox.net>
> Tested-by: Daniel Borkmann <daniel@iogearbox.net>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-work-security-namespace_free-v2-0-65591dbba7b3@kernel.org?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 3/7] selftests/bpf: add verifier tests for the namespace LSM hooks
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
2026-10-29 16:40 ` [PATCH v2 1/7] lsm: always call the namespace_free hook Christian Brauner
2026-10-29 16:40 ` [PATCH v2 2/7] bpf: don't mark namespace_init and namespace_free arguments as trusted Christian Brauner
@ 2026-10-29 16:40 ` Christian Brauner
2026-10-29 16:46 ` sashiko-bot
2026-10-29 16:40 ` [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook Christian Brauner
` (3 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:40 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable)
Add a test kfunc that takes a trusted struct ns_common and check that:
- namespace_init and namespace_free arguments can't be passed to it
- namespace_install arguments can
The test fails if namespace_init and namespace_free are treated as
trusted.
Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Tested-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tools/testing/selftests/bpf/prog_tests/verifier.c | 2 ++
.../selftests/bpf/progs/verifier_lsm_namespace.c | 38 ++++++++++++++++++++++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 5 +++
.../selftests/bpf/test_kmods/bpf_testmod_kfunc.h | 1 +
4 files changed, 46 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 04740ba37e91..3963a4f71d98 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -147,6 +147,7 @@
#include "verifier_set_retval.skel.h"
#include "verifier_lsm.skel.h"
#include "verifier_lsm_init_xattr.skel.h"
+#include "verifier_lsm_namespace.skel.h"
#include "verifier_jit_inline.skel.h"
#include "irq.skel.h"
#include "verifier_ctx_ptr_param.skel.h"
@@ -337,6 +338,7 @@ void test_verifier_bits_iter(void) { RUN(verifier_bits_iter); }
void test_verifier_set_retval(void) { RUN(verifier_set_retval); }
void test_verifier_lsm(void) { RUN(verifier_lsm); }
void test_verifier_lsm_init_xattr(void) { RUN(verifier_lsm_init_xattr); }
+void test_verifier_lsm_namespace(void) { RUN_TESTS(verifier_lsm_namespace); }
void test_irq(void) { RUN(irq); }
void test_verifier_mtu(void) { RUN(verifier_mtu); }
void test_verifier_jit_inline(void) { RUN(verifier_jit_inline); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm_namespace.c b/tools/testing/selftests/bpf/progs/verifier_lsm_namespace.c
new file mode 100644
index 000000000000..d14fa2363dcc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm_namespace.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+/* The namespace hasn't been set up yet. */
+SEC("lsm/namespace_init")
+__failure __msg("R1 must be referenced or trusted")
+int BPF_PROG(namespace_init_untrusted, struct ns_common *ns)
+{
+ bpf_kfunc_trusted_ns_test(ns);
+ return 0;
+}
+
+/* The namespace is being torn down. */
+SEC("lsm/namespace_free")
+__failure __msg("R1 must be referenced or trusted")
+int BPF_PROG(namespace_free_untrusted, struct ns_common *ns)
+{
+ bpf_kfunc_trusted_ns_test(ns);
+ return 0;
+}
+
+/* setns() holds a reference to a fully set up namespace. */
+SEC("lsm/namespace_install")
+__success
+int BPF_PROG(namespace_install_trusted, const struct nsset *nsset,
+ struct ns_common *ns)
+{
+ bpf_kfunc_trusted_ns_test(ns);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 6380f52f4dd2..8d6bd2237f34 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -350,6 +350,10 @@ __bpf_kfunc void bpf_kfunc_trusted_task_test(struct task_struct *ptr)
{
}
+__bpf_kfunc void bpf_kfunc_trusted_ns_test(struct ns_common *ptr)
+{
+}
+
__bpf_kfunc struct task_struct *bpf_kfunc_trusted_null_test(void)
{
return NULL;
@@ -1030,6 +1034,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_zero_offset_test, KF_ACQUIRE)
BTF_ID_FLAGS(func, bpf_kfunc_nested_release_test, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_kfunc_trusted_vma_test)
BTF_ID_FLAGS(func, bpf_kfunc_trusted_task_test)
+BTF_ID_FLAGS(func, bpf_kfunc_trusted_ns_test)
BTF_ID_FLAGS(func, bpf_kfunc_trusted_null_test, KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_kfunc_trusted_nullable_test)
BTF_ID_FLAGS(func, bpf_kfunc_trusted_num_test)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index 3b810f1e438f..bc32c4078d27 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -355,6 +355,7 @@ int bpf_kfunc_st_ops_inc10(struct st_ops_args *args) __ksym;
void bpf_kfunc_trusted_vma_test(struct vm_area_struct *ptr) __ksym;
void bpf_kfunc_trusted_task_test(struct task_struct *ptr) __ksym;
+void bpf_kfunc_trusted_ns_test(struct ns_common *ptr) __ksym;
struct task_struct *bpf_kfunc_trusted_null_test(void) __ksym;
void bpf_kfunc_trusted_nullable_test(struct task_struct *ptr__nullable) __ksym;
void bpf_kfunc_trusted_num_test(int *ptr) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
` (2 preceding siblings ...)
2026-10-29 16:40 ` [PATCH v2 3/7] selftests/bpf: add verifier tests for the namespace LSM hooks Christian Brauner
@ 2026-10-29 16:40 ` Christian Brauner
2026-10-29 16:51 ` sashiko-bot
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() Christian Brauner
` (2 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:40 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable), stable
Currently the namespace id is allocated only after __ns_common_init()
has called the namespace_init LSM hook. Most namespace types allocate it
in ns_tree_add() right before the namespace is added to the namespace
trees. So the namespace_init hook always sees an id of zero.
The namespace id is the only identifier of a namespace that is never
recycled. A bpf LSM wants to use it to track a namespace from
namespace_init until namespace_free, e.g., by using it as the key into a
map that associates data with the namespace. The inode number doesn't
work for that as it is quickly recycled.
So allocate the id in __ns_common_init() before calling the
namespace_init hook. Every namespace then has its id by the time it is
added to the namespace trees. The initial namespaces still get their
fixed ids, either statically from NS_COMMON_INIT() or for init_net from
ns_common_init(). So ns_tree_add() doesn't need to allocate an id
anymore. Ids are still handed out in creation order so mnt_ns_loop()
keeps working. Mount and ipc namespaces already allocated their id right
after ns_common_init() anyway.
If creating the namespace fails after the id has been allocated the id
is simply never used.
Landlock logs the namespace id when it denies the use of a namespace.
For a denied namespace creation that used to be zero. Now it's the id
that was allocated for the new namespace. That namespace never becomes
visible to userspace but it's the same id that other LSMs see in
namespace_init. Update the Landlock documentation and selftests
accordingly. The selftests can't know the id of a namespace whose
creation was denied so they only check that it isn't zero.
While at it, drop ns_tree_gen_id() as it has no users left.
Reproducer, 100 user and uts namespaces:
bpftrace -e 'fentry:bpf_lsm_namespace_init {
@[args.ns->ns_id ? "set" : "zero"] = count(); }' &
for i in $(seq 100); do unshare -Uu true; done
before: @[zero]: 200
after: @[set]: 200
Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Cc: <stable@kernel.org> # mainline only
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Documentation/admin-guide/LSM/landlock.rst | 5 +++--
Documentation/trace/events-landlock.rst | 5 +++--
fs/namespace.c | 2 --
include/linux/ns_common.h | 16 +++++++++-------
include/linux/nstree.h | 18 +++---------------
include/trace/events/landlock.h | 3 ++-
ipc/namespace.c | 1 -
kernel/nscommon.c | 7 +++++--
net/core/net_namespace.c | 2 +-
security/landlock/ns.c | 3 ++-
tools/testing/selftests/landlock/ns_test.c | 7 +++++--
11 files changed, 33 insertions(+), 36 deletions(-)
diff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst
index 718e0d23ffb5..946f3bf89650 100644
--- a/Documentation/admin-guide/LSM/landlock.rst
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -73,8 +73,9 @@ AUDIT_LANDLOCK_ACCESS
:manpage:`open_tree(2)`, or :manpage:`fsmount(2)`, or joining via
:manpage:`setns(2)`);
``namespace_type`` indicates the type (hex ``CLONE_NEW*`` bitmask),
- and ``namespace_id`` identifies the target namespace for
- :manpage:`setns(2)` operations (zero for creation)
+ and ``namespace_id`` identifies the target namespace (for a denied
+ creation, the namespace never becomes reachable so its ID is not
+ directly visible from user space)
**capability.*** - Capability restrictions (ABI 12+):
- capability.use - Capability use was denied;
diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst
index 9ccf2769a2d5..dc5f05a17c2c 100644
--- a/Documentation/trace/events-landlock.rst
+++ b/Documentation/trace/events-landlock.rst
@@ -187,8 +187,9 @@ therefore appears as zero while still advancing the ruleset version, and adding
a member already present still emits an event.
``landlock_deny_permission_namespace`` reports the denied raw
-``CLONE_NEW*`` value. ``namespace_id`` is zero for namespace creation and is
-the exact target namespace ID for :manpage:`setns(2)`.
+``CLONE_NEW*`` value. ``namespace_id`` is the exact target namespace ID. For
+a denied namespace creation, the namespace never becomes reachable so its ID
+is not directly visible from user space.
``landlock_deny_permission_capability`` reports the denied ``CAP_*`` number.
Like every denial event, both include the denying domain, ``same_exec``, and
``logged``.
diff --git a/fs/namespace.c b/fs/namespace.c
index 9f6ec35c7b9c..f81d6a4bc3fe 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4394,8 +4394,6 @@ static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns, bool a
dec_mnt_namespaces(ucounts);
return ERR_PTR(ret);
}
- ns_tree_gen_id(new_ns);
-
new_ns->is_anon = anon;
refcount_set(&new_ns->passive, 1);
new_ns->mounts = RB_ROOT;
diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h
index c8e227a3f9e2..e8b2bf0da5ed 100644
--- a/include/linux/ns_common.h
+++ b/include/linux/ns_common.h
@@ -9,7 +9,8 @@
#include <uapi/linux/nsfs.h>
bool is_current_namespace(struct ns_common *ns);
-int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops, int inum);
+int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops,
+ int inum, u64 ns_id);
void __ns_common_free(struct ns_common *ns);
struct ns_common *__must_check ns_owner(struct ns_common *ns);
@@ -41,17 +42,18 @@ static __always_inline bool is_ns_init_id(const struct ns_common *ns)
.ns_owner_root.ns_list_head = LIST_HEAD_INIT(nsname.ns.ns_owner_root.ns_list_head), \
}
-#define ns_common_init(__ns) \
- __ns_common_init(to_ns_common(__ns), \
- ns_common_type(__ns), \
- to_ns_operations(__ns), \
- (((__ns) == ns_init_ns(__ns)) ? ns_init_inum(__ns) : 0))
+#define ns_common_init(__ns) \
+ __ns_common_init(to_ns_common(__ns), \
+ ns_common_type(__ns), \
+ to_ns_operations(__ns), \
+ (((__ns) == ns_init_ns(__ns)) ? ns_init_inum(__ns) : 0), \
+ (((__ns) == ns_init_ns(__ns)) ? ns_init_id(__ns) : 0))
#define ns_common_init_inum(__ns, __inum) \
__ns_common_init(to_ns_common(__ns), \
ns_common_type(__ns), \
to_ns_operations(__ns), \
- __inum)
+ __inum, 0)
#define ns_common_free(__ns) __ns_common_free(to_ns_common((__ns)))
diff --git a/include/linux/nstree.h b/include/linux/nstree.h
index 5b64d4572881..3293df17397d 100644
--- a/include/linux/nstree.h
+++ b/include/linux/nstree.h
@@ -41,10 +41,6 @@ void ns_tree_node_del(struct ns_tree_node *node, struct ns_tree_root *root);
struct user_namespace *: &(user_ns_tree), \
struct uts_namespace *: &(uts_ns_tree))
-#define ns_tree_gen_id(__ns) \
- __ns_tree_gen_id(to_ns_common(__ns), \
- (((__ns) == ns_init_ns(__ns)) ? ns_init_id(__ns) : 0))
-
u64 __ns_tree_gen_id(struct ns_common *ns, u64 id);
void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree_root *ns_tree);
void __ns_tree_remove(struct ns_common *ns, struct ns_tree_root *ns_tree);
@@ -53,12 +49,6 @@ struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns,
struct ns_tree_root *ns_tree,
bool previous);
-static inline void __ns_tree_add(struct ns_common *ns, struct ns_tree_root *ns_tree, u64 id)
-{
- __ns_tree_gen_id(ns, id);
- __ns_tree_add_raw(ns, ns_tree);
-}
-
/**
* ns_tree_add_raw - Add a namespace to a namespace
* @__ns: Namespace to add
@@ -72,12 +62,10 @@ static inline void __ns_tree_add(struct ns_common *ns, struct ns_tree_root *ns_t
* ns_tree_add - Add a namespace to a namespace tree
* @__ns: Namespace to add
*
- * This function assigns a new id to the namespace and adds it to the
- * appropriate namespace tree and list.
+ * This function adds a namespace to the appropriate namespace tree and
+ * list. The namespace must already have been assigned an id.
*/
-#define ns_tree_add(__ns) \
- __ns_tree_add(to_ns_common(__ns), to_ns_tree(__ns), \
- (((__ns) == ns_init_ns(__ns)) ? ns_init_id(__ns) : 0))
+#define ns_tree_add(__ns) __ns_tree_add_raw(to_ns_common(__ns), to_ns_tree(__ns))
/**
* ns_tree_remove - Remove a namespace from a namespace tree
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index 6d9d9e7c7ca2..b6fdc6e2eef8 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -987,7 +987,8 @@ TRACE_EVENT(landlock_deny_access_net,
* @logged: Whether this denial was selected for audit logging.
* @blockers: Request type and final missing permission subset (never NULL).
* @namespace_type: CLONE_NEW* namespace type that was denied.
- * @namespace_id: Namespace ID, or 0 when creation was denied.
+ * @namespace_id: Namespace ID, not directly visible from user space when
+ * creation was denied.
*
* Emitted when a Landlock domain denies namespace use.
*/
diff --git a/ipc/namespace.c b/ipc/namespace.c
index 75aa7b469348..9084e0643017 100644
--- a/ipc/namespace.c
+++ b/ipc/namespace.c
@@ -66,7 +66,6 @@ static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns,
if (err)
goto fail_free;
- ns_tree_gen_id(ns);
ns->user_ns = get_user_ns(user_ns);
ns->ucounts = ucounts;
diff --git a/kernel/nscommon.c b/kernel/nscommon.c
index e72426bba29a..4438c6b663f9 100644
--- a/kernel/nscommon.c
+++ b/kernel/nscommon.c
@@ -54,7 +54,8 @@ static void ns_debug(struct ns_common *ns, const struct proc_ns_operations *ops)
}
#endif
-int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops, int inum)
+int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops,
+ int inum, u64 ns_id)
{
int ret = 0;
@@ -64,7 +65,6 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
ns->ns_security = NULL;
#endif
ns->ops = ops;
- ns->ns_id = 0;
ns->ns_type = ns_type;
ns_tree_node_init(&ns->ns_tree_node);
ns_tree_node_init(&ns->ns_unified_node);
@@ -82,6 +82,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
if (ret)
return ret;
+ /* Allocate the id before the LSM hook so it can track the namespace by it. */
+ __ns_tree_gen_id(ns, ns_id);
+
ret = security_namespace_init(ns);
if (ret) {
if (!inum)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..ec4077b81389 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -437,7 +437,7 @@ static __net_init int setup_net(struct net *net)
LIST_HEAD(net_exit_list);
int error = 0;
- net->net_cookie = ns_tree_gen_id(net);
+ net->net_cookie = to_ns_common(net)->ns_id;
list_for_each_entry(ops, &pernet_list, list) {
error = ops_init(ops, net);
diff --git a/security/landlock/ns.c b/security/landlock/ns.c
index cd44c927f83a..06d9784746fe 100644
--- a/security/landlock/ns.c
+++ b/security/landlock/ns.c
@@ -94,7 +94,8 @@ static const struct access_masks ns_permission = {
* Shared check for namespace_init (creation via clone(2), unshare(2),
* open_tree(2), or fsmount(2)) and namespace_install (use via setns(2)): denies
* when the namespace type is not in the domain's allowed set. At allocation
- * time @ns->ns_id is still zero and is logged as such.
+ * time @ns->ns_id is already set even though the namespace is not reachable
+ * from user space yet.
*
* Return: 0 if allowed, -EPERM if denied.
*/
diff --git a/tools/testing/selftests/landlock/ns_test.c b/tools/testing/selftests/landlock/ns_test.c
index f5d4b86edd30..8f387fc47e21 100644
--- a/tools/testing/selftests/landlock/ns_test.c
+++ b/tools/testing/selftests/landlock/ns_test.c
@@ -1899,8 +1899,9 @@ TEST(setns_inherited_fd)
static int matches_log_ns_create(int audit_fd, __u64 ns_type)
{
+ /* A denied creation logs the ID allocated for the new namespace. */
static const char log_template[] = REGEX_LANDLOCK_PREFIX
- " blockers=namespace\\.use namespace_type=0x%x namespace_id=0$";
+ " blockers=namespace\\.use namespace_type=0x%x namespace_id=[1-9][0-9]*$";
char log_match[sizeof(log_template) + 10];
int log_match_len;
@@ -2538,7 +2539,9 @@ static void test_ns_trace_denial(struct __test_metadata *const _metadata,
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK),
"namespace_id", field, sizeof(field)));
- EXPECT_EQ(use_setns ? expected_ns_id : 0, strtoull(field, NULL, 10));
+ EXPECT_STRNE("0", field);
+ if (use_setns)
+ EXPECT_EQ(expected_ns_id, strtoull(field, NULL, 10));
free(buf);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook
2026-10-29 16:40 ` [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook Christian Brauner
@ 2026-10-29 16:51 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-29 16:51 UTC (permalink / raw)
Cc: linux-security-module
> Currently the namespace id is allocated only after __ns_common_init()
> has called the namespace_init LSM hook. Most namespace types allocate it
> in ns_tree_add() right before the namespace is added to the namespace
> trees. So the namespace_init hook always sees an id of zero.
>
> The namespace id is the only identifier of a namespace that is never
> recycled. A bpf LSM wants to use it to track a namespace from
> namespace_init until namespace_free, e.g., by using it as the key into a
> map that associates data with the namespace. The inode number doesn't
> work for that as it is quickly recycled.
>
> So allocate the id in __ns_common_init() before calling the
> namespace_init hook. Every namespace then has its id by the time it is
> added to the namespace trees. The initial namespaces still get their
> fixed ids, either statically from NS_COMMON_INIT() or for init_net from
> [ ... ]
> after: @[set]: 200
>
> Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
> Cc: <stable@kernel.org> # mainline only
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-work-security-namespace_free-v2-0-65591dbba7b3@kernel.org?part=4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 5/7] nstree: drop ns_tree_add_raw()
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
` (3 preceding siblings ...)
2026-10-29 16:40 ` [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook Christian Brauner
@ 2026-10-29 16:41 ` Christian Brauner
2026-10-29 16:49 ` sashiko-bot
2026-10-29 16:41 ` [PATCH v2 6/7] lsm: fix the security_namespace_init() kernel-doc Christian Brauner
2026-10-29 16:41 ` [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks Christian Brauner
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:41 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable), stable
Now that ns_tree_add() doesn't allocate an id anymore it does the same
thing as ns_tree_add_raw(). Switch the remaining ns_tree_add_raw() users
over to ns_tree_add() and drop it. Rename __ns_tree_add_raw() to
__ns_tree_add() while at it.
No functional changes.
Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Cc: <stable@kernel.org> # mainline only
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/namespace.c | 4 ++--
include/linux/nstree.h | 13 ++-----------
ipc/namespace.c | 2 +-
kernel/nstree.c | 2 +-
net/core/net_namespace.c | 2 +-
5 files changed, 7 insertions(+), 16 deletions(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index f81d6a4bc3fe..2f9a1460b4ac 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3296,7 +3296,7 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
}
new_ns->root = new_ns_root;
- ns_tree_add_raw(new_ns);
+ ns_tree_add(new_ns);
return new_ns;
}
@@ -4497,7 +4497,7 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns,
p = next_mnt(skip_mnt_tree(p), old);
}
}
- ns_tree_add_raw(new_ns);
+ ns_tree_add(new_ns);
return new_ns;
}
diff --git a/include/linux/nstree.h b/include/linux/nstree.h
index 3293df17397d..eeed713804be 100644
--- a/include/linux/nstree.h
+++ b/include/linux/nstree.h
@@ -42,22 +42,13 @@ void ns_tree_node_del(struct ns_tree_node *node, struct ns_tree_root *root);
struct uts_namespace *: &(uts_ns_tree))
u64 __ns_tree_gen_id(struct ns_common *ns, u64 id);
-void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree_root *ns_tree);
+void __ns_tree_add(struct ns_common *ns, struct ns_tree_root *ns_tree);
void __ns_tree_remove(struct ns_common *ns, struct ns_tree_root *ns_tree);
struct ns_common *ns_tree_lookup_rcu(u64 ns_id, int ns_type);
struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns,
struct ns_tree_root *ns_tree,
bool previous);
-/**
- * ns_tree_add_raw - Add a namespace to a namespace
- * @__ns: Namespace to add
- *
- * This function adds a namespace to the appropriate namespace tree
- * without assigning a id.
- */
-#define ns_tree_add_raw(__ns) __ns_tree_add_raw(to_ns_common(__ns), to_ns_tree(__ns))
-
/**
* ns_tree_add - Add a namespace to a namespace tree
* @__ns: Namespace to add
@@ -65,7 +56,7 @@ struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns,
* This function adds a namespace to the appropriate namespace tree and
* list. The namespace must already have been assigned an id.
*/
-#define ns_tree_add(__ns) __ns_tree_add_raw(to_ns_common(__ns), to_ns_tree(__ns))
+#define ns_tree_add(__ns) __ns_tree_add(to_ns_common(__ns), to_ns_tree(__ns))
/**
* ns_tree_remove - Remove a namespace from a namespace tree
diff --git a/ipc/namespace.c b/ipc/namespace.c
index 9084e0643017..b4fab86aee27 100644
--- a/ipc/namespace.c
+++ b/ipc/namespace.c
@@ -86,7 +86,7 @@ static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns,
sem_init_ns(ns);
shm_init_ns(ns);
- ns_tree_add_raw(ns);
+ ns_tree_add(ns);
return ns;
diff --git a/kernel/nstree.c b/kernel/nstree.c
index 831f279d174a..94153dfddb3d 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -194,7 +194,7 @@ static int ns_cmp_owner(struct rb_node *a, const struct rb_node *b)
return ns_id_cmp(node_to_ns_owner(a)->ns_id, node_to_ns_owner(b)->ns_id);
}
-void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree_root *ns_tree)
+void __ns_tree_add(struct ns_common *ns, struct ns_tree_root *ns_tree)
{
struct rb_node *node;
const struct proc_ns_operations *ops = ns->ops;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index ec4077b81389..54ba07eba003 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -447,7 +447,7 @@ static __net_init int setup_net(struct net *net)
down_write(&net_rwsem);
list_add_tail_rcu(&net->list, &net_namespace_list);
up_write(&net_rwsem);
- ns_tree_add_raw(net);
+ ns_tree_add(net);
out:
return error;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 6/7] lsm: fix the security_namespace_init() kernel-doc
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
` (4 preceding siblings ...)
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() Christian Brauner
@ 2026-10-29 16:41 ` Christian Brauner
2026-10-29 16:50 ` sashiko-bot
2026-10-29 16:41 ` [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks Christian Brauner
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:41 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable), stable
The kernel-doc claims that the owning user namespace is available via
ns->ops->owner(ns). But security_namespace_init() is called from
__ns_common_init() before the namespace has been set up. Apart from
network namespaces the owning user namespace isn't set at that point so
->owner() returns NULL. And for init_net with CONFIG_NET_NS=n ns->ops
itself is NULL.
What a namespace_init hook can rely on is the namespace type and, now
that it is allocated before the hook is called, the namespace id. So
document that instead.
No functional changes.
Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Cc: <stable@kernel.org> # mainline only
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
security/security.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/security/security.c b/security/security.c
index 1e76c097b28d..81dac9e48a51 100644
--- a/security/security.c
+++ b/security/security.c
@@ -3382,9 +3382,9 @@ int security_create_user_ns(const struct cred *cred)
* security_namespace_init() - Initialize LSM security data for a namespace
* @ns: the namespace being initialized
*
- * Initialize the LSM security blob attached to the namespace. The namespace type
- * is available via ns->ns_type, and the owning user namespace (if any)
- * via ns->ops->owner(ns).
+ * Initialize the LSM security blob attached to the namespace. The namespace
+ * hasn't been set up yet but its type and id are available via ns->ns_type
+ * and ns->ns_id.
*
* Return: Returns 0 if successful, otherwise < 0 error code.
*/
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
` (5 preceding siblings ...)
2026-10-29 16:41 ` [PATCH v2 6/7] lsm: fix the security_namespace_init() kernel-doc Christian Brauner
@ 2026-10-29 16:41 ` Christian Brauner
2026-10-29 16:55 ` sashiko-bot
6 siblings, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2026-10-29 16:41 UTC (permalink / raw)
To: linux-fsdevel, bpf
Cc: linux-security-module, Alexei Starovoitov, Daniel Borkmann,
Paul Moore, Mickaël Salaün, Günther Noack, netdev,
Linus Torvalds, Christian Brauner (Amutable)
Track new uts, ipc and net namespaces from a bpf LSM by their namespace
id and check that:
- namespace_init already sees the id that NS_GET_ID reports
- namespace_init, namespace_install and namespace_free fire exactly once
for each of them
The test fails if the namespace_free hook isn't called or if the
namespace id is allocated after namespace_init.
Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Tested-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
.../selftests/bpf/prog_tests/ns_lifecycle.c | 145 +++++++++++++++++++++
.../selftests/bpf/progs/test_ns_lifecycle.c | 88 +++++++++++++
2 files changed, 233 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c b/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c
new file mode 100644
index 000000000000..8ea4939efb23
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Exercise all namespace LSM hooks from a BPF LSM: unshare() new
+ * namespaces (namespace_init), setns() into them (namespace_install) and
+ * drop the last reference (namespace_free).
+ */
+
+#define _GNU_SOURCE
+#include <test_progs.h>
+#include <sched.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <linux/nsfs.h>
+#include "task_local_storage_helpers.h"
+#include "test_ns_lifecycle.skel.h"
+
+struct ns_stat {
+ __u32 ns_type;
+ __u32 init_count;
+ __u32 install_count;
+ __u32 free_count;
+};
+
+static const struct {
+ const char *path;
+ int type;
+} namespaces[] = {
+ { "/proc/self/ns/uts", CLONE_NEWUTS },
+ { "/proc/self/ns/ipc", CLONE_NEWIPC },
+ { "/proc/self/ns/net", CLONE_NEWNET },
+};
+
+#define NR_NS ARRAY_SIZE(namespaces)
+
+static bool all_freed(int map_fd, const __u64 *ns_id)
+{
+ struct ns_stat stat;
+ int i;
+
+ for (i = 0; i < NR_NS; i++) {
+ if (bpf_map_lookup_elem(map_fd, &ns_id[i], &stat) || !stat.free_count)
+ return false;
+ }
+ return true;
+}
+
+void test_ns_lifecycle(void)
+{
+ int orig_fd[NR_NS] = { -1, -1, -1 }, new_fd[NR_NS] = { -1, -1, -1 };
+ struct test_ns_lifecycle *skel = NULL;
+ int pidfd, map_fd, err, i, tries;
+ struct ns_stat stat;
+ __u64 ns_id[NR_NS];
+ struct stat st;
+
+ for (i = 0; i < NR_NS; i++) {
+ orig_fd[i] = open(namespaces[i].path, O_RDONLY | O_CLOEXEC);
+ if (!ASSERT_OK_FD(orig_fd[i], "open orig ns"))
+ goto close_fds;
+ }
+
+ pidfd = sys_pidfd_open(getpid(), 0);
+ if (!ASSERT_OK_FD(pidfd, "pidfd_open"))
+ goto close_fds;
+ err = fstat(pidfd, &st);
+ close(pidfd);
+ if (!ASSERT_OK(err, "fstat pidfd"))
+ goto close_fds;
+
+ skel = test_ns_lifecycle__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
+ goto close_fds;
+
+ err = test_ns_lifecycle__attach(skel);
+ if (!ASSERT_OK(err, "skel attach"))
+ goto destroy;
+
+ map_fd = bpf_map__fd(skel->maps.ns_lifecycle);
+ skel->bss->monitored_pidfd_ino = st.st_ino;
+
+ err = unshare(CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET);
+ if (!ASSERT_OK(err, "unshare"))
+ goto disarm;
+
+ for (i = 0; i < NR_NS; i++) {
+ new_fd[i] = open(namespaces[i].path, O_RDONLY | O_CLOEXEC);
+ if (!ASSERT_OK_FD(new_fd[i], "open new ns"))
+ goto restore;
+ if (!ASSERT_OK(ioctl(new_fd[i], NS_GET_ID, &ns_id[i]), "NS_GET_ID"))
+ goto restore;
+ }
+
+ /* Leave the new namespaces and install them again. */
+ for (i = 0; i < NR_NS; i++) {
+ if (!ASSERT_OK(setns(orig_fd[i], namespaces[i].type), "setns orig"))
+ goto restore;
+ if (!ASSERT_OK(setns(new_fd[i], namespaces[i].type), "setns new"))
+ goto restore;
+ if (!ASSERT_OK(setns(orig_fd[i], namespaces[i].type), "setns orig again"))
+ goto restore;
+ }
+
+ skel->bss->monitored_pidfd_ino = 0;
+
+ /* Drop the last references. Network namespaces are freed asynchronously. */
+ for (i = 0; i < NR_NS; i++) {
+ close(new_fd[i]);
+ new_fd[i] = -1;
+ }
+
+ for (tries = 0; tries < 1000 && !all_freed(map_fd, ns_id); tries++) {
+ kern_sync_rcu();
+ usleep(10000);
+ }
+
+ for (i = 0; i < NR_NS; i++) {
+ /* namespace_init must already have seen the id. */
+ if (!ASSERT_OK(bpf_map_lookup_elem(map_fd, &ns_id[i], &stat), "lookup ns_stat"))
+ continue;
+
+ ASSERT_EQ(stat.ns_type, namespaces[i].type, "ns_type");
+ ASSERT_EQ(stat.init_count, 1, "init_count");
+ ASSERT_EQ(stat.install_count, 1, "install_count");
+ ASSERT_EQ(stat.free_count, 1, "free_count");
+ }
+ goto destroy;
+
+restore:
+ for (i = 0; i < NR_NS; i++)
+ setns(orig_fd[i], namespaces[i].type);
+disarm:
+ skel->bss->monitored_pidfd_ino = 0;
+destroy:
+ test_ns_lifecycle__destroy(skel);
+close_fds:
+ for (i = 0; i < NR_NS; i++) {
+ if (new_fd[i] >= 0)
+ close(new_fd[i]);
+ if (orig_fd[i] >= 0)
+ close(orig_fd[i]);
+ }
+}
diff --git a/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c b/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c
new file mode 100644
index 000000000000..88c5d40ba27a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct ns_stat {
+ __u32 ns_type;
+ __u32 init_count;
+ __u32 install_count;
+ __u32 free_count;
+};
+
+/* Unlike inode numbers namespace ids are never recycled. */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 16);
+ __type(key, __u64);
+ __type(value, struct ns_stat);
+} ns_lifecycle SEC(".maps");
+
+__u64 monitored_pidfd_ino = 0;
+
+char _license[] SEC("license") = "GPL";
+
+static bool is_monitored(void)
+{
+ struct task_struct *task;
+
+ if (!monitored_pidfd_ino)
+ return false;
+
+ task = bpf_get_current_task_btf();
+ return task->thread_pid->ino == monitored_pidfd_ino;
+}
+
+SEC("lsm/namespace_init")
+int BPF_PROG(ns_init, struct ns_common *ns, int ret)
+{
+ struct ns_stat new = {}, *stat;
+ __u64 ns_id;
+
+ if (ret)
+ return ret;
+
+ if (!is_monitored())
+ return 0;
+
+ ns_id = ns->ns_id;
+ new.ns_type = ns->ns_type;
+ bpf_map_update_elem(&ns_lifecycle, &ns_id, &new, BPF_NOEXIST);
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->init_count, 1);
+ return 0;
+}
+
+SEC("lsm/namespace_install")
+int BPF_PROG(ns_install, const struct nsset *nsset, struct ns_common *ns, int ret)
+{
+ struct ns_stat *stat;
+ __u64 ns_id;
+
+ if (ret)
+ return ret;
+
+ if (!is_monitored())
+ return 0;
+
+ ns_id = ns->ns_id;
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->install_count, 1);
+ return 0;
+}
+
+SEC("lsm/namespace_free")
+int BPF_PROG(ns_free, struct ns_common *ns)
+{
+ struct ns_stat *stat;
+ __u64 ns_id;
+
+ ns_id = ns->ns_id;
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->free_count, 1);
+ return 0;
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread