Netdev List
 help / color / mirror / Atom feed
From: "Mickaël Salaün" <mic@digikod.net>
To: Christian Brauner <brauner@kernel.org>
Cc: linux-fsdevel@vger.kernel.org, bpf@vger.kernel.org,
	linux-security-module@vger.kernel.org,
	"Alexei Starovoitov" <ast@kernel.org>,
	"Daniel Borkmann" <daniel@iogearbox.net>,
	"Paul Moore" <paul@paul-moore.com>,
	"Günther Noack" <gnoack@google.com>,
	netdev@vger.kernel.org,
	"Linus Torvalds" <torvalds@linux-foundation.org>,
	stable@kernel.org
Subject: Re: [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook
Date: Thu, 29 Oct 2026 23:13:37 +0100	[thread overview]
Message-ID: <20261029.Eu4Ohquaixoh@digikod.net> (raw)
In-Reply-To: <20261029-work-security-namespace_free-v2-4-65591dbba7b3@kernel.org>

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
> 

  reply	other threads:[~2026-10-29 22:13 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` [PATCH v2 3/7] selftests/bpf: add verifier tests for the namespace LSM hooks Christian Brauner
2026-10-29 16:40 ` [PATCH v2 4/7] ns: allocate the namespace id before the namespace_init hook Christian Brauner
2026-10-29 22:13   ` Mickaël Salaün [this message]
2026-10-29 16:41 ` [PATCH v2 5/7] nstree: drop ns_tree_add_raw() Christian Brauner
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
2026-10-29 21:06 ` [PATCH v2 0/7] ns: fixes " Paul Moore

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261029.Eu4Ohquaixoh@digikod.net \
    --to=mic@digikod.net \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=brauner@kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=gnoack@google.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=paul@paul-moore.com \
    --cc=stable@kernel.org \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox