VYPR
Unrated severityNVD Advisory· Published May 28, 2026

CVE-2026-46154

CVE-2026-46154

Description

In the Linux kernel, the following vulnerability has been resolved:

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

scx_group_set_{weight,idle,bandwidth}() cache scx_root before acquiring scx_cgroup_ops_rwsem, so the pointer can be stale by the time the op runs. If the loaded scheduler is disabled and freed (via RCU work) and another is enabled between the naked load and the rwsem acquire, the reader sees scx_cgroup_enabled=true (the new scheduler's) but dereferences the freed one - UAF on SCX_HAS_OP(sch, ...) / SCX_CALL_OP(sch, ...).

scx_cgroup_enabled is toggled only under scx_cgroup_ops_rwsem write (scx_cgroup_{init,exit}), so reading scx_root inside the rwsem read section correlates @sch with the enabled snapshot.

Affected products

2

Patches

6
ce9aaa3af445

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitTejun HeoMay 13, 2026Fixed in 6.18.32via kernel-cna
1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index 423098966a2919..177bbf31116d0c 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -3251,9 +3251,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -3267,9 +3268,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, SCX_KF_UNLOCKED, cgroup_set_idle, NULL,
    @@ -3284,9 +3286,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    
0f54f6355575

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitTejun HeoApr 25, 2026Fixed in 7.0.7via kernel-cna
1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index 064eaa76be4b9f..89814646a98687 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -3430,9 +3430,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -3446,9 +3447,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, SCX_KF_UNLOCKED, cgroup_set_idle, NULL,
    @@ -3463,9 +3465,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    
80afd4c84bc8

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitTejun HeoApr 25, 2026Fixed in 7.1-rc2via kernel-cna
1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index dd0539ab9ba82c..f6d22636a4de3e 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -4343,9 +4343,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -4358,9 +4359,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle);
    @@ -4374,9 +4376,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    
ce9aaa3af445

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index 423098966a2919..177bbf31116d0c 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -3251,9 +3251,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -3267,9 +3268,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, SCX_KF_UNLOCKED, cgroup_set_idle, NULL,
    @@ -3284,9 +3286,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    
0f54f6355575

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index 064eaa76be4b9f..89814646a98687 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -3430,9 +3430,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -3446,9 +3447,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, SCX_KF_UNLOCKED, cgroup_set_idle, NULL,
    @@ -3463,9 +3465,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    
80afd4c84bc8

sched_ext: Read scx_root under scx_cgroup_ops_rwsem in cgroup setters

1 file changed · +6 4
  • kernel/sched/ext.c+6 4 modified
    diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
    index dd0539ab9ba82c..f6d22636a4de3e 100644
    --- a/kernel/sched/ext.c
    +++ b/kernel/sched/ext.c
    @@ -4343,9 +4343,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
     
     void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
     	    tg->scx.weight != weight)
    @@ -4358,9 +4359,10 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
     
     void scx_group_set_idle(struct task_group *tg, bool idle)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
     		SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle);
    @@ -4374,9 +4376,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
     void scx_group_set_bandwidth(struct task_group *tg,
     			     u64 period_us, u64 quota_us, u64 burst_us)
     {
    -	struct scx_sched *sch = scx_root;
    +	struct scx_sched *sch;
     
     	percpu_down_read(&scx_cgroup_ops_rwsem);
    +	sch = scx_root;
     
     	if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
     	    (tg->scx.bw_period_us != period_us ||
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"TOCTOU race: scx_group_set_{weight,idle,bandwidth}() read the global scx_root pointer before acquiring scx_cgroup_ops_rwsem, so the pointer can become stale (pointing to a freed scheduler) by the time the lock is held and the ops are invoked."

Attack vector

An attacker with the ability to trigger cgroup weight/idle/bandwidth write operations (e.g. writing to cgroup control files) while concurrently loading and unloading sched_ext schedulers can exploit a time-of-check-time-of-use (TOCTOU) race. Between the naked load of scx_root and the acquisition of scx_cgroup_ops_rwsem, a scheduler can be disabled and freed via RCU work, and a new scheduler enabled. The function then sees scx_cgroup_enabled=true (from the new scheduler) but dereferences the freed scx_root pointer, causing a use-after-free on SCX_HAS_OP(sch, ...) and SCX_CALL_OP(sch, ...) [patch_id=2898233].

Affected code

The vulnerable code is in kernel/sched/ext.c in the functions scx_group_set_weight, scx_group_set_idle, and scx_group_set_bandwidth. Each function previously cached `struct scx_sched *sch = scx_root` before acquiring scx_cgroup_ops_rwsem [patch_id=2898233].

What the fix does

The patch moves the assignment `sch = scx_root` to after `percpu_down_read(&scx_cgroup_ops_rwsem)` in all three functions: scx_group_set_weight, scx_group_set_idle, and scx_group_set_bandwidth [patch_id=2898233]. Because scx_cgroup_enabled is toggled only under the write side of the same rwsem (in scx_cgroup_init and scx_cgroup_exit), reading scx_root inside the read-side critical section guarantees that the obtained pointer correlates with the enabled/disabled snapshot seen by the subsequent checks.

Preconditions

  • configThe system must have CONFIG_SCHED_CLASS_EXT (sched_ext) enabled.
  • inputAn attacker must be able to write to cgroup control files (e.g., cpu.weight, cpu.idle, cpu.max) to trigger the setter functions.
  • inputAn attacker must be able to concurrently load and unload sched_ext schedulers (e.g., via BPF program loading/unloading).

Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.