mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Michal Koutný" <mkoutny@suse.com>
To: Noah Feldt <noah@feldt.systems>
Cc: Tejun Heo <tj@kernel.org>,
	cgroups@vger.kernel.org,  linux-kernel@vger.kernel.org,
	N.Feldt@mittwald.de, carnil@debian.org, dschatzberg@meta.com,
	 hannes@cmpxchg.org, peterz@infradead.org,
	stable@vger.kernel.org
Subject: Re: [PATCH v2] cgroup: Avoid iteration of dying tasks with zero refcount
Date: Tue, 8 Sep 2026 17:55:43 +0200	[thread overview]
Message-ID: <aqAtv3WVn51LN3E8@localhost.localdomain> (raw)
In-Reply-To: <20260907192723.72167-1-noah@feldt.systems>

[-- Attachment #1: Type: text/plain, Size: 4086 bytes --]

On Mon, Sep 07, 2026 at 07:27:32PM +0000, Noah Feldt <noah@feldt.systems> wrote:
> Hi Michal,
> 
> thanks for the v2. I tested it on the same production node and with the
> same reproducer (the 2nd PoC) that triggered the original crash.
> 
> Result: it no longer panics, the use-after-free of the reaped leader is
> gone. But the node now hard-locks up instead. dmesg is attached as
> prod.log (module lists trimmed); the relevant part is:
> 
>   watchdog: CPU24: Watchdog detected hard LOCKUP on cpu 24
>   RIP: 0010:native_queued_spin_lock_slowpath+0x2aa/0x2f0
>   Call Trace:
>    _raw_spin_lock_irqsave+0x3d/0x50
>    cgroup_task_dead+0x29/0x140
>    finish_task_switch.isra.0+0x238/0x2c0
>    __schedule+0x4ec/0xfe0
> 
> i.e. one CPU spins forever holding css_set_lock with IRQs disabled, and
> the other CPUs pile up on that spinlock until the NMI watchdog fires on
> several of them.

I bothed the loop...

> 
> > First, I replaced the if() with a while() loop because when one such
> > dying leader could remain on dying_tasks, there can be more of them
> > (matter of effort) and single css_task_iter_advance() won't be
>  enough
> > (we need to skip all such tasks before get_task_struct()).
> 
> The while() is what locks up:
> 
> > +	while (it->task_pos && it->cur_tasks_head == &it->cur_cset->dying_tasks) {
> > +		task = list_entry(it->task_pos, struct task_struct, cg_list);
> > +		if (!atomic_read(&task->signal->live))
> > +			css_task_iter_advance(it);
> > +	}

...and after I hit Send, I realized it might be unnecessary thanks to
implicit loop via `goto repeat;` in css_task_iter_advance(). But then
there's CSS_TASK_ITER_WITH_DEAD which needs additional care
(fortunately, this flag is not used by those userspace users, so the
if-variant would be sufficient to fix the race in non-sched_ext
scenarios).


> 
> A LLM helped me debug this
> 
> When the leader is still live, atomic_read(&task->signal->live) != 0, so
> the if() body is skipped, css_task_iter_advance() is never called,
> it->task_pos never moves and the loop condition stays true forever. A
> live dying-list leader thus spins the loop under css_set_lock -> the hard
> lockup above.
> 
> The loop has to stop on the first live leader (that is exactly the task we
> want to hand out) and only advance past the dead ones. Turning the skip
> into a break makes it terminate. The variant I tested:
> 
> --- a/kernel/cgroup/cgroup.c
> +++ b/kernel/cgroup/cgroup.c
> @@ -5209,6 +5209,7 @@
>   */
>  struct task_struct *css_ta
> sk_iter_next(struct css_task_iter *it)
>  {
> +	struct task_struct *task;
>  	unsigned long irqflags;
> 
>  	if (it->cur_task) {
> @@ -5222,6 +5223,22 @@
>  	if (it->flags & CSS_TASK_ITER_SKIPPED)
>  		css_task_iter_advance(it);
> 
> +	/*
> +	 * @it->task_pos was picked on an earlier call. A dying leader stays on
> +	 * dying_tasks until cgroup_task_free(), past its last usage ref drop,
> +	 * so it may have been reaped since and get_task_struct() on it would
> +	 * resurrect a task about to be freed. That last ref is dropped by an
> +	 * RCU callback queued from release_task(), after signal->live hit zero,
> +	 * so a leader still showing live threads in this irq-disabled section
> +	 * can't lose its ref before the section ends.
> +	 */
> +	while (it->task_pos && it->cur_tasks_head == &it->cur_cset->dying_tasks) {
> +		task = list_entry(it->task_pos, struct task_struct, cg_list);
> +		if (atomic_read(&task->signal->live))
> +			break;
> +		css_task_iter_advance(it);
> +	}
> +
>  	if (it->task_pos) {
>  		it->c
> ur_task = list_entry(it->task_pos, struct task_struct,
>  					  cg_list);
> 
> Same reproducer after this change: no panic and no lockup, the node stays
> up under the load that reproduced it before.
> 
> Tested-by: Noah Elias Feldt <N.Feldt@mittwald.de> # while-variant with the break

Thanks, factoring the live count into the loop is what I should've done
with the loop.
Though, the fix loses a bit of elegance. Hm, thinking...

Michal

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 265 bytes --]

  reply	other threads:[~2026-09-08 15:55 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-07 19:27 Noah Feldt
2026-09-08 15:55 ` Michal Koutný [this message]
2026-09-11 16:11   ` Michal Koutný
2026-09-11 16:57     ` Tejun Heo
  -- strict thread matches above, loose matches on Subject: below --
2026-09-07 17:03 Michal Koutný
2026-09-07 17:07 ` Michal Koutný

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=aqAtv3WVn51LN3E8@localhost.localdomain \
    --to=mkoutny@suse.com \
    --cc=N.Feldt@mittwald.de \
    --cc=carnil@debian.org \
    --cc=cgroups@vger.kernel.org \
    --cc=dschatzberg@meta.com \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=noah@feldt.systems \
    --cc=peterz@infradead.org \
    --cc=stable@vger.kernel.org \
    --cc=tj@kernel.org \
    /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®