mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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>

  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®