mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] nstree: check listing permission before taking a namespace reference
@ 2026-09-04 14:28 Norbert Szetei
  2026-09-06 11:52 ` Bradley Morgan
  0 siblings, 1 reply; 6+ messages in thread
From: Norbert Szetei @ 2026-09-04 14:28 UTC (permalink / raw)
  To: Christian Brauner; +Cc: linux-kernel, linux-fsdevel

legitimize_ns() takes a reference on the candidate namespace before
may_list_ns() has decided whether the caller may see it. The
__free(ns_put) cleanup on the denied path can drop the last reference to a
mount namespace while we still hold the rcu read lock, and put_mnt_ns()
may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make
sure reference are dropped outside of rcu lock") fixed for the put_user()
path. Neither ns_requested() nor may_list_ns() needs a reference, both
only look at the namespace type and at the caller's own namespaces, so do
the checks first and take the reference last.

Fixes: 76b6f5dfb3fd ("nstree: add listns()")
Signed-off-by: Norbert Szetei <norbert@doyensec.com>
---
A reproducer is available on request.

 kernel/nstree.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/kernel/nstree.c b/kernel/nstree.c
index 6d12e5900ac0..831f279d174a 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T))
 static inline struct ns_common *__must_check legitimize_ns(const struct klistns *kls,
 							   struct ns_common *candidate)
 {
-	struct ns_common *ns __free(ns_put) = NULL;
-
 	if (!ns_requested(kls, candidate))
 		return NULL;
 
-	ns = ns_get_unless_inactive(candidate);
-	if (!ns)
-		return NULL;
-
-	if (!may_list_ns(kls, ns))
+	if (!may_list_ns(kls, candidate))
 		return NULL;
 
-	return no_free_ptr(ns);
+	return ns_get_unless_inactive(candidate);
 }
 
 static ssize_t do_listns_userns(struct klistns *kls)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] nstree: check listing permission before taking a namespace reference
  2026-09-04 14:28 [PATCH] nstree: check listing permission before taking a namespace reference Norbert Szetei
@ 2026-09-06 11:52 ` Bradley Morgan
  2026-09-06 11:52   ` Bradley Morgan
  2026-09-06 18:25   ` Norbert Szetei
  0 siblings, 2 replies; 6+ messages in thread
From: Bradley Morgan @ 2026-09-06 11:52 UTC (permalink / raw)
  To: norbert; +Cc: brauner, linux-fsdevel, linux-kernel, akpm

On 4 September 2026 15:28:05 BST, Norbert Szetei <norbert@doyensec.com>
wrote:
>legitimize_ns() takes a reference on the candidate namespace before
>may_list_ns() has decided whether the caller may see it. The
>__free(ns_put) cleanup on the denied path can drop the last reference to a
>mount namespace while we still hold the rcu read lock, and put_mnt_ns()
>may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make
>sure reference are dropped outside of rcu lock") fixed for the put_user()
>path. Neither ns_requested() nor may_list_ns() needs a reference, both
>only look at the namespace type and at the caller's own namespaces, so do
>the checks first and take the reference last.
>

Great catch!

>Fixes: 76b6f5dfb3fd ("nstree: add listns()")
>Signed-off-by: Norbert Szetei <norbert@doyensec.com>
>---
>A reproducer is available on request.

Please? Or a splat, or anything?


>
> kernel/nstree.c | 10 ++--------
> 1 file changed, 2 insertions(+), 8 deletions(-)
>
>diff --git a/kernel/nstree.c b/kernel/nstree.c
>index 6d12e5900ac0..831f279d174a 100644
>--- a/kernel/nstree.c
>+++ b/kernel/nstree.c
>@@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T))
> static inline struct ns_common *__must_check legitimize_ns(const struct
> klistns *kls,
> 							   struct ns_common *candidate)
> {
>-	struct ns_common *ns __free(ns_put) = NULL;
>-
> 	if (!ns_requested(kls, candidate))
> 		return NULL;
> 
>-	ns = ns_get_unless_inactive(candidate);
>-	if (!ns)
>-		return NULL;
>-
>-	if (!may_list_ns(kls, ns))
>+	if (!may_list_ns(kls, candidate))
> 		return NULL;
> 
>-	return no_free_ptr(ns);
>+	return ns_get_unless_inactive(candidate);
> }

Fix LGTM, 

+ CC akpm, he tends to look at fixes in kernel/, if brauner wants to merge this then that's ok :)

> 
> static ssize_t do_listns_userns(struct klistns *kls)
>

--- Thanks!
https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] nstree: check listing permission before taking a namespace reference
  2026-09-06 11:52 ` Bradley Morgan
@ 2026-09-06 11:52   ` Bradley Morgan
  2026-09-06 18:25   ` Norbert Szetei
  1 sibling, 0 replies; 6+ messages in thread
From: Bradley Morgan @ 2026-09-06 11:52 UTC (permalink / raw)
  To: norbert; +Cc: brauner, linux-fsdevel, linux-kernel, akpm

On 6 September 2026 12:52:04 BST, Bradley Morgan <brads@mainlining.org>
wrote:
>On 4 September 2026 15:28:05 BST, Norbert Szetei <norbert@doyensec.com>
>wrote:
>>legitimize_ns() takes a reference on the candidate namespace before
>>may_list_ns() has decided whether the caller may see it. The
>>__free(ns_put) cleanup on the denied path can drop the last reference to
>a
>>mount namespace while we still hold the rcu read lock, and put_mnt_ns()
>>may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make
>>sure reference are dropped outside of rcu lock") fixed for the put_user()
>>path. Neither ns_requested() nor may_list_ns() needs a reference, both
>>only look at the namespace type and at the caller's own namespaces, so do
>>the checks first and take the reference last.
>>
>
>Great catch!
>
>>Fixes: 76b6f5dfb3fd ("nstree: add listns()")
>>Signed-off-by: Norbert Szetei <norbert@doyensec.com>
>>---
>>A reproducer is available on request.
>
>Please? Or a splat, or anything?
>
>
>>
>> kernel/nstree.c | 10 ++--------
>> 1 file changed, 2 insertions(+), 8 deletions(-)
>>
>>diff --git a/kernel/nstree.c b/kernel/nstree.c
>>index 6d12e5900ac0..831f279d174a 100644
>>--- a/kernel/nstree.c
>>+++ b/kernel/nstree.c
>>@@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if
>(!IS_ERR_OR_NULL(_T)) ns_put(_T))
>> static inline struct ns_common *__must_check legitimize_ns(const struct
>> klistns *kls,
>> 							   struct ns_common *candidate)
>> {
>>-	struct ns_common *ns __free(ns_put) = NULL;
>>-
>> 	if (!ns_requested(kls, candidate))
>> 		return NULL;
>> 
>>-	ns = ns_get_unless_inactive(candidate);
>>-	if (!ns)
>>-		return NULL;
>>-
>>-	if (!may_list_ns(kls, ns))
>>+	if (!may_list_ns(kls, candidate))
>> 		return NULL;
>> 
>>-	return no_free_ptr(ns);
>>+	return ns_get_unless_inactive(candidate);
>> }
>
>Fix LGTM, 
>
>+ CC akpm, he tends to look at fixes in kernel/, if brauner wants to merge this then that's ok :)
>
>> 
>> static ssize_t do_listns_userns(struct klistns *kls)
>>
>
>--- Thanks!
>https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/

Forgot my tag, oops!

Reviewed-by: Bradley Morgan <brads@mainlining.org>

--- Thanks!
https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] nstree: check listing permission before taking a namespace reference
  2026-09-06 11:52 ` Bradley Morgan
  2026-09-06 11:52   ` Bradley Morgan
@ 2026-09-06 18:25   ` Norbert Szetei
  2026-09-06 18:26     ` Bradley Morgan
  1 sibling, 1 reply; 6+ messages in thread
From: Norbert Szetei @ 2026-09-06 18:25 UTC (permalink / raw)
  To: Bradley Morgan; +Cc: brauner, linux-fsdevel, linux-kernel, akpm

On Sep 6, 2026, at 13:52, Bradley Morgan <brads@mainlining.org> wrote:
> 
> On 4 September 2026 15:28:05 BST, Norbert Szetei <norbert@doyensec.com>
> wrote:
>> legitimize_ns() takes a reference on the candidate namespace before
>> may_list_ns() has decided whether the caller may see it. The
>> __free(ns_put) cleanup on the denied path can drop the last reference to a
>> mount namespace while we still hold the rcu read lock, and put_mnt_ns()
>> may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make
>> sure reference are dropped outside of rcu lock") fixed for the put_user()
>> path. Neither ns_requested() nor may_list_ns() needs a reference, both
>> only look at the namespace type and at the caller's own namespaces, so do
>> the checks first and take the reference last.
>> 
> 
> Great catch!
> 
>> Fixes: 76b6f5dfb3fd ("nstree: add listns()")
>> Signed-off-by: Norbert Szetei <norbert@doyensec.com>
>> ---
>> A reproducer is available on request.
> 
> Please? Or a splat, or anything?

Sure, both reproducer and splat below.

The reproducer uses two thread pools, one repeatedly calls listns for
CLONE_NEWNS, the other spins up short-lived mount namespaces via
unshare(CLONE_NEWUSER | CLONE_NEWNS) and lets them die.

The tmpfs mount in the child is the part that makes it show up quickly.

Run as an ordinary user, no capabilities, no root. I reproduced it on 
the latest mainline and also the latest ubuntu.

Reproducer:

#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/types.h>

#ifndef __NR_listns
#define __NR_listns 470
#endif

struct ns_id_req {
	__u32 size;
	__u32 spare;
	__u64 ns_id;
	__u32 ns_type;
	__u32 spare2;
	__u64 user_ns_id;
};

#define NR_IDS 4096

static volatile int stop;
static unsigned long nr_list, nr_churn;

static void handler(int sig)
{
	(void)sig;
	stop = 1;
}

static long listns(struct ns_id_req *req, __u64 *ids, size_t nr)
{
	return syscall(__NR_listns, req, ids, nr, 0);
}

static void *walker(void *unused)
{
	struct ns_id_req req = {
		.size = sizeof(req),
		.ns_type = CLONE_NEWNS,
	};
	__u64 ids[NR_IDS];

	(void)unused;
	while (!stop) {
		listns(&req, ids, NR_IDS);
		__atomic_fetch_add(&nr_list, 1, __ATOMIC_RELAXED);
	}
	return NULL;
}

static void child(void)
{
	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
		_exit(1);
	mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
	mount("none", "/tmp", "tmpfs", 0, NULL);
	_exit(0);
}

static void *churner(void *unused)
{
	(void)unused;
	while (!stop) {
		pid_t pid = fork();

		if (pid == 0)
			child();
		if (pid < 0) {
			usleep(1000);
			continue;
		}
		waitpid(pid, NULL, 0);
		__atomic_fetch_add(&nr_churn, 1, __ATOMIC_RELAXED);
	}
	return NULL;
}

int main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 60;
	int nr_walkers = argc > 2 ? atoi(argv[2]) : 6;
	int nr_churners = argc > 3 ? atoi(argv[3]) : 6;
	struct ns_id_req req = { .size = sizeof(req), .ns_type = CLONE_NEWNS };
	pthread_t th[64];
	__u64 ids[NR_IDS];
	int i, n = 0;
	long ret;

	if (nr_walkers + nr_churners > 64)
		return 1;

	ret = listns(&req, ids, NR_IDS);
	if (ret < 0) {
		perror("listns");
		return 1;
	}
	printf("uid %d sees %ld mount namespaces\n", getuid(), ret);

	signal(SIGALRM, handler);
	alarm(secs);

	for (i = 0; i < nr_walkers; i++)
		pthread_create(&th[n++], NULL, walker, NULL);
	for (i = 0; i < nr_churners; i++)
		pthread_create(&th[n++], NULL, churner, NULL);

	while (!stop)
		sleep(1);

	for (i = 0; i < n; i++)
		pthread_join(th[i], NULL);

	printf("listns=%lu unshare=%lu\n", nr_list, nr_churn);
	return 0;
}

Splat, on Ubuntu 7.0.0-30-generic (PREEMPT_DYNAMIC, no KASAN, no debug options):

[   24.499428] RIP: 0010:rcu_note_context_switch+0x238/0x2a0
[   24.499431] Code: 88 09 00 00 48 8d 93 88 09 00 00 48 39 d1 75 6a 48 23 47 20 74 4d 4c 89 e6 e8 64 f3 ff ff e9 46 fe ff ff 48 8d 3d b8 fe 99 02 <67> 48 0f b9 3a e9 02 fe ff ff 49 8b 7c 24 20 48 89 7d e0 e8 c0 1b
[   24.499433] RSP: 0018:ffffcfa383867830 EFLAGS: 00010002
[   24.499435] RAX: 0000000000000001 RBX: ffff8c33e1612ac0 RCX: 0000000000000000
[   24.499436] RDX: 0000000000000000 RSI: ffffffffb6527c27 RDI: ffffffffb7ce0450
[   24.499437] RBP: ffffcfa383867850 R08: 0000000000000000 R09: 0000000000000000
[   24.499438] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8c33fbb34580
[   24.499439] R13: 0000000000000000 R14: 0000000000000000 R15: ffffcfa383867910
[   24.499441] FS:  00007eb8b11fa6c0(0000) GS:ffff8c34438ff000(0000) knlGS:0000000000000000
[   24.499442] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   24.499443] CR2: 00007eb8b11f1e48 CR3: 000000016656a001 CR4: 0000000000f72ef0
[   24.499447] PKRU: 55555554
[   24.499448] Call Trace:
[   24.499450]  <TASK>
[   24.499452]  __schedule+0xcf/0x650
[   24.499456]  schedule+0x27/0x90
[   24.499458]  schedule_preempt_disabled+0x15/0x30
[   24.499460]  __mutex_lock.constprop.0+0x550/0xaf0
[   24.499461]  ? irqentry_exit+0x2e/0x6f0
[   24.499464]  ? irqentry_exit+0x2e/0x6f0
[   24.499466]  __mutex_lock_slowpath+0x13/0x20
[   24.499468]  mutex_lock+0x3b/0x50
[   24.499470]  exp_funnel_lock+0xb2/0x260
[   24.499472]  ? _raw_spin_unlock_irqrestore+0x11/0x60
[   24.499474]  ? ida_free+0x14e/0x160
[   24.499477]  synchronize_rcu_expedited+0xe7/0x220
[   24.499480]  namespace_unlock+0x26a/0x320
[   24.499484]  put_mnt_ns+0xd3/0x120
[   24.499486]  mntns_put+0xe/0x20
[   24.499488]  do_listns+0x13e/0x560
[   24.499491]  ? __do_sys_listns+0x126/0x2d0
[   24.499494]  __do_sys_listns+0x126/0x2d0
[   24.499496]  __x64_sys_listns+0x20/0x30
[   24.499498]  x64_sys_call+0x2366/0x2390
[   24.499501]  do_syscall_64+0x105/0x5a0
[   24.499503]  ? __audit_syscall_exit+0x36/0x120
[   24.499506]  ? security_capable+0x70/0x1e0
[   24.499510]  ? ns_capable_noaudit+0x34/0x60
[   24.499513]  ? mntns_put+0xe/0x20
[   24.499514]  ? do_listns+0x18a/0x560
[   24.499517]  ? __do_sys_listns+0x126/0x2d0
[   24.499520]  ? __audit_syscall_exit+0x36/0x120
[   24.499521]  ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
[   24.499524]  ? do_syscall_64+0x140/0x5a0
[   24.499525]  ? ns_capable_noaudit+0x34/0x60
[   24.499527]  ? mntns_put+0xe/0x20
[   24.499529]  ? do_listns+0x18a/0x560
[   24.499532]  ? __do_sys_listns+0x126/0x2d0
[   24.499534]  ? __audit_syscall_exit+0x36/0x120
[   24.499536]  ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
[   24.499538]  ? do_syscall_64+0x140/0x5a0
[   24.499540]  ? exc_page_fault+0x94/0x1e0
[   24.499541]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[   24.499543] RIP: 0033:0x7eb8b3b34c8d
[   24.499545] Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 4b d1 0d 00 f7 d8 64 89 01 48
[   24.499547] RSP: 002b:00007eb8b11f1e18 EFLAGS: 00000202 ORIG_RAX: 00000000000001d6
[   24.499555] RAX: ffffffffffffffda RBX: 00007eb8b11fa6c0 RCX: 00007eb8b3b34c8d
[   24.499556] RDX: 0000000000001000 RSI: 00007eb8b11f1e80 RDI: 00007eb8b11f1e60
[   24.499558] RBP: 00007eb8b11f1e40 R08: 0000000000000000 R09: 0000000000000000
[   24.499559] R10: 0000000000000000 R11: 0000000000000202 R12: 00007eb8b11fa6c0
[   24.499560] R13: 00007ffd3d46d640 R14: 00007eb8b11face4 R15: 00007ffd3d46d747
[   24.499562]  </TASK>
[   24.499563] ---[ end trace 0000000000000000 ]---

>> 
>> kernel/nstree.c | 10 ++--------
>> 1 file changed, 2 insertions(+), 8 deletions(-)
>> 
>> diff --git a/kernel/nstree.c b/kernel/nstree.c
>> index 6d12e5900ac0..831f279d174a 100644
>> --- a/kernel/nstree.c
>> +++ b/kernel/nstree.c
>> @@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T))
>> static inline struct ns_common *__must_check legitimize_ns(const struct
>> klistns *kls,
>>    struct ns_common *candidate)
>> {
>> - struct ns_common *ns __free(ns_put) = NULL;
>> -
>> if (!ns_requested(kls, candidate))
>> return NULL;
>> 
>> - ns = ns_get_unless_inactive(candidate);
>> - if (!ns)
>> - return NULL;
>> -
>> - if (!may_list_ns(kls, ns))
>> + if (!may_list_ns(kls, candidate))
>> return NULL;
>> 
>> - return no_free_ptr(ns);
>> + return ns_get_unless_inactive(candidate);
>> }
> 
> Fix LGTM, 
> 
> + CC akpm, he tends to look at fixes in kernel/, if brauner wants to merge this then that's ok :)

Thank you.

>> static ssize_t do_listns_userns(struct klistns *kls)
>> 
> 
> --- Thanks!
> https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] nstree: check listing permission before taking a namespace reference
  2026-09-06 18:25   ` Norbert Szetei
@ 2026-09-06 18:26     ` Bradley Morgan
  2026-09-09  8:04       ` Christian Brauner
  0 siblings, 1 reply; 6+ messages in thread
From: Bradley Morgan @ 2026-09-06 18:26 UTC (permalink / raw)
  To: Norbert Szetei; +Cc: brauner, linux-fsdevel, linux-kernel, akpm

On 6 September 2026 19:25:22 BST, Norbert Szetei <norbert@doyensec.com>
wrote:
>On Sep 6, 2026, at 13:52, Bradley Morgan <brads@mainlining.org> wrote:
>> 
>> On 4 September 2026 15:28:05 BST, Norbert Szetei <norbert@doyensec.com>
>> wrote:
>>> legitimize_ns() takes a reference on the candidate namespace before
>>> may_list_ns() has decided whether the caller may see it. The
>>> __free(ns_put) cleanup on the denied path can drop the last reference
>to a
>>> mount namespace while we still hold the rcu read lock, and put_mnt_ns()
>>> may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns:
>make
>>> sure reference are dropped outside of rcu lock") fixed for the
>put_user()
>>> path. Neither ns_requested() nor may_list_ns() needs a reference, both
>>> only look at the namespace type and at the caller's own namespaces, so
>do
>>> the checks first and take the reference last.
>>> 
>> 
>> Great catch!
>> 
>>> Fixes: 76b6f5dfb3fd ("nstree: add listns()")
>>> Signed-off-by: Norbert Szetei <norbert@doyensec.com>
>>> ---
>>> A reproducer is available on request.
>> 
>> Please? Or a splat, or anything?
>
>Sure, both reproducer and splat below.
>
>The reproducer uses two thread pools, one repeatedly calls listns for
>CLONE_NEWNS, the other spins up short-lived mount namespaces via
>unshare(CLONE_NEWUSER | CLONE_NEWNS) and lets them die.
>
>The tmpfs mount in the child is the part that makes it show up quickly.
>
>Run as an ordinary user, no capabilities, no root. I reproduced it on 
>the latest mainline and also the latest ubuntu.
>
>Reproducer:
>
>#define _GNU_SOURCE
>#include <errno.h>
>#include <pthread.h>
>#include <sched.h>
>#include <signal.h>
>#include <stdio.h>
>#include <stdlib.h>
>#include <unistd.h>
>#include <sys/mount.h>
>#include <sys/syscall.h>
>#include <sys/types.h>
>#include <sys/wait.h>
>#include <linux/types.h>
>
>#ifndef __NR_listns
>#define __NR_listns 470
>#endif
>
>struct ns_id_req {
>	__u32 size;
>	__u32 spare;
>	__u64 ns_id;
>	__u32 ns_type;
>	__u32 spare2;
>	__u64 user_ns_id;
>};
>
>#define NR_IDS 4096
>
>static volatile int stop;
>static unsigned long nr_list, nr_churn;
>
>static void handler(int sig)
>{
>	(void)sig;
>	stop = 1;
>}
>
>static long listns(struct ns_id_req *req, __u64 *ids, size_t nr)
>{
>	return syscall(__NR_listns, req, ids, nr, 0);
>}
>
>static void *walker(void *unused)
>{
>	struct ns_id_req req = {
>		.size = sizeof(req),
>		.ns_type = CLONE_NEWNS,
>	};
>	__u64 ids[NR_IDS];
>
>	(void)unused;
>	while (!stop) {
>		listns(&req, ids, NR_IDS);
>		__atomic_fetch_add(&nr_list, 1, __ATOMIC_RELAXED);
>	}
>	return NULL;
>}
>
>static void child(void)
>{
>	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
>		_exit(1);
>	mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
>	mount("none", "/tmp", "tmpfs", 0, NULL);
>	_exit(0);
>}
>
>static void *churner(void *unused)
>{
>	(void)unused;
>	while (!stop) {
>		pid_t pid = fork();
>
>		if (pid == 0)
>			child();
>		if (pid < 0) {
>			usleep(1000);
>			continue;
>		}
>		waitpid(pid, NULL, 0);
>		__atomic_fetch_add(&nr_churn, 1, __ATOMIC_RELAXED);
>	}
>	return NULL;
>}
>
>int main(int argc, char **argv)
>{
>	int secs = argc > 1 ? atoi(argv[1]) : 60;
>	int nr_walkers = argc > 2 ? atoi(argv[2]) : 6;
>	int nr_churners = argc > 3 ? atoi(argv[3]) : 6;
>	struct ns_id_req req = { .size = sizeof(req), .ns_type = CLONE_NEWNS };
>	pthread_t th[64];
>	__u64 ids[NR_IDS];
>	int i, n = 0;
>	long ret;
>
>	if (nr_walkers + nr_churners > 64)
>		return 1;
>
>	ret = listns(&req, ids, NR_IDS);
>	if (ret < 0) {
>		perror("listns");
>		return 1;
>	}
>	printf("uid %d sees %ld mount namespaces\n", getuid(), ret);
>
>	signal(SIGALRM, handler);
>	alarm(secs);
>
>	for (i = 0; i < nr_walkers; i++)
>		pthread_create(&th[n++], NULL, walker, NULL);
>	for (i = 0; i < nr_churners; i++)
>		pthread_create(&th[n++], NULL, churner, NULL);
>
>	while (!stop)
>		sleep(1);
>
>	for (i = 0; i < n; i++)
>		pthread_join(th[i], NULL);
>
>	printf("listns=%lu unshare=%lu\n", nr_list, nr_churn);
>	return 0;
>}
>
>Splat, on Ubuntu 7.0.0-30-generic (PREEMPT_DYNAMIC, no KASAN, no debug
>options):
>
>[   24.499428] RIP: 0010:rcu_note_context_switch+0x238/0x2a0
>[   24.499431] Code: 88 09 00 00 48 8d 93 88 09 00 00 48 39 d1 75 6a 48 23
>47 20 74 4d 4c 89 e6 e8 64 f3 ff ff e9 46 fe ff ff 48 8d 3d b8 fe 99 02
><67> 48 0f b9 3a e9 02 fe ff ff 49 8b 7c 24 20 48 89 7d e0 e8 c0 1b
>[   24.499433] RSP: 0018:ffffcfa383867830 EFLAGS: 00010002
>[   24.499435] RAX: 0000000000000001 RBX: ffff8c33e1612ac0 RCX:
>0000000000000000
>[   24.499436] RDX: 0000000000000000 RSI: ffffffffb6527c27 RDI:
>ffffffffb7ce0450
>[   24.499437] RBP: ffffcfa383867850 R08: 0000000000000000 R09:
>0000000000000000
>[   24.499438] R10: 0000000000000000 R11: 0000000000000000 R12:
>ffff8c33fbb34580
>[   24.499439] R13: 0000000000000000 R14: 0000000000000000 R15:
>ffffcfa383867910
>[   24.499441] FS:  00007eb8b11fa6c0(0000) GS:ffff8c34438ff000(0000)
>knlGS:0000000000000000
>[   24.499442] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
>[   24.499443] CR2: 00007eb8b11f1e48 CR3: 000000016656a001 CR4:
>0000000000f72ef0
>[   24.499447] PKRU: 55555554
>[   24.499448] Call Trace:
>[   24.499450]  <TASK>
>[   24.499452]  __schedule+0xcf/0x650
>[   24.499456]  schedule+0x27/0x90
>[   24.499458]  schedule_preempt_disabled+0x15/0x30
>[   24.499460]  __mutex_lock.constprop.0+0x550/0xaf0
>[   24.499461]  ? irqentry_exit+0x2e/0x6f0
>[   24.499464]  ? irqentry_exit+0x2e/0x6f0
>[   24.499466]  __mutex_lock_slowpath+0x13/0x20
>[   24.499468]  mutex_lock+0x3b/0x50
>[   24.499470]  exp_funnel_lock+0xb2/0x260
>[   24.499472]  ? _raw_spin_unlock_irqrestore+0x11/0x60
>[   24.499474]  ? ida_free+0x14e/0x160
>[   24.499477]  synchronize_rcu_expedited+0xe7/0x220
>[   24.499480]  namespace_unlock+0x26a/0x320
>[   24.499484]  put_mnt_ns+0xd3/0x120
>[   24.499486]  mntns_put+0xe/0x20
>[   24.499488]  do_listns+0x13e/0x560
>[   24.499491]  ? __do_sys_listns+0x126/0x2d0
>[   24.499494]  __do_sys_listns+0x126/0x2d0
>[   24.499496]  __x64_sys_listns+0x20/0x30
>[   24.499498]  x64_sys_call+0x2366/0x2390
>[   24.499501]  do_syscall_64+0x105/0x5a0
>[   24.499503]  ? __audit_syscall_exit+0x36/0x120
>[   24.499506]  ? security_capable+0x70/0x1e0
>[   24.499510]  ? ns_capable_noaudit+0x34/0x60
>[   24.499513]  ? mntns_put+0xe/0x20
>[   24.499514]  ? do_listns+0x18a/0x560
>[   24.499517]  ? __do_sys_listns+0x126/0x2d0
>[   24.499520]  ? __audit_syscall_exit+0x36/0x120
>[   24.499521]  ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
>[   24.499524]  ? do_syscall_64+0x140/0x5a0
>[   24.499525]  ? ns_capable_noaudit+0x34/0x60
>[   24.499527]  ? mntns_put+0xe/0x20
>[   24.499529]  ? do_listns+0x18a/0x560
>[   24.499532]  ? __do_sys_listns+0x126/0x2d0
>[   24.499534]  ? __audit_syscall_exit+0x36/0x120
>[   24.499536]  ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
>[   24.499538]  ? do_syscall_64+0x140/0x5a0
>[   24.499540]  ? exc_page_fault+0x94/0x1e0
>[   24.499541]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
>[   24.499543] RIP: 0033:0x7eb8b3b34c8d
>[   24.499545] Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48
>89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05
><48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 4b d1 0d 00 f7 d8 64 89 01 48
>[   24.499547] RSP: 002b:00007eb8b11f1e18 EFLAGS: 00000202 ORIG_RAX:
>00000000000001d6
>[   24.499555] RAX: ffffffffffffffda RBX: 00007eb8b11fa6c0 RCX:
>00007eb8b3b34c8d
>[   24.499556] RDX: 0000000000001000 RSI: 00007eb8b11f1e80 RDI:
>00007eb8b11f1e60
>[   24.499558] RBP: 00007eb8b11f1e40 R08: 0000000000000000 R09:
>0000000000000000
>[   24.499559] R10: 0000000000000000 R11: 0000000000000202 R12:
>00007eb8b11fa6c0
>[   24.499560] R13: 00007ffd3d46d640 R14: 00007eb8b11face4 R15:
>00007ffd3d46d747
>[   24.499562]  </TASK>
>[   24.499563] ---[ end trace 0000000000000000 ]---
>
>>> 
>>> kernel/nstree.c | 10 ++--------
>>> 1 file changed, 2 insertions(+), 8 deletions(-)
>>> 
>>> diff --git a/kernel/nstree.c b/kernel/nstree.c
>>> index 6d12e5900ac0..831f279d174a 100644
>>> --- a/kernel/nstree.c
>>> +++ b/kernel/nstree.c
>>> @@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if
>(!IS_ERR_OR_NULL(_T)) ns_put(_T))
>>> static inline struct ns_common *__must_check legitimize_ns(const struct
>>> klistns *kls,
>>>    struct ns_common *candidate)
>>> {
>>> - struct ns_common *ns __free(ns_put) = NULL;
>>> -
>>> if (!ns_requested(kls, candidate))
>>> return NULL;
>>> 
>>> - ns = ns_get_unless_inactive(candidate);
>>> - if (!ns)
>>> - return NULL;
>>> -
>>> - if (!may_list_ns(kls, ns))
>>> + if (!may_list_ns(kls, candidate))
>>> return NULL;
>>> 
>>> - return no_free_ptr(ns);
>>> + return ns_get_unless_inactive(candidate);
>>> }
>> 
>> Fix LGTM, 
>> 
>> + CC akpm, he tends to look at fixes in kernel/, if brauner wants to
>merge this then that's ok :)
>
>Thank you.
>
>>> static ssize_t do_listns_userns(struct klistns *kls)
>>> 
>> 
>> --- Thanks!
>>
>https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/
>
>
>
For a V2, could you include the splat in the description?
--- Thanks!
https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] nstree: check listing permission before taking a namespace reference
  2026-09-06 18:26     ` Bradley Morgan
@ 2026-09-09  8:04       ` Christian Brauner
  0 siblings, 0 replies; 6+ messages in thread
From: Christian Brauner @ 2026-09-09  8:04 UTC (permalink / raw)
  To: Bradley Morgan; +Cc: Norbert Szetei, linux-fsdevel, linux-kernel, akpm

On Sun, Sep 06, 2026 at 07:26:30PM +0100, Bradley Morgan wrote:
> On 6 September 2026 19:25:22 BST, Norbert Szetei <norbert@doyensec.com>
> wrote:
> >On Sep 6, 2026, at 13:52, Bradley Morgan <brads@mainlining.org> wrote:
> >> 
> >> On 4 September 2026 15:28:05 BST, Norbert Szetei <norbert@doyensec.com>
> >> wrote:
> >>> legitimize_ns() takes a reference on the candidate namespace before
> >>> may_list_ns() has decided whether the caller may see it. The
> >>> __free(ns_put) cleanup on the denied path can drop the last reference
> >to a
> >>> mount namespace while we still hold the rcu read lock, and put_mnt_ns()
> >>> may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns:
> >make
> >>> sure reference are dropped outside of rcu lock") fixed for the
> >put_user()
> >>> path. Neither ns_requested() nor may_list_ns() needs a reference, both
> >>> only look at the namespace type and at the caller's own namespaces, so
> >do
> >>> the checks first and take the reference last.
> >>> 
> >> 
> >> Great catch!
> >> 
> >>> Fixes: 76b6f5dfb3fd ("nstree: add listns()")
> >>> Signed-off-by: Norbert Szetei <norbert@doyensec.com>
> >>> ---
> >>> A reproducer is available on request.
> >> 
> >> Please? Or a splat, or anything?
> >
> >Sure, both reproducer and splat below.

The repro is available under the patch link in the commit. So no need to
dump all of this in there.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-09  8:04 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-04 14:28 [PATCH] nstree: check listing permission before taking a namespace reference Norbert Szetei
2026-09-06 11:52 ` Bradley Morgan
2026-09-06 11:52   ` Bradley Morgan
2026-09-06 18:25   ` Norbert Szetei
2026-09-06 18:26     ` Bradley Morgan
2026-09-09  8:04       ` Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®