From: Jeff Layton <jlayton@kernel.org>
To: Christian Brauner <brauner@kernel.org>, linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
Miklos Szeredi <mszeredi@redhat.com>, shaos <shawnq1@163.com>,
linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH 5/6] listns: don't wrap around with a U64_MAX cursor
Date: Wed, 21 Oct 2026 07:55:45 -0400 [thread overview]
Message-ID: <33ec91ed2a10afecee87c08b623f3d6f6949a4f7.camel@kernel.org> (raw)
In-Reply-To: <20261021-work-mount-statmount_listns_fixes-v1-5-8917a4c1bd14@kernel.org>
On Wed, 2026-10-21 at 13:46 +0200, Christian Brauner wrote:
> 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;
Reviewed-by: Jeff Layton <jlayton@kernel.org>
next prev parent reply other threads:[~2026-10-21 11:55 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 ` [PATCH 5/6] listns: don't wrap around " Christian Brauner
2026-10-21 11:55 ` Jeff Layton [this message]
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=33ec91ed2a10afecee87c08b623f3d6f6949a4f7.camel@kernel.org \
--to=jlayton@kernel.org \
--cc=brauner@kernel.org \
--cc=jack@suse.cz \
--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®