nstree: don't end listns() at an inactive namespace

listns() resumes after the last returned id by looking up the first
namespace with a larger id with lookup_ns_id_after(), or with
lookup_ns_owner_after() when filtering by owner. Both also take a
reference with ns_get_unless_inactive(). If that namespace is inactive,
e.g. a network namespace that is only pinned by a socket or a user
namespace that is only pinned by a file's credentials, or if it is being
torn down, the lookup returns NULL and listns() fails with -ENOENT.
Userspace takes that as the end of the list, so every namespace after
it is silently missed:

  ids 10 (active), 11 (inactive), 12 (active)
  listns(ns_id = 0,  nr = 1)  -> 10
  listns(ns_id = 10, nr = 1)  -> lookup_ns_id_after(10) -> NULL, -ENOENT
  12 is never listed

A call without a cursor doesn't have this problem because the loop just
skips the namespaces that legitimize_ns() rejects.

The reference only kept the starting point valid until the RCU walk
began. Look it up under rcu_read_lock() instead, without taking a
reference. The namespace can't be freed before the RCU read-side section
ends, and if it is unlinked in the meantime its ->next still leads back
into the list, which the walk already relies on for every other entry.
The starting namespace then goes through legitimize_ns() like every
other one, and kls->first_ns isn't needed anymore.

Reported-by: shaos <shawnq1@163.com>
Link: https://lore.kernel.org/r/20261020110746.959806-1-shawnq1@163.com
Fixes: 76b6f5dfb3 ("nstree: add listns()")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Josef Bacik 2026-10-04 00:10:59 +00:00 committed by Christian Brauner
parent 02a336a831
commit da29ac46d3
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2

View File

@ -408,15 +408,12 @@ struct klistns {
u32 ns_type;
struct user_namespace *user_ns;
bool userns_capable;
struct ns_common *first_ns;
};
static void __free_klistns_free(const struct klistns *kls)
{
if (kls->user_ns_id != LISTNS_CURRENT_USER)
put_user_ns(kls->user_ns);
if (kls->first_ns && kls->first_ns->ops)
kls->first_ns->ops->put(kls->first_ns);
}
#define NS_ALL (PID_NS | USER_NS | MNT_NS | UTS_NS | IPC_NS | NET_NS | CGROUP_NS | TIME_NS)
@ -461,6 +458,9 @@ 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.
*
* No reference is taken. The caller must hold rcu_read_lock() and may only
* use the result as the starting point of an RCU list walk.
*/
static struct ns_common *lookup_ns_owner_after(u64 ns_id, struct ns_common *owner)
{
@ -484,8 +484,6 @@ static struct ns_common *lookup_ns_owner_after(u64 ns_id, struct ns_common *owne
}
}
if (ret)
ret = ns_get_unless_inactive(ret);
return ret;
}
@ -558,25 +556,27 @@ static ssize_t do_listns_userns(struct klistns *kls)
return -EINVAL;
kls->user_ns = to_user_ns(ns);
/*
* Use the rbtree to find the first namespace we care about and
* then use it's list entry to iterate from there.
*/
if (kls->last_ns_id) {
kls->first_ns = lookup_ns_owner_after(kls->last_ns_id, ns);
if (!kls->first_ns)
return -ENOENT;
first_ns = kls->first_ns;
}
ret = 0;
head = &to_ns_common(kls->user_ns)->ns_owner_root.ns_list_head;
kls->userns_capable = may_see_all_namespaces();
rcu_read_lock();
if (!first_ns)
/*
* Use the rbtree to find the first namespace we care about and
* then use it's list entry to iterate from there. That namespace
* is filtered like any other, so the walk doesn't end early just
* because it is inactive or already dying.
*/
if (kls->last_ns_id) {
first_ns = lookup_ns_owner_after(kls->last_ns_id, ns);
if (!first_ns) {
rcu_read_unlock();
return -ENOENT;
}
} else {
first_ns = list_entry_rcu(head->next, typeof(*first_ns), ns_owner_node.ns_list_entry);
}
ns = first_ns;
list_for_each_entry_from_rcu(ns, head, ns_owner_node.ns_list_entry) {
@ -613,6 +613,9 @@ 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.
*
* No reference is taken. The caller must hold rcu_read_lock() and may only
* use the result as the starting point of an RCU list walk.
*/
static struct ns_common *lookup_ns_id_after(u64 ns_id, int ns_type)
{
@ -649,8 +652,6 @@ static struct ns_common *lookup_ns_id_after(u64 ns_id, int ns_type)
}
}
if (ret)
ret = ns_get_unless_inactive(ret);
return ret;
}
@ -700,13 +701,6 @@ static ssize_t do_listns(struct klistns *kls)
return -EINVAL;
}
if (kls->last_ns_id) {
kls->first_ns = lookup_ns_id_after(kls->last_ns_id, ns_type);
if (!kls->first_ns)
return -ENOENT;
first_ns = kls->first_ns;
}
ret = 0;
if (ns_tree)
head = &ns_tree->ns_list_head;
@ -715,8 +709,15 @@ static ssize_t do_listns(struct klistns *kls)
rcu_read_lock();
if (!first_ns)
if (kls->last_ns_id) {
first_ns = lookup_ns_id_after(kls->last_ns_id, ns_type);
if (!first_ns) {
rcu_read_unlock();
return -ENOENT;
}
} else {
first_ns = first_ns_common(head, ns_tree);
}
for (ns = first_ns; !ns_common_is_head(ns, head, ns_tree) && nr_ns_ids;
ns = next_ns_common(ns, ns_tree)) {