mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Bradley Morgan <brads@mainlining.org>
To: Norbert Szetei <norbert@doyensec.com>
Cc: brauner@kernel.org, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, akpm@linux-foundation.org
Subject: Re: [PATCH] nstree: check listing permission before taking a namespace reference
Date: Sun, 06 Sep 2026 19:26:30 +0100	[thread overview]
Message-ID: <F86352E4-FECE-49F6-98B7-8B5DBE49FE0E@mainlining.org> (raw)
In-Reply-To: <A0ABE170-488A-49CD-8A6C-4D9EB94A18A8@doyensec.com>

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/

  reply	other threads:[~2026-09-06 18:26 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 14:28 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 [this message]
2026-09-09  8:04       ` Christian Brauner

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=F86352E4-FECE-49F6-98B7-8B5DBE49FE0E@mainlining.org \
    --to=brads@mainlining.org \
    --cc=akpm@linux-foundation.org \
    --cc=brauner@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=norbert@doyensec.com \
    /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

all inboxes | Powered by JetHome®