mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
02a336a831
commit
da29ac46d3
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user