mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
	 Miklos Szeredi <mszeredi@redhat.com>,
	Jeff Layton <jlayton@kernel.org>,  shaos <shawnq1@163.com>,
	linux-kernel@vger.kernel.org,  stable@vger.kernel.org,
	"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 5/6] listns: don't wrap around with a U64_MAX cursor
Date: Wed, 21 Oct 2026 13:46:58 +0200	[thread overview]
Message-ID: <20261021-work-mount-statmount_listns_fixes-v1-5-8917a4c1bd14@kernel.org> (raw)
In-Reply-To: <20261021-work-mount-statmount_listns_fixes-v1-0-8917a4c1bd14@kernel.org>

do_listns() and do_listns_userns() take the last namespace id the caller
saw and look up the first namespace after it by searching the rbtree for
last_ns_id + 1. With U64_MAX that wraps around to zero and the search
returns the namespace with the smallest id. So a cursor at the largest
possible id restarts the listing from the beginning instead of failing
with ENOENT like a cursor of U64_MAX - 1 does. That's the case for the
unified tree, the type-specific trees and the owner tree alike.

No namespace will ever have that id. They come from a counter and a
caller that only feeds back ids it got from us can't hit this. It's
still wrong.

Instead of adding one to the cursor and searching for the smallest id
that's greater or equal make the lookup helpers search for the smallest
id that's strictly greater than the cursor. That's what both callers
want anyway and there's nothing left to overflow.

Reproducer, unprivileged:

    #define _GNU_SOURCE
    #include <errno.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #ifndef __NR_listns
    #define __NR_listns 470
    #endif
    #define CLONE_NEWUSER		0x10000000U
    #define LISTNS_CURRENT_USER	0xffffffffffffffffULL
    struct ns_id_req {
    	uint32_t size, spare;
    	uint64_t ns_id;
    	uint32_t ns_type, spare2;
    	uint64_t user_ns_id;
    };
    static void list(const char *tree, uint32_t type, uint64_t owner,
    		 uint64_t cursor)
    {
    	struct ns_id_req req = {
    		.size = sizeof(req), .ns_id = cursor, .ns_type = type,
    		.user_ns_id = owner,
    	};
    	uint64_t ids[8];
    	long ret;
    	errno = 0;
    	ret = syscall(__NR_listns, &req, ids, 8, 0);
    	printf("%s cursor %llu: ret %ld errno %d", tree,
    	       (unsigned long long)cursor, ret, errno);
    	for (long i = 0; i < ret && i < 3; i++)
    		printf("%s%llu", i ? "," : " ids ", (unsigned long long)ids[i]);
    	printf("%s\n", ret > 3 ? ",..." : "");
    }
    int main(void)
    {
    	static const uint64_t cursors[] = { 0, UINT64_MAX - 1, UINT64_MAX };
    	for (int i = 0; i < 3; i++)
    		list("unified", 0, 0, cursors[i]);
    	for (int i = 0; i < 3; i++)
    		list("userns", CLONE_NEWUSER, 0, cursors[i]);
    	for (int i = 0; i < 3; i++)
    		list("owner", 0, LISTNS_CURRENT_USER, cursors[i]);
    	return 0;
    }

Before, on 7.3.0-rc7-00252-g4e3259b08c54 (vfs.fixes) in a VM:

    unified cursor 0: ret 8 errno 0 ids 1,2,3,...
    unified cursor 18446744073709551614: ret -1 errno 2
    unified cursor 18446744073709551615: ret 8 errno 0 ids 1,2,3,...
    userns cursor 0: ret 1 errno 0 ids 3
    userns cursor 18446744073709551614: ret -1 errno 2
    userns cursor 18446744073709551615: ret 1 errno 0 ids 3
    owner cursor 0: ret 8 errno 0 ids 1,2,4,...
    owner cursor 18446744073709551614: ret -1 errno 2
    owner cursor 18446744073709551615: ret 8 errno 0 ids 1,2,4,...

After:

    unified cursor 0: ret 8 errno 0 ids 1,2,3,...
    unified cursor 18446744073709551614: ret -1 errno 2
    unified cursor 18446744073709551615: ret -1 errno 2
    userns cursor 0: ret 1 errno 0 ids 3
    userns cursor 18446744073709551614: ret -1 errno 2
    userns cursor 18446744073709551615: ret -1 errno 2
    owner cursor 0: ret 8 errno 0 ids 1,2,4,...
    owner cursor 18446744073709551614: ret -1 errno 2
    owner cursor 18446744073709551615: ret -1 errno 2

Reported-by: shaos <shawnq1@163.com>
Link: https://lore.kernel.org/r/20261020110732.959552-1-shawnq1@163.com
Fixes: 76b6f5dfb3fd ("nstree: add listns()")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/nstree.c | 29 +++++++++++------------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/kernel/nstree.c b/kernel/nstree.c
index 831f279d174a..58b2b55fa8d2 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -459,10 +459,10 @@ static inline int prepare_klistns(struct klistns *kls, struct ns_id_req *kreq,
 }
 
 /*
- * Lookup a namespace owned by owner with id >= ns_id.
- * Returns the namespace with the smallest id that is >= ns_id.
+ * Lookup a namespace owned by owner with id > ns_id.
+ * Returns the namespace with the smallest id that is > ns_id.
  */
-static struct ns_common *lookup_ns_owner_at(u64 ns_id, struct ns_common *owner)
+static struct ns_common *lookup_ns_owner_after(u64 ns_id, struct ns_common *owner)
 {
 	struct ns_common *ret = NULL;
 	struct rb_node *node;
@@ -476,10 +476,8 @@ static struct ns_common *lookup_ns_owner_at(u64 ns_id, struct ns_common *owner)
 		struct ns_common *ns;
 
 		ns = node_to_ns_owner(node);
-		if (ns_id <= ns->ns_id) {
+		if (ns_id < ns->ns_id) {
 			ret = ns;
-			if (ns_id == ns->ns_id)
-				break;
 			node = node->rb_left;
 		} else {
 			node = node->rb_right;
@@ -565,7 +563,7 @@ static ssize_t do_listns_userns(struct klistns *kls)
 	 * then use it's list entry to iterate from there.
 	 */
 	if (kls->last_ns_id) {
-		kls->first_ns = lookup_ns_owner_at(kls->last_ns_id + 1, ns);
+		kls->first_ns = lookup_ns_owner_after(kls->last_ns_id, ns);
 		if (!kls->first_ns)
 			return -ENOENT;
 		first_ns = kls->first_ns;
@@ -613,10 +611,10 @@ static ssize_t do_listns_userns(struct klistns *kls)
 }
 
 /*
- * Lookup a namespace with id >= ns_id in either the unified tree or a type-specific tree.
- * Returns the namespace with the smallest id that is >= ns_id.
+ * Lookup a namespace with id > ns_id in either the unified tree or a type-specific tree.
+ * Returns the namespace with the smallest id that is > ns_id.
  */
-static struct ns_common *lookup_ns_id_at(u64 ns_id, int ns_type)
+static struct ns_common *lookup_ns_id_after(u64 ns_id, int ns_type)
 {
 	struct ns_common *ret = NULL;
 	struct ns_tree_root *ns_tree = NULL;
@@ -643,13 +641,8 @@ static struct ns_common *lookup_ns_id_at(u64 ns_id, int ns_type)
 		else
 			ns = node_to_ns_unified(node);
 
-		if (ns_id <= ns->ns_id) {
-			if (ns_type)
-				ret = node_to_ns(node);
-			else
-				ret = node_to_ns_unified(node);
-			if (ns_id == ns->ns_id)
-				break;
+		if (ns_id < ns->ns_id) {
+			ret = ns;
 			node = node->rb_left;
 		} else {
 			node = node->rb_right;
@@ -708,7 +701,7 @@ static ssize_t do_listns(struct klistns *kls)
 	}
 
 	if (kls->last_ns_id) {
-		kls->first_ns = lookup_ns_id_at(kls->last_ns_id + 1, ns_type);
+		kls->first_ns = lookup_ns_id_after(kls->last_ns_id, ns_type);
 		if (!kls->first_ns)
 			return -ENOENT;
 		first_ns = kls->first_ns;

-- 
2.53.0


  parent reply	other threads:[~2026-10-21 11:47 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 11:46 [PATCH 0/6] fs: fixes for statmount() short buffers and listmount()/listns() cursors Christian Brauner
2026-10-21 11:46 ` [PATCH 1/6] statmount: don't copy the reserved empty string past the buffer Christian Brauner
2026-10-21 11:52   ` Jeff Layton
2026-10-21 15:18   ` Jan Kara
2026-10-21 11:46 ` [PATCH 2/6] selftests/filesystems: check short buffers for statmount() strings Christian Brauner
2026-10-21 11:46 ` [PATCH 3/6] listmount: don't wrap around with a U64_MAX cursor Christian Brauner
2026-10-21 11:52   ` Jeff Layton
2026-10-21 15:25   ` Jan Kara
2026-10-21 11:46 ` [PATCH 4/6] selftests/filesystems: check listmount() " Christian Brauner
2026-10-21 11:46 ` Christian Brauner [this message]
2026-10-21 11:55   ` [PATCH 5/6] listns: don't wrap around " Jeff Layton
2026-10-21 15:29   ` Jan Kara
2026-10-21 11:46 ` [PATCH 6/6] selftests/namespaces: check listns() " Christian Brauner
2026-10-21 13:18 ` [PATCH 0/6] fs: fixes for statmount() short buffers and listmount()/listns() cursors 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=20261021-work-mount-statmount_listns_fixes-v1-5-8917a4c1bd14@kernel.org \
    --to=brauner@kernel.org \
    --cc=jack@suse.cz \
    --cc=jlayton@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mszeredi@redhat.com \
    --cc=shawnq1@163.com \
    --cc=stable@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /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®