From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9399F17A305; Fri, 9 Oct 2026 14:17:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791555481; cv=none; b=jE+oHnsNNgyTwBR33JzL61sJJ5UvzwSolEPPevIbxA5cYYUuPEEt1MP1NU5+x7IV/pJupOjfsBF0fWkpCJMbRi4cQOmwjuoUe6/fPvyKA9oHLvFGDgyNAcOHkP+EsHPzx4TnZ80CKaUlFpn2wrxkUQfad1c4/rWmsErJGBhBILg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791555481; c=relaxed/simple; bh=aD8rBoAUnzQz0rkJ1JpUE9JWYgeo/xii9vVK1/bbw3I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bISXZ4uSJAshKhD8Nk/r+7QOOwL0HnJWDwXMa53iyQvX0wmNKEjp4Y40uG0p8y3AGreUqfGi4OHaKPcpgRNtDcoaXICjBsZWhBQZ0oply7/So1PUcOpfXOBWo0qQ7UMyCDOog0oSzHbAKhM3ALn1Jp/a85CWp8vspEjOpOqSHsI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=tB7EW2pB; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="tB7EW2pB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 8027B1F000FF; Fri, 9 Oct 2026 14:17:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1791555477; bh=BaB57VypdLktvPDAmaf5HezbUmF29Uj6hZeBS1RlgrI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=tB7EW2pB2UQv8gWHH8JCpvJWPUR3eS17OCHtbuPkDG4Ro/wu/m6hlAHD4h9mFLKAB SYw0uyjnT8rBSLRxrwa7Y9Vano+6FvuaTr11+4QwM4j0u85OTatQiZQhtrag3en160 hmxfDbiCr0h7QbhWm/YjZ9YTIuBrDdXUvunf3PHI= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Andrea Righi , fangqiurong , Tejun Heo , Sasha Levin Subject: [PATCH 7.2 012/428] sched_ext: Dont run ops.dequeue() with a DSQ lock held Date: Fri, 9 Oct 2026 16:05:49 +0200 Message-ID: <20261009140946.312576568@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261009140946.008206689@linuxfoundation.org> References: <20261009140946.008206689@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 7.2-stable review patch. If anyone has any objections, please let me know. ------------------ From: fangqiurong [ Upstream commit cb86607ada73185f321baa7f9d94a08a3a40bbf5 ] ops.dequeue() is invoked with the source user DSQ's lock still held on the consume and move paths (scx_consume_dispatch_q(), move_task_between_dsqs()). A BPF scheduler which locks the source user DSQ from ops.dequeue() - e.g. by iterating it with bpf_iter_scx_dsq - self-deadlocks. ops.dequeue() can only call the "any" kfuncs and none of them can lock a builtin DSQ, so the global and bypass paths can't deadlock; however, all DSQ locks share one lockdep class, so iterating any user DSQ from ops.dequeue() on those paths trips the recursion check. Move the invocation after the DSQ unlock on all three paths. SCX_TASK_IN_CUSTODY is cleared under the lock serializing the transfer so that the callback is invoked exactly once. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Acked-by: Andrea Righi Signed-off-by: fangqiurong Signed-off-by: Tejun Heo [ adapted upstream sched_ext helpers to their older static equivalents. ] Signed-off-by: Sasha Levin --- kernel/sched/ext/ext.c | 48 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 59ed2c2e39d77..476a584d0d4da 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -1450,20 +1450,14 @@ static inline bool task_scx_migrating(struct task_struct *p) return p->scx.sticky_cpu >= 0; } -/* - * Call ops.dequeue() if the task is in BPF custody and not migrating. - * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked. - */ -static void call_task_dequeue(struct scx_sched *sch, struct rq *rq, - struct task_struct *p, u64 deq_flags) +/* Must be called under the lock serializing @p's custody transfers. */ +static bool task_leave_custody(struct task_struct *p) { if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p)) - return; - - if (SCX_HAS_OP(sch, dequeue)) - SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); + return false; p->scx.flags &= ~SCX_TASK_IN_CUSTODY; + return true; } static void local_dsq_post_enq(struct scx_sched *sch, struct scx_dispatch_q *dsq, @@ -1471,7 +1465,8 @@ static void local_dsq_post_enq(struct scx_sched *sch, struct scx_dispatch_q *dsq { struct rq *rq = container_of(dsq, struct rq, scx.local_dsq); - call_task_dequeue(sch, rq, p, 0); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); /* * Note that @rq's lock may be dropped between this enqueue and @p @@ -1634,16 +1629,28 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq, if (is_local) { local_dsq_post_enq(sch, dsq, p, enq_flags); } else { + bool call_dequeue = false; + /* - * Task on global/bypass DSQ: leave custody, task on - * non-terminal DSQ: enter custody. + * Global and bypass DSQs are terminal - the task leaves the + * scheduler's custody, so ops.dequeue() fires. It can run + * without @p's rq lock (finish_dispatch() passes the dispatch + * rq); that's safe because dequeue_task_scx() waits on + * SCX_OPSS_DISPATCHING (see the ops_state note above) and so + * can't race it. A non-terminal DSQ keeps the task in custody. + * The custody transfer happens under @dsq->lock so that later + * consumers see the flag clear; the callback runs after + * @dsq->lock is dropped because it may lock a DSQ itself. */ if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS) - call_task_dequeue(sch, rq, p, 0); + call_dequeue = task_leave_custody(p); else p->scx.flags |= SCX_TASK_IN_CUSTODY; raw_spin_unlock(&dsq->lock); + + if (call_dequeue && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); } /* @@ -2111,7 +2118,7 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) /* * A queued task must always be in BPF scheduler's custody. If * SCX_TASK_IN_CUSTODY is clear, finish_dispatch() on another - * CPU has already passed call_task_dequeue() (which clears the + * CPU has already passed task_leave_custody() (which clears the * flag), but has not yet written SCX_OPSS_NONE. That final * store does not require this rq's lock, so retrying with * cpu_relax() is bounded: we will observe NONE (or DISPATCHING, @@ -2159,7 +2166,8 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) * NONE but the task may still have %SCX_TASK_IN_CUSTODY set until * it is enqueued on the destination. */ - call_task_dequeue(sch, rq, p, deq_flags); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); } static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_flags) @@ -2263,13 +2271,10 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl static void move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, u64 enq_flags, - struct scx_dispatch_q *src_dsq, struct rq *dst_rq) { struct scx_dispatch_q *dst_dsq = &dst_rq->scx.local_dsq; - /* @dsq is locked and @p is on @dst_rq */ - lockdep_assert_held(&src_dsq->lock); lockdep_assert_rq_held(dst_rq); WARN_ON_ONCE(p->scx.holding_cpu >= 0); @@ -2513,9 +2518,8 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch, /* @p is going from a non-local DSQ to a local DSQ */ if (src_rq == dst_rq) { task_unlink_from_dsq(p, src_dsq); - move_local_task_to_local_dsq(sch, p, enq_flags, - src_dsq, dst_rq); raw_spin_unlock(&src_dsq->lock); + move_local_task_to_local_dsq(sch, p, enq_flags, dst_rq); } else { raw_spin_unlock(&src_dsq->lock); move_remote_task_to_local_dsq(p, enq_flags, @@ -2566,8 +2570,8 @@ static bool consume_dispatch_q(struct scx_sched *sch, struct rq *rq, if (rq == task_rq) { task_unlink_from_dsq(p, dsq); - move_local_task_to_local_dsq(sch, p, enq_flags, dsq, rq); raw_spin_unlock(&dsq->lock); + move_local_task_to_local_dsq(sch, p, enq_flags, rq); return true; } -- 2.53.0