[PATCH 3/4] nstree: Fix spurious ENOENT in listns pagination during grace period

From: Yohei Kojima

Date: Sun Apr 05 2026 - 12:55:14 EST


Fix false ENOENT returned from listns when (1) pagination is used
(req.ns_id != 0) and (2) listns tries to start enumeration from a
destroyed or inactive namespace.

The cause was that lookup_ns_id_at(kls->last_ns_id + 1, ...) returned
NULL if the first namespace after ns_id was destroyed or inactivated
like below: (Note that we can take nstree as a list as it is an rbtree
sorted by ns id.)

A: active namespace
D: destroyed (or inactive) namespace

+-----+-----+-----+-----+-----+-----+-----+-----+
state: | A | A | A | D | D | A | A | A |
ns_id: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+-----+-----+-----+-----+-----+-----+-----+-----+
| |
| +-- (kls->last_ns_id + 1)
+-- req.ns_id = 3

In this case, lookup_ns_id_at() returns NULL, which results in -ENOENT
returned from do_listns() although three namespaces remains in the nstree.

The bug is fixed by iterating over the nstree's internal list until it
reaches the first active namespace.

Fixes: 76b6f5dfb3fd ("nstree: add listns()")
Signed-off-by: Yohei Kojima <yk@xxxxxxxxx>
---
kernel/nstree.c | 68 +++++++++++++++++++++++++++++++++----------------
1 file changed, 46 insertions(+), 22 deletions(-)

diff --git a/kernel/nstree.c b/kernel/nstree.c
index 6d12e5900ac0..476d22203ee0 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -618,14 +618,32 @@ static ssize_t do_listns_userns(struct klistns *kls)
return ret;
}

+static inline struct ns_common *next_ns_common(struct ns_common *ns,
+ struct ns_tree_root *ns_tree)
+{
+ if (ns_tree)
+ return list_entry_rcu(ns->ns_tree_node.ns_list_entry.next, struct ns_common, ns_tree_node.ns_list_entry);
+ return list_entry_rcu(ns->ns_unified_node.ns_list_entry.next, struct ns_common, ns_unified_node.ns_list_entry);
+}
+
+static inline bool ns_common_is_head(struct ns_common *ns,
+ const struct list_head *head,
+ struct ns_tree_root *ns_tree)
+{
+ if (ns_tree)
+ return &ns->ns_tree_node.ns_list_entry == head;
+ return &ns->ns_unified_node.ns_list_entry == head;
+}
+
/*
* 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)
{
- struct ns_common *ret = NULL;
+ struct ns_common *min = NULL, *ret = NULL;
struct ns_tree_root *ns_tree = NULL;
+ struct list_head *head;
struct rb_node *node;

if (ns_type) {
@@ -651,9 +669,9 @@ static struct ns_common *lookup_ns_id_at(u64 ns_id, int ns_type)

if (ns_id <= ns->ns_id) {
if (ns_type)
- ret = node_to_ns(node);
+ min = node_to_ns(node);
else
- ret = node_to_ns_unified(node);
+ min = node_to_ns_unified(node);
if (ns_id == ns->ns_id)
break;
node = node->rb_left;
@@ -662,8 +680,31 @@ static struct ns_common *lookup_ns_id_at(u64 ns_id, int ns_type)
}
}

- if (ret)
- ret = ns_get_unless_inactive(ret);
+ if (!min)
+ return NULL;
+ /*
+ * Now min->ns_id is the minimum id where min->ns_id >= ns_id holds,
+ * but min could be inactive or destroyed here, therefore
+ * ns_get_unless_inactive(min) could return NULL.
+ *
+ * To handle this case, try acquiring the next ns until it reaches the
+ * first valid ns.
+ */
+ if (ns_tree)
+ head = &ns_tree->ns_list_head;
+ else
+ head = &ns_unified_root.ns_list_head;
+
+ while (!ns_common_is_head(min, head, ns_tree)) {
+ ret = ns_get_unless_inactive(min);
+ if (ret)
+ break;
+
+ rcu_read_lock();
+ min = next_ns_common(min, ns_tree);
+ rcu_read_unlock();
+ }
+
return ret;
}

@@ -675,23 +716,6 @@ static inline struct ns_common *first_ns_common(const struct list_head *head,
return list_entry_rcu(head->next, struct ns_common, ns_unified_node.ns_list_entry);
}

-static inline struct ns_common *next_ns_common(struct ns_common *ns,
- struct ns_tree_root *ns_tree)
-{
- if (ns_tree)
- return list_entry_rcu(ns->ns_tree_node.ns_list_entry.next, struct ns_common, ns_tree_node.ns_list_entry);
- return list_entry_rcu(ns->ns_unified_node.ns_list_entry.next, struct ns_common, ns_unified_node.ns_list_entry);
-}
-
-static inline bool ns_common_is_head(struct ns_common *ns,
- const struct list_head *head,
- struct ns_tree_root *ns_tree)
-{
- if (ns_tree)
- return &ns->ns_tree_node.ns_list_entry == head;
- return &ns->ns_unified_node.ns_list_entry == head;
-}
-
static ssize_t do_listns(struct klistns *kls)
{
u64 __user *ns_ids = kls->uns_ids;
--
2.52.0