Sched_ext development
 help / color / mirror / Atom feed
* [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
@ 2026-09-30 10:17 Hui Su
  2026-09-30 10:36 ` sashiko-bot
  0 siblings, 1 reply; 9+ messages in thread
From: Hui Su @ 2026-09-30 10:17 UTC (permalink / raw)
  To: sched-ext
  Cc: tj, void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, Hui Su, stable

A deferred user-DSQ node can be detached by
process_deferred_reenq_users() before the DSQ RCU callback reaches
exit_dsq(). Once detached, exit_dsq() can no longer find the node,
while the deferred path still uses the raw DSQ pointer after dropping
deferred_reenq_lock. The callback can therefore free the DSQ before
the deferred path checks its ID or calls reenq_user().

An RCU grace period only delays reclamation past pre-existing RCU
read-side critical sections. It doesn't protect a deferred reenqueue
which has detached its node and keeps using the raw DSQ pointer
afterwards.

Take a reference under deferred_reenq_lock before detaching the node.
The RCU callback drops the base reference after exit_dsq(), and the
deferred path drops its reference after its final DSQ access. This
keeps the object alive until all detached reenqueues finish while
preserving invalidated-DSQ behavior.

A KASAN regression test of the pre-fix kernel reported the
use-after-free while processing the deferred reenqueue:

    BUG: KASAN: slab-use-after-free in run_deferred+0x1312/0x1710
    Read of size 8 at addr ffff8880087009b0 by task swapper/3/0
    Call Trace:
     <IRQ>
     run_deferred+0x1312/0x1710
     ttwu_do_activate+0x29a/0x600
     try_to_wake_up+0x815/0x1700

The patched kernel completed the same regression test without a KASAN
report.

Fixes: 84b1a0ea0b7c ("sched_ext: Implement scx_bpf_dsq_reenq() for user DSQs")
Cc: stable@vger.kernel.org # v7.1+
Signed-off-by: Hui Su <sh_def@163.com>

diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h
index 23f9e178bc5a..1d36196b2238 100644
--- a/include/linux/sched/ext.h
+++ b/include/linux/sched/ext.h
@@ -13,6 +13,7 @@
 
 #include <linux/llist.h>
 #include <linux/rhashtable-types.h>
+#include <linux/refcount.h>
 
 enum scx_public_consts {
 	SCX_OPS_NAME_LEN	= 128,
@@ -92,6 +93,8 @@ struct scx_dispatch_q {
 	struct llist_node	free_node;
 	struct scx_sched	*sched;
 	struct scx_dsq_pcpu __percpu *pcpu_user;
+	/* one base ref held until deferred reclamation, plus detached workers */
+	refcount_t		deferred_reenq_refs;
 	struct rcu_head		rcu;
 };
 
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 405d0d1038f8..9fd18fa5725b 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -5057,6 +5057,7 @@ static void process_deferred_reenq_users(struct rq *rq)
 			dsq_pcpu = container_of(dru, struct scx_dsq_pcpu,
 						deferred_reenq_user);
 			dsq = dsq_pcpu->dsq;
+			refcount_inc(&dsq->deferred_reenq_refs);
 			reenq_flags = dru->flags;
 			WRITE_ONCE(dru->flags, 0);
 			list_del_init(&dru->node);
@@ -5068,10 +5069,14 @@ static void process_deferred_reenq_users(struct rq *rq)
 		/* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
 		dsq_id = READ_ONCE(dsq->id);
 		if (unlikely(dsq_id == SCX_DSQ_INVALID))
-			continue;
+			goto put_dsq;
 
 		BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);
 		reenq_user(rq, dsq, reenq_flags);
+
+put_dsq:
+		if (refcount_dec_and_test(&dsq->deferred_reenq_refs))
+			kfree(dsq);
 	}
 }
 
@@ -5565,6 +5570,7 @@ s32 scx_init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id, struct scx_sched *sch)
 	if (dsq_id & SCX_DSQ_FLAG_BUILTIN)
 		return 0;
 
+	refcount_set(&dsq->deferred_reenq_refs, 1);
 	dsq->pcpu_user = alloc_percpu(struct scx_dsq_pcpu);
 	if (!dsq->pcpu_user)
 		return -ENOMEM;
@@ -5609,7 +5615,8 @@ static void free_dsq_rcufn(struct rcu_head *rcu)
 	struct scx_dispatch_q *dsq = container_of(rcu, struct scx_dispatch_q, rcu);
 
 	exit_dsq(dsq);
-	kfree(dsq);
+	if (refcount_dec_and_test(&dsq->deferred_reenq_refs))
+		kfree(dsq);
 }
 
 static void free_dsq_irq_workfn(struct irq_work *irq_work)


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 10:17 Hui Su
@ 2026-09-30 10:36 ` sashiko-bot
  2026-09-30 15:04   ` Hui Su
  0 siblings, 1 reply; 9+ messages in thread
From: sashiko-bot @ 2026-09-30 10:36 UTC (permalink / raw)
  To: Hui Su; +Cc: sched-ext

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] sched_ext: invalid wait context calling kfree() under raw_spinlock_t
- [High] sched_ext: UAF due to missing memory barrier in deferred reenqueue
--

commit 32dd5c50544bf44b37d3cb4933b2658023766e35
Author: Hui Su <sh_def@163.com>

sched_ext: Hold DSQ refs for deferred reenqueues

This patch adds a reference count to DSQ to protect deferred reenqueues.
It takes a reference before detaching the node and drops it after the final 
DSQ access, preventing the DSQ from being freed prematurely by the RCU
callback.

> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index 6b037d27c99d3..88f229bd0c4d2 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
[ ... ]
> @@ -5075,6 +5075,7 @@ static void process_deferred_reenq_users(struct rq *rq)
>  			dsq_pcpu = container_of(dru, struct scx_dsq_pcpu,
>  						deferred_reenq_user);
>  			dsq = dsq_pcpu->dsq;
> +			refcount_inc(&dsq->deferred_reenq_refs);
>  			reenq_flags = dru->flags;
>  			WRITE_ONCE(dru->flags, 0);
>  			list_del_init(&dru->node);

[Severity: High]
Is a memory barrier needed here to ensure the refcount increment is
visible before the list deletion?

Because refcount_inc() uses relaxed atomics, it provides no memory
ordering guarantees. On weakly-ordered architectures, the CPU can commit
the list_del_init() to memory before the refcount_inc().

Concurrently, exit_dsq() performs a lockless read check:

kernel/sched/ext/ext.c:exit_dsq() {
    ...
    if (WARN_ON_ONCE(!list_empty(&dru->node))) {
        ...
    }
}

If exit_dsq() observes the reordered list deletion, it skips acquiring
the spinlock and proceeds to drop the base refcount. Since the refcount
increment hasn't reached global visibility yet, the base refcount drops
from 1 to 0 and dsq is prematurely freed, which can cause a
use-after-free when the deferred path continues.

Additionally, list_del_init() is not atomic and momentarily leaves
node->next pointing to the old element before pointing to itself. The
lockless check in exit_dsq() could evaluate to true during this window,
triggering a spurious warning.

[ ... ]
> @@ -5086,10 +5087,14 @@ static void process_deferred_reenq_users(struct rq *rq)
>  		/* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
>  		dsq_id = READ_ONCE(dsq->id);
>  		if (unlikely(dsq_id == SCX_DSQ_INVALID))
> -			continue;
> +			goto put_dsq;
>  
>  		BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);
>  		reenq_user(rq, dsq, reenq_flags);
> +
> +put_dsq:
> +		if (refcount_dec_and_test(&dsq->deferred_reenq_refs))
> +			kfree(dsq);

[Severity: High]
Does calling kfree() here violate lock nesting rules?

The process_deferred_reenq_users() function is executed with the
runqueue lock held, which is a raw_spinlock_t (never sleeps).

The slab allocator's kfree() slow path acquires internal spinlock_t or
local locks, which can sleep on PREEMPT_RT kernels. Acquiring a
sleepable lock while holding a true spinning lock triggers an "Invalid
wait context" splat and could potentially lead to deadlocks.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260930101722.2468434-1-sh_def@163.com?part=1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
@ 2026-09-30 14:34 Hui Su
  2026-09-30 15:20 ` Hui Su
  2026-09-30 15:26 ` Andrea Righi
  0 siblings, 2 replies; 9+ messages in thread
From: Hui Su @ 2026-09-30 14:34 UTC (permalink / raw)
  To: sched-ext
  Cc: tj, void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, Hui Su, stable

A deferred user-DSQ node can be detached by
process_deferred_reenq_users() before the DSQ RCU callback reaches
exit_dsq(). Once detached, exit_dsq() can no longer find the node,
while the deferred path still uses the raw DSQ pointer after dropping
deferred_reenq_lock. The callback can therefore free the DSQ before
the deferred path checks its ID or calls reenq_user().

An RCU grace period only delays reclamation past pre-existing RCU
read-side critical sections. It doesn't protect a deferred reenqueue
which has detached its node and keeps using the raw DSQ pointer
afterwards.

Take a reference under deferred_reenq_lock before detaching the node.
The RCU callback drops the base reference after exit_dsq(), and the
deferred path drops its reference after its final DSQ access. This
keeps the object alive until all detached reenqueues finish while
preserving invalidated-DSQ behavior.

A KASAN regression test of the pre-fix kernel reported the
use-after-free while processing the deferred reenqueue:

    BUG: KASAN: slab-use-after-free in run_deferred+0x1312/0x1710
    Read of size 8 at addr ffff8880087009b0 by task swapper/3/0
    Call Trace:
     <IRQ>
     run_deferred+0x1312/0x1710
     ttwu_do_activate+0x29a/0x600
     try_to_wake_up+0x815/0x1700

The patched kernel completed the same regression test without a KASAN
report.

Fixes: 84b1a0ea0b7c ("sched_ext: Implement scx_bpf_dsq_reenq() for user DSQs")
Cc: stable@vger.kernel.org # v7.1+
Signed-off-by: Hui Su <sh_def@163.com>

diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h
index 23f9e178bc5a..3344cf33d324 100644
--- a/include/linux/sched/ext.h
+++ b/include/linux/sched/ext.h
@@ -13,6 +13,7 @@
 
 #include <linux/llist.h>
 #include <linux/rhashtable-types.h>
+#include <linux/refcount.h>
 
 enum scx_public_consts {
 	SCX_OPS_NAME_LEN	= 128,
@@ -92,6 +93,8 @@ struct scx_dispatch_q {
 	struct llist_node	free_node;
 	struct scx_sched	*sched;
 	struct scx_dsq_pcpu __percpu *pcpu_user;
+	/* one base ref held until deferred reclamation, plus detached consumers */
+	refcount_t		deferred_reenq_refs;
 	struct rcu_head		rcu;
 };
 
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 405d0d1038f8..1df0ff7e3b72 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -5057,6 +5057,7 @@ static void process_deferred_reenq_users(struct rq *rq)
 			dsq_pcpu = container_of(dru, struct scx_dsq_pcpu,
 						deferred_reenq_user);
 			dsq = dsq_pcpu->dsq;
+			refcount_inc(&dsq->deferred_reenq_refs);
 			reenq_flags = dru->flags;
 			WRITE_ONCE(dru->flags, 0);
 			list_del_init(&dru->node);
@@ -5068,10 +5069,13 @@ static void process_deferred_reenq_users(struct rq *rq)
 		/* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
 		dsq_id = READ_ONCE(dsq->id);
 		if (unlikely(dsq_id == SCX_DSQ_INVALID))
-			continue;
+			goto put_dsq;
 
 		BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);
 		reenq_user(rq, dsq, reenq_flags);
+
+put_dsq:
+		refcount_dec(&dsq->deferred_reenq_refs);
 	}
 }
 
@@ -5565,6 +5569,7 @@ s32 scx_init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id, struct scx_sched *sch)
 	if (dsq_id & SCX_DSQ_FLAG_BUILTIN)
 		return 0;
 
+	refcount_set(&dsq->deferred_reenq_refs, 1);
 	dsq->pcpu_user = alloc_percpu(struct scx_dsq_pcpu);
 	if (!dsq->pcpu_user)
 		return -ENOMEM;
@@ -5591,25 +5596,33 @@ static void exit_dsq(struct scx_dispatch_q *dsq)
 		struct scx_deferred_reenq_user *dru = &pcpu->deferred_reenq_user;
 		struct rq *rq = cpu_rq(cpu);
 
-		/*
-		 * There must have been a RCU grace period since the last
-		 * insertion and @dsq should be off the deferred list by now.
-		 */
-		if (WARN_ON_ONCE(!list_empty(&dru->node))) {
-			guard(raw_spinlock_irqsave)(&rq->scx.deferred_reenq_lock);
+		guard(raw_spinlock_irqsave)(&rq->scx.deferred_reenq_lock);
+
+		if (WARN_ON_ONCE(!list_empty(&dru->node)))
 			list_del_init(&dru->node);
-		}
 	}
 
 	free_percpu(dsq->pcpu_user);
 }
 
+static void free_dsq_finish_rcufn(struct rcu_head *rcu)
+{
+	struct scx_dispatch_q *dsq = container_of(rcu, struct scx_dispatch_q, rcu);
+
+	if (!refcount_dec_if_one(&dsq->deferred_reenq_refs)) {
+		call_rcu(&dsq->rcu, free_dsq_finish_rcufn);
+		return;
+	}
+
+	kfree(dsq);
+}
+
 static void free_dsq_rcufn(struct rcu_head *rcu)
 {
 	struct scx_dispatch_q *dsq = container_of(rcu, struct scx_dispatch_q, rcu);
 
 	exit_dsq(dsq);
-	kfree(dsq);
+	free_dsq_finish_rcufn(rcu);
 }
 
 static void free_dsq_irq_workfn(struct irq_work *irq_work)


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 10:36 ` sashiko-bot
@ 2026-09-30 15:04   ` Hui Su
  0 siblings, 0 replies; 9+ messages in thread
From: Hui Su @ 2026-09-30 15:04 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: sched-ext

> Sashiko AI review found 2 potential issues to consider:
> - [High] sched_ext: invalid wait context calling kfree() under raw_spinlock_t
> - [High] sched_ext: UAF due to missing memory barrier in deferred reenqueue

Agreed.

Please disregard this version. It has been superseded by a later revision:
https://lore.kernel.org/lkml/20260930143443.2862150-1-sh_def@163.com/

The later posting should have been tagged v2; I accidentally omitted the
version tag. Sorry for the confusion.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 14:34 [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues Hui Su
@ 2026-09-30 15:20 ` Hui Su
  2026-09-30 17:15   ` Tejun Heo
  2026-09-30 15:26 ` Andrea Righi
  1 sibling, 1 reply; 9+ messages in thread
From: Hui Su @ 2026-09-30 15:20 UTC (permalink / raw)
  To: sched-ext
  Cc: tj, void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, Hui Su, stable

Please disregard this posting. This was intended to be v2, but I
accidentally omitted the version tag.

I am preparing v3 to address the review feedback. In particular, it
will serialize exit_dsq() with deferred detach/reference acquisition
using deferred_reenq_lock, and keep final DSQ reclamation out of the
deferred path while the rq raw spinlock is held.

The revised design keeps a single DSQ refcount and the existing
rcu_head, without polling the refcount from repeated RCU callbacks.

I will post v3 after revalidation. Sorry for the confusion.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 14:34 [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues Hui Su
  2026-09-30 15:20 ` Hui Su
@ 2026-09-30 15:26 ` Andrea Righi
  2026-10-08  3:38   ` Hui Su
  1 sibling, 1 reply; 9+ messages in thread
From: Andrea Righi @ 2026-09-30 15:26 UTC (permalink / raw)
  To: Hui Su
  Cc: sched-ext, tj, void, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, stable

Hi Hui,

On Wed, Sep 30, 2026 at 11:34:43PM +0900, Hui Su wrote:
> A deferred user-DSQ node can be detached by
> process_deferred_reenq_users() before the DSQ RCU callback reaches
> exit_dsq(). Once detached, exit_dsq() can no longer find the node,
> while the deferred path still uses the raw DSQ pointer after dropping
> deferred_reenq_lock. The callback can therefore free the DSQ before
> the deferred path checks its ID or calls reenq_user().
> 
> An RCU grace period only delays reclamation past pre-existing RCU
> read-side critical sections. It doesn't protect a deferred reenqueue
> which has detached its node and keeps using the raw DSQ pointer
> afterwards.
> 
> Take a reference under deferred_reenq_lock before detaching the node.
> The RCU callback drops the base reference after exit_dsq(), and the
> deferred path drops its reference after its final DSQ access. This
> keeps the object alive until all detached reenqueues finish while
> preserving invalidated-DSQ behavior.
> 
> A KASAN regression test of the pre-fix kernel reported the
> use-after-free while processing the deferred reenqueue:
> 
>     BUG: KASAN: slab-use-after-free in run_deferred+0x1312/0x1710
>     Read of size 8 at addr ffff8880087009b0 by task swapper/3/0
>     Call Trace:
>      <IRQ>
>      run_deferred+0x1312/0x1710
>      ttwu_do_activate+0x29a/0x600
>      try_to_wake_up+0x815/0x1700
> 
> The patched kernel completed the same regression test without a KASAN
> report.

The race looks real, can you also share the test or the steps used to reproduce
this? That would help validate the fix and assess the stable backport.

> 
> Fixes: 84b1a0ea0b7c ("sched_ext: Implement scx_bpf_dsq_reenq() for user DSQs")
> Cc: stable@vger.kernel.org # v7.1+
> Signed-off-by: Hui Su <sh_def@163.com>
> 
> diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h
> index 23f9e178bc5a..3344cf33d324 100644
> --- a/include/linux/sched/ext.h
> +++ b/include/linux/sched/ext.h
> @@ -13,6 +13,7 @@
>  
>  #include <linux/llist.h>
>  #include <linux/rhashtable-types.h>
> +#include <linux/refcount.h>
>  
>  enum scx_public_consts {
>  	SCX_OPS_NAME_LEN	= 128,
> @@ -92,6 +93,8 @@ struct scx_dispatch_q {
>  	struct llist_node	free_node;
>  	struct scx_sched	*sched;
>  	struct scx_dsq_pcpu __percpu *pcpu_user;
> +	/* one base ref held until deferred reclamation, plus detached consumers */
> +	refcount_t		deferred_reenq_refs;
>  	struct rcu_head		rcu;
>  };
>  
> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index 405d0d1038f8..1df0ff7e3b72 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
> @@ -5057,6 +5057,7 @@ static void process_deferred_reenq_users(struct rq *rq)
>  			dsq_pcpu = container_of(dru, struct scx_dsq_pcpu,
>  						deferred_reenq_user);
>  			dsq = dsq_pcpu->dsq;
> +			refcount_inc(&dsq->deferred_reenq_refs);
>  			reenq_flags = dru->flags;
>  			WRITE_ONCE(dru->flags, 0);
>  			list_del_init(&dru->node);

Sashiko's ordering concern looks like a false positive to me, at least on
sched_ext/for-7.4, both this sequence and exit_dsq() list check are protected by
deferred_reenq_lock.

> @@ -5068,10 +5069,13 @@ static void process_deferred_reenq_users(struct rq *rq)
>  		/* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
>  		dsq_id = READ_ONCE(dsq->id);
>  		if (unlikely(dsq_id == SCX_DSQ_INVALID))
> -			continue;
> +			goto put_dsq;
>  
>  		BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);
>  		reenq_user(rq, dsq, reenq_flags);
> +
> +put_dsq:
> +		refcount_dec(&dsq->deferred_reenq_refs);
>  	}
>  }
>  
> @@ -5565,6 +5569,7 @@ s32 scx_init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id, struct scx_sched *sch)
>  	if (dsq_id & SCX_DSQ_FLAG_BUILTIN)
>  		return 0;
>  
> +	refcount_set(&dsq->deferred_reenq_refs, 1);
>  	dsq->pcpu_user = alloc_percpu(struct scx_dsq_pcpu);
>  	if (!dsq->pcpu_user)
>  		return -ENOMEM;
> @@ -5591,25 +5596,33 @@ static void exit_dsq(struct scx_dispatch_q *dsq)
>  		struct scx_deferred_reenq_user *dru = &pcpu->deferred_reenq_user;
>  		struct rq *rq = cpu_rq(cpu);
>  
> -		/*
> -		 * There must have been a RCU grace period since the last
> -		 * insertion and @dsq should be off the deferred list by now.
> -		 */
> -		if (WARN_ON_ONCE(!list_empty(&dru->node))) {
> -			guard(raw_spinlock_irqsave)(&rq->scx.deferred_reenq_lock);
> +		guard(raw_spinlock_irqsave)(&rq->scx.deferred_reenq_lock);
> +
> +		if (WARN_ON_ONCE(!list_empty(&dru->node)))
>  			list_del_init(&dru->node);
> -		}
>  	}
>  
>  	free_percpu(dsq->pcpu_user);
>  }
>  
> +static void free_dsq_finish_rcufn(struct rcu_head *rcu)
> +{
> +	struct scx_dispatch_q *dsq = container_of(rcu, struct scx_dispatch_q, rcu);
> +
> +	if (!refcount_dec_if_one(&dsq->deferred_reenq_refs)) {
> +		call_rcu(&dsq->rcu, free_dsq_finish_rcufn);
> +		return;
> +	}

Can we avoid repeatedly queueing RCU callbacks while a detached reenqueue holds
a reference?

The callback leaves the base reference in place whenever a detached reenqueue is
active, then starts another grace period just to check the count again. A long
reenq_user() could make this repeat several times.

Could the first callback instead drop the base reference, and have whichever
side drops the final reference arrange the free? This would avoid polling
through repeated RCU callbacks.

Thanks,
-Andrea

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 15:20 ` Hui Su
@ 2026-09-30 17:15   ` Tejun Heo
  2026-10-08  3:41     ` Hui Su
  0 siblings, 1 reply; 9+ messages in thread
From: Tejun Heo @ 2026-09-30 17:15 UTC (permalink / raw)
  To: Hui Su
  Cc: sched-ext, void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, stable

On Thu, Oct 01, 2026 at 12:20:49AM +0900, Hui Su wrote:
> Please disregard this posting. This was intended to be v2, but I
> accidentally omitted the version tag.
> 
> I am preparing v3 to address the review feedback. In particular, it
> will serialize exit_dsq() with deferred detach/reference acquisition
> using deferred_reenq_lock, and keep final DSQ reclamation out of the
> deferred path while the rq raw spinlock is held.
> 
> The revised design keeps a single DSQ refcount and the existing
> rcu_head, without polling the refcount from repeated RCU callbacks.
> 
> I will post v3 after revalidation. Sorry for the confusion.

Can you try to do it without refcnts? DSQ destructions are rare events and
we can reliably detect whether the DSQ is full or not. I'd much rather make
the destruction path expensive than adding refcnt to hot paths.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 15:26 ` Andrea Righi
@ 2026-10-08  3:38   ` Hui Su
  0 siblings, 0 replies; 9+ messages in thread
From: Hui Su @ 2026-10-08  3:38 UTC (permalink / raw)
  To: Andrea Righi
  Cc: sched-ext, tj, void, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, stable

Hi Andrea,

On Wed, Sep 30, 2026 at 17:26:37 +0200, Andrea Righi wrote:
> The race looks real, can you also share the test or the steps used to reproduce
> this? That would help validate the fix and assess the stable backport.

I originally observed a crash in this area on an unmodified kernel, but I
don't have a reliable natural reproducer for that schedule. To validate
the lifetime issue deterministically, I used a validation-only fixture to
force the window.

The pre-fix fixture pauses after process_deferred_reenq_users() detaches
the request, lets the DSQ be destroyed, and directly runs the reclaim
callback before the deferred path's final DSQ access. KASAN then reports
the UAF in run_deferred().

I ran it on 4-vCPU x86_64 KVM with CONFIG_KASAN_GENERIC=y and
CONFIG_SCHED_CLASS_EXT=y.

For v3, I used a separate active-cursor test. Reclamation stayed deferred
while the reenq_user() cursor was linked and completed after the cursor
was removed. KASAN and a separate PROVE_LOCKING/LOCKDEP run were clean.

> Sashiko's ordering concern looks like a false positive to me, at least on
> sched_ext/for-7.4, both this sequence and exit_dsq() list check are protected by
> deferred_reenq_lock.

> Can we avoid repeatedly queueing RCU callbacks while a detached reenqueue holds
> a reference?
>
> The callback leaves the base reference in place whenever a detached reenqueue is
> active, then starts another grace period just to check the count again. A long
> reenq_user() could make this repeat several times.
>
> Could the first callback instead drop the base reference, and have whichever
> side drops the final reference arrange the free? This would avoid polling
> through repeated RCU callbacks.

v3 follows Tejun's suggestion to avoid refcounts and leave the deferred
reenqueue hot path unchanged. The destruction-side sweep serializes with
deferred-list updates and checks whether a cursor remains. A callback may
still be retried while an active cursor remains, but that retry is
confined to DSQ destruction.

v3:
https://lore.kernel.org/lkml/20261008024753.4096008-1-sh_def@163.com/

Thanks,
Hui


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues
  2026-09-30 17:15   ` Tejun Heo
@ 2026-10-08  3:41     ` Hui Su
  0 siblings, 0 replies; 9+ messages in thread
From: Hui Su @ 2026-10-08  3:41 UTC (permalink / raw)
  To: Tejun Heo
  Cc: sched-ext, void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, linux-kernel, stable

Hi Tejun,

On Wed, Sep 30, 2026 at 07:15:20AM -1000, Tejun Heo wrote:
> Can you try to do it without refcnts? DSQ destructions are rare events and
> we can reliably detect whether the DSQ is full or not. I'd much rather make
> the destruction path expensive than adding refcnt to hot paths.

Thanks. v3 follows this direction and adds no refcounting to the deferred
reenqueue path. After the initial grace period, the destruction callback
sweeps the per-CPU deferred lists under the rq lock followed by
deferred_reenq_lock, then checks the DSQ list under its lock. If the list is
still non-empty, it retries the callback, which also covers the case where
an active reenq_user() cursor remains; otherwise it proceeds with final
reclamation.

This keeps the additional synchronization on the rare DSQ destruction path.

v3:
https://lore.kernel.org/lkml/20261008024753.4096008-1-sh_def@163.com/

Thanks,
Hui


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-10-08  3:55 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-30 14:34 [PATCH] sched_ext: Hold DSQ refs for deferred reenqueues Hui Su
2026-09-30 15:20 ` Hui Su
2026-09-30 17:15   ` Tejun Heo
2026-10-08  3:41     ` Hui Su
2026-09-30 15:26 ` Andrea Righi
2026-10-08  3:38   ` Hui Su
  -- strict thread matches above, loose matches on Subject: below --
2026-09-30 10:17 Hui Su
2026-09-30 10:36 ` sashiko-bot
2026-09-30 15:04   ` Hui Su

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox