* [PATCH v2 0/7] ns: fixes for the namespace LSM hooks
@ 2026-10-29 16:40 Christian Brauner
2026-10-29 16:40 ` [PATCH v2 1/7] lsm: always call the namespace_free hook Christian Brauner
` (7 more replies)
0 siblings, 8 replies; 17+ 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
I tried to switch systemd-nsresourced over to the namespace_free LSM
hook that went in this cycle. I explicitly developed it to work nicely
with that. But the version that is merged is broken for that case.
The namespace_free hook is never called. security_namespace_free() bails
if ns->ns_security is NULL and that's only allocated if an LSM requests
a namespace blob. Nobody does. Landlock doesn't care as it only uses
namespace_init and namespace_install but a bpf program attached to
namespace_free never runs. And it can't work around that either. We
could make the bpf LSM request a blob but that would just be a way to
make the hook fire. So call it unconditionally like we do for
superblocks, files and bpf maps.
The bpf LSM also marks the arguments of namespace_init and
namespace_free as trusted. Curiously that patch was also dropped.
namespace_init is called before the namespace is set up and if anything
fails afterwards the namespace is freed without looking at its reference
count. namespace_free is called on the way out. There's no kfunc that
takes a namespace yet but once there is one a program could take a
reference to a namespace that's about to be freed. So add both hooks to
untrusted_lsm_hooks.
The namespace_init hook also always sees a namespace id of zero because
the id is only allocated after the hook has run. The id is the only
identifier of a namespace that is never recycled. So it's what a bpf LSM
wants to use to track a namespace from namespace_init until
namespace_free.
The inode number is recycled way too quickly for that. So allocate the
id in __ns_common_init() before the hook is called. Then ns_tree_add()
doesn't need to allocate ids anymore and ns_tree_add_raw() can go.
Landlock now logs that id instead of zero when it denies the creation of
a namespace.
I'd want to get the whole series into v7.4 since the hooks are new this
cycle. Otherwise v7.4 is the only release where namespace_init sees a
zero id and where Landlock logs zero for a denied namespace creation.
The systemd change I have for this uses the namespace_free hook whenever
the kernel has it. On a kernel with the hook but without the first fix
it would never learn about dead user namespaces.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- Drop "landlock: pass the namespace id to log to check_ns_type()".
Landlock now logs the real namespace id for denied namespace creation
and its documentation and selftests are updated in "ns: allocate the
namespace id before the namespace_init hook" (Mickaël):
https://patch.msgid.link/20261028.oonaisai0Doh@digikod.net
- Link to v1: https://patch.msgid.link/20261026-work-security-namespace_free-v1-0-ffa3ebbf71bd@kernel.org
---
Christian Brauner (7):
lsm: always call the namespace_free hook
bpf: don't mark namespace_init and namespace_free arguments as trusted
selftests/bpf: add verifier tests for the namespace LSM hooks
ns: allocate the namespace id before the namespace_init hook
nstree: drop ns_tree_add_raw()
lsm: fix the security_namespace_init() kernel-doc
selftests/bpf: add a lifecycle test for the namespace LSM hooks
Documentation/admin-guide/LSM/landlock.rst | 5 +-
Documentation/trace/events-landlock.rst | 5 +-
fs/namespace.c | 6 +-
include/linux/ns_common.h | 16 ++-
include/linux/nstree.h | 29 +----
include/trace/events/landlock.h | 3 +-
ipc/namespace.c | 3 +-
kernel/bpf/bpf_lsm.c | 2 +
kernel/nscommon.c | 7 +-
kernel/nstree.c | 2 +-
net/core/net_namespace.c | 4 +-
security/landlock/ns.c | 3 +-
security/security.c | 9 +-
.../selftests/bpf/prog_tests/ns_lifecycle.c | 145 +++++++++++++++++++++
tools/testing/selftests/bpf/prog_tests/verifier.c | 2 +
.../selftests/bpf/progs/test_ns_lifecycle.c | 88 +++++++++++++
.../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 +
tools/testing/selftests/landlock/ns_test.c | 7 +-
20 files changed, 323 insertions(+), 57 deletions(-)
---
base-commit: 21a7f44fd0765fcc5194130b28767786eba1d1e7
change-id: 20261026-work-security-namespace_free-7aaf89b5b00e
^ permalink raw reply [flat|nested] 17+ messages in thread
* [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
` (6 subsequent siblings)
7 siblings, 1 reply; 17+ 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] 17+ 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
` (5 subsequent siblings)
7 siblings, 1 reply; 17+ 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] 17+ 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
` (4 subsequent siblings)
7 siblings, 1 reply; 17+ 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] 17+ 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 22:13 ` Mickaël Salaün
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() Christian Brauner
` (3 subsequent siblings)
7 siblings, 2 replies; 17+ 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] 17+ 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
` (2 subsequent siblings)
7 siblings, 1 reply; 17+ 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] 17+ 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
2026-10-29 21:06 ` [PATCH v2 0/7] ns: fixes " Paul Moore
7 siblings, 1 reply; 17+ 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] 17+ 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
2026-10-29 21:06 ` [PATCH v2 0/7] ns: fixes " Paul Moore
7 siblings, 1 reply; 17+ 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] 17+ messages in thread
* Re: [PATCH v2 3/7] selftests/bpf: add verifier tests for the namespace LSM hooks
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:46 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-29 16:46 UTC (permalink / raw)
Cc: linux-security-module
> 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>
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=3
^ permalink raw reply [flat|nested] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ messages in thread
* Re: [PATCH v2 5/7] nstree: drop ns_tree_add_raw()
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() Christian Brauner
@ 2026-10-29 16:49 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-29 16:49 UTC (permalink / raw)
Cc: linux-security-module
> 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>
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=5
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 6/7] lsm: fix the security_namespace_init() kernel-doc
2026-10-29 16:41 ` [PATCH v2 6/7] lsm: fix the security_namespace_init() kernel-doc Christian Brauner
@ 2026-10-29 16:50 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-29 16:50 UTC (permalink / raw)
Cc: linux-security-module
> 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>
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=6
^ permalink raw reply [flat|nested] 17+ 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
2026-10-29 22:13 ` Mickaël Salaün
1 sibling, 0 replies; 17+ 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] 17+ messages in thread
* Re: [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks
2026-10-29 16:41 ` [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks Christian Brauner
@ 2026-10-29 16:55 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-29 16:55 UTC (permalink / raw)
Cc: linux-security-module
> 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>
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=7
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 0/7] ns: fixes for the namespace LSM hooks
2026-10-29 16:40 [PATCH v2 0/7] ns: fixes for the namespace LSM hooks Christian Brauner
` (6 preceding siblings ...)
2026-10-29 16:41 ` [PATCH v2 7/7] selftests/bpf: add a lifecycle test for the namespace LSM hooks Christian Brauner
@ 2026-10-29 21:06 ` Paul Moore
7 siblings, 0 replies; 17+ messages in thread
From: Paul Moore @ 2026-10-29 21:06 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, bpf, linux-security-module, Alexei Starovoitov,
Daniel Borkmann, Mickaël Salaün, Günther Noack,
netdev, Linus Torvalds, stable
On Thu, Oct 29, 2026 at 12:41 PM Christian Brauner <brauner@kernel.org> wrote:
>
> I tried to switch systemd-nsresourced over to the namespace_free LSM
> hook that went in this cycle. I explicitly developed it to work nicely
> with that. But the version that is merged is broken for that case.
>
> The namespace_free hook is never called. security_namespace_free() bails
> if ns->ns_security is NULL and that's only allocated if an LSM requests
> a namespace blob. Nobody does. Landlock doesn't care as it only uses
> namespace_init and namespace_install but a bpf program attached to
> namespace_free never runs. And it can't work around that either. We
> could make the bpf LSM request a blob but that would just be a way to
> make the hook fire. So call it unconditionally like we do for
> superblocks, files and bpf maps.
>
> The bpf LSM also marks the arguments of namespace_init and
> namespace_free as trusted. Curiously that patch was also dropped.
>
> namespace_init is called before the namespace is set up and if anything
> fails afterwards the namespace is freed without looking at its reference
> count. namespace_free is called on the way out. There's no kfunc that
> takes a namespace yet but once there is one a program could take a
> reference to a namespace that's about to be freed. So add both hooks to
> untrusted_lsm_hooks.
>
> The namespace_init hook also always sees a namespace id of zero because
> the id is only allocated after the hook has run. The id is the only
> identifier of a namespace that is never recycled. So it's what a bpf LSM
> wants to use to track a namespace from namespace_init until
> namespace_free.
>
> The inode number is recycled way too quickly for that. So allocate the
> id in __ns_common_init() before the hook is called. Then ns_tree_add()
> doesn't need to allocate ids anymore and ns_tree_add_raw() can go.
> Landlock now logs that id instead of zero when it denies the creation of
> a namespace.
>
> I'd want to get the whole series into v7.4 since the hooks are new this
> cycle. Otherwise v7.4 is the only release where namespace_init sees a
> zero id and where Landlock logs zero for a denied namespace creation.
> The systemd change I have for this uses the namespace_free hook whenever
> the kernel has it. On a kernel with the hook but without the first fix
> it would never learn about dead user namespaces.
I'm renewing my comments from the v1 patchset (see the link Christian
provided below) about the LSM framework changes being appropriate for
Linux v7.5, not Linux v7.4. It is clear that the BPF LSM use case was
not tested prior to the merge window so BPF LSM support for these new
LSM hooks should be deferred to Linux v7.5, allowing for proper
testing.
Disabling the BPF LSM support for these new hooks can easily be done
by adding them to the bpf_lsm_disabled_hooks list in the BPF LSM for
Linux v7.4, and removed in Linux v7.5 when the LSM hooks have been
changed in a way that is deemed suitable for all LSMs. The
bpf_lsm_disabled_hooks change would be substantially smaller than this
patchset and would be fully contained within the BPF LSM.
As a reminder, the LSM hooks and their callbacks are not part of the
kernel's stable API and are subject to change between kernel releases.
LSMs that wish to provide a stable API need to provide an abstraction
between the LSM hooks/callbacks and their API; the LSM framework is
not responsible for ensuring individual LSM API stability.
> - Link to v1: https://patch.msgid.link/20261026-work-security-namespace_free-v1-0-ffa3ebbf71bd@kernel.org
--
paul-moore.com
^ permalink raw reply [flat|nested] 17+ 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
@ 2026-10-29 22:13 ` Mickaël Salaün
1 sibling, 0 replies; 17+ messages in thread
From: Mickaël Salaün @ 2026-10-29 22:13 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, bpf, linux-security-module, Alexei Starovoitov,
Daniel Borkmann, Paul Moore, Günther Noack, netdev,
Linus Torvalds, stable
On Thu, Oct 29, 2026 at 05:40:59PM +0100, Christian Brauner wrote:
> 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>
Reviewed-by: Mickaël Salaün <mic@digikod.net> # Landlock changes
> ---
> 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 [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-29 22:13 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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: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
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
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
2026-10-29 16:51 ` sashiko-bot
2026-10-29 22:13 ` Mickaël Salaün
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() 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: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
2026-10-29 16:55 ` sashiko-bot
2026-10-29 21:06 ` [PATCH v2 0/7] ns: fixes " Paul Moore
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.