Skip to content

Commit f8d5a89

Browse files
committed
ns: handle setns(pidfd, ...) cleanly
The setns() system call supports: (1) namespace file descriptors (nsfd) (2) process file descriptors (pidfd) When using nsfds the namespaces will remain active because they are pinned by the vfs. However, when pidfds are used things are more complicated. When the target task exits and passes through exit_nsproxy_namespaces() or is reaped and thus also passes through exit_cred_namespaces() after the setns()'ing task has called prepare_nsset() but before the active reference count of the set of namespaces it wants to setns() to might have been dropped already: P1 P2 pid_p1 = clone(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) pidfd = pidfd_open(pid_p1) setns(pidfd, CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) prepare_nsset() exit(0) // ns->__ns_active_ref == 1 // parent_ns->__ns_active_ref == 1 -> exit_nsproxy_namespaces() -> exit_cred_namespaces() // ns_active_ref_put() will also put // the reference on the owner of the // namespace. If the only reason the // owning namespace was alive was // because it was a parent of @ns // it's active reference count now goes // to zero... -------------------------------- // | // ns->__ns_active_ref == 0 | // parent_ns->__ns_active_ref == 0 | | commit_nsset() -----------------> // If setns() // now manages to install the namespaces // it will call ns_active_ref_get() // on them thus bumping the active reference // count from zero again but without also // taking the required reference on the owner. // Thus we get: // // ns->__ns_active_ref == 1 // parent_ns->__ns_active_ref == 0 When later someone does ns_active_ref_put() on @ns it will underflow parent_ns->__ns_active_ref leading to a splat from our asserts thinking there are still active references when in fact the counter just underflowed. So resurrect the ownership chain if necessary as well. If the caller succeeded to grab passive references to the set of namespaces the setns() should simply succeed even if the target task exists or gets reaped in the meantime and thus has dropped all active references to its namespaces. The race is rare and can only be triggered when using pidfs to setns() to namespaces. Also note that active reference on initial namespaces are nops. Since we now always handle parent references directly we can drop ns_ref_active_get_owner() when adding a namespace to a namespace tree. This is now all handled uniformly in the places where the new namespaces actually become active. Link: https://patch.msgid.link/20251109-namespace-6-19-fixes-v1-5-ae8a4ad5a3b3@kernel.org Fixes: 3c9820d ("ns: add active reference count") Reported-by: syzbot+1957b26299cf3ff7890c@syzkaller.appspotmail.com Signed-off-by: Christian Brauner <brauner@kernel.org>
1 parent a51dce7 commit f8d5a89

4 files changed

Lines changed: 17 additions & 61 deletions

File tree

fs/nsfs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ static int nsfs_init_inode(struct inode *inode, void *data)
430430
* ioctl on such a socket will resurrect the relevant namespace
431431
* subtree.
432432
*/
433-
__ns_ref_active_resurrect(ns);
433+
__ns_ref_active_get(ns);
434434
return 0;
435435
}
436436

include/linux/ns_common.h

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -287,47 +287,8 @@ static __always_inline __must_check int __ns_ref_read(const struct ns_common *ns
287287
#define ns_ref_active_read(__ns) \
288288
((__ns) ? __ns_ref_active_read(to_ns_common(__ns)) : 0)
289289

290-
void __ns_ref_active_get_owner(struct ns_common *ns);
290+
void __ns_ref_active_put(struct ns_common *ns);
291291

292-
static __always_inline void __ns_ref_active_get(struct ns_common *ns)
293-
{
294-
/* Initial namespaces are always active. */
295-
if (!is_ns_init_id(ns))
296-
WARN_ON_ONCE(atomic_add_negative(1, &ns->__ns_ref_active));
297-
}
298-
#define ns_ref_active_get(__ns) \
299-
do { if (__ns) __ns_ref_active_get(to_ns_common(__ns)); } while (0)
300-
301-
static __always_inline bool __ns_ref_active_get_not_zero(struct ns_common *ns)
302-
{
303-
/* Initial namespaces are always active. */
304-
if (is_ns_init_id(ns))
305-
return true;
306-
307-
if (atomic_inc_not_zero(&ns->__ns_ref_active)) {
308-
VFS_WARN_ON_ONCE(!__ns_ref_read(ns));
309-
return true;
310-
}
311-
return false;
312-
}
313-
314-
#define ns_ref_active_get_owner(__ns) \
315-
do { if (__ns) __ns_ref_active_get_owner(to_ns_common(__ns)); } while (0)
316-
317-
void __ns_ref_active_put_owner(struct ns_common *ns);
318-
319-
static __always_inline void __ns_ref_active_put(struct ns_common *ns)
320-
{
321-
/* Initial namespaces are always active. */
322-
if (is_ns_init_id(ns))
323-
return;
324-
325-
if (atomic_dec_and_test(&ns->__ns_ref_active)) {
326-
VFS_WARN_ON_ONCE(is_initial_namespace(ns));
327-
VFS_WARN_ON_ONCE(!__ns_ref_read(ns));
328-
__ns_ref_active_put_owner(ns);
329-
}
330-
}
331292
#define ns_ref_active_put(__ns) \
332293
do { if (__ns) __ns_ref_active_put(to_ns_common(__ns)); } while (0)
333294

@@ -343,9 +304,9 @@ static __always_inline struct ns_common *__must_check ns_get_unless_inactive(str
343304
return ns;
344305
}
345306

346-
void __ns_ref_active_resurrect(struct ns_common *ns);
307+
void __ns_ref_active_get(struct ns_common *ns);
347308

348-
#define ns_ref_active_resurrect(__ns) \
349-
do { if (__ns) __ns_ref_active_resurrect(to_ns_common(__ns)); } while (0)
309+
#define ns_ref_active_get(__ns) \
310+
do { if (__ns) __ns_ref_active_get(to_ns_common(__ns)); } while (0)
350311

351312
#endif

kernel/nscommon.c

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,6 @@ struct ns_common *__must_check ns_owner(struct ns_common *ns)
114114
return to_ns_common(owner);
115115
}
116116

117-
void __ns_ref_active_get_owner(struct ns_common *ns)
118-
{
119-
ns = ns_owner(ns);
120-
if (ns)
121-
WARN_ON_ONCE(atomic_add_negative(1, &ns->__ns_ref_active));
122-
}
123-
124117
/*
125118
* The active reference count works by having each namespace that gets
126119
* created take a single active reference on its owning user namespace.
@@ -171,8 +164,18 @@ void __ns_ref_active_get_owner(struct ns_common *ns)
171164
* The iteration stops once we reach a namespace that still has active
172165
* references.
173166
*/
174-
void __ns_ref_active_put_owner(struct ns_common *ns)
167+
void __ns_ref_active_put(struct ns_common *ns)
175168
{
169+
/* Initial namespaces are always active. */
170+
if (is_ns_init_id(ns))
171+
return;
172+
173+
if (!atomic_dec_and_test(&ns->__ns_ref_active))
174+
return;
175+
176+
VFS_WARN_ON_ONCE(is_ns_init_id(ns));
177+
VFS_WARN_ON_ONCE(!__ns_ref_read(ns));
178+
176179
for (;;) {
177180
ns = ns_owner(ns);
178181
if (!ns)
@@ -275,7 +278,7 @@ void __ns_ref_active_put_owner(struct ns_common *ns)
275278
* it also needs to take another reference on its owning user namespace
276279
* and so on.
277280
*/
278-
void __ns_ref_active_resurrect(struct ns_common *ns)
281+
void __ns_ref_active_get(struct ns_common *ns)
279282
{
280283
/* Initial namespaces are always active. */
281284
if (is_ns_init_id(ns))

kernel/nstree.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,6 @@ void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree *ns_tree)
173173
write_sequnlock(&ns_tree_lock);
174174

175175
VFS_WARN_ON_ONCE(node);
176-
177-
/*
178-
* Take an active reference on the owner namespace. This ensures
179-
* that the owner remains visible while any of its child namespaces
180-
* are active. For init namespaces this is a no-op as ns_owner()
181-
* returns NULL for namespaces owned by init_user_ns.
182-
*/
183-
__ns_ref_active_get_owner(ns);
184176
}
185177

186178
void __ns_tree_remove(struct ns_common *ns, struct ns_tree *ns_tree)

0 commit comments

Comments
 (0)