[PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key

Chen Jinghuang posted 1 patch 1 month, 4 weeks ago
There is a newer version of this series
kernel/sched/core.c | 4 ++++
1 file changed, 4 insertions(+)
[PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key
Posted by Chen Jinghuang 1 month, 4 weeks ago
While fuzzing with syzkaller, a concurrent 1/0 write race to
/proc/sys/kernel/numa_balancing was found that trips a jump_label
WARN_ON_ONCE().

Concurrent writes of 1/0 to /proc/sys/kernel/numa_balancing enable/disable
the same static key. Enable sets key->enabled to -1 while holding the
lock, restoring it to 1 only after jump_label_update(); disable checks
enabled before taking the lock. Under concurrency, disable reads -1 and
trips WARN_ON_ONCE().

Timeline:
    write 1 → enable                     write 0 → disable
    │                                    │
    ├─ static_key_enable_cpuslocked()    ├─ static_key_disable_cpuslocked()
    │  jump_label_lock()                 │  atomic_read(enabled)   ← before lock
    │    atomic_set(enabled, -1) ◄───────┼── reads -1
    │    jump_label_update()             │  WARN_ON_ONCE(enabled!=0)
    │    atomic_set_release(enabled,1)   │  return  ← disable skipped
    │  jump_label_unlock()               │

Serialize the enable/disable switch at the convergence point in
set_numabalancing_state() with a mutex, so the transient -1 in
key->enabled never leaks to a concurrent disable and this WARN_ON_ONCE
no longer trips.

This follows existing kernel practice, e.g. timer_key_mutex guarding
timers_update_migration() (kernel/time/timer.c) and perf_sched_mutex
guarding static_branch_enable() (kernel/events/core.c).

Fixes: 1dbb6704de91 ("jump_label: Fix concurrent static_key_enable/disable()")
Reported-by: Zhang zhaotian <zhangzhaotian@h-partners.com>
Signed-off-by: Chen Jinghuang <chenjinghuang2@huawei.com>
---
 kernel/sched/core.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 96226707c2f6..61fb0d7966e4 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4629,13 +4629,17 @@ static void __set_numabalancing_state(bool enabled)
 		static_branch_disable(&sched_numa_balancing);
 }
 
+static DEFINE_MUTEX(numabalancing_mutex);
+
 void set_numabalancing_state(bool enabled)
 {
+	mutex_lock(&numabalancing_mutex);
 	if (enabled)
 		sysctl_numa_balancing_mode = NUMA_BALANCING_NORMAL;
 	else
 		sysctl_numa_balancing_mode = NUMA_BALANCING_DISABLED;
 	__set_numabalancing_state(enabled);
+	mutex_unlock(&numabalancing_mutex);
 }
 
 #ifdef CONFIG_PROC_SYSCTL
-- 
2.34.1

Re: [PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key
Posted by K Prateek Nayak 1 month, 4 weeks ago
Hell Chen,

On 8/3/2026 6:00 PM, Chen Jinghuang wrote:
> +static DEFINE_MUTEX(numabalancing_mutex);
> +
>  void set_numabalancing_state(bool enabled)
>  {
> +	mutex_lock(&numabalancing_mutex);
>  	if (enabled)
>  		sysctl_numa_balancing_mode = NUMA_BALANCING_NORMAL;
>  	else
>  		sysctl_numa_balancing_mode = NUMA_BALANCING_DISABLED;
>  	__set_numabalancing_state(enabled);
> +	mutex_unlock(&numabalancing_mutex);
>  }

Doesn't sysctl_numa_balancing() directly call __set_numabalancing_state()
and this bit is only used by check_numabalancing_enable() on the mm side
during early init?

I think you should move this serialization into sysctl_numa_balancing()
because nothing can race during early init and we only need this for sysfs
writes.

-- 
Thanks and Regards,
Prateek
Re: [PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key
Posted by chenjinghuang 1 month, 4 weeks ago
On 8/4/2026 1:09 PM, K Prateek Nayak wrote:
> Hell Chen,
> 
> On 8/3/2026 6:00 PM, Chen Jinghuang wrote:
>> +static DEFINE_MUTEX(numabalancing_mutex);
>> +
>>  void set_numabalancing_state(bool enabled)
>>  {
>> +	mutex_lock(&numabalancing_mutex);
>>  	if (enabled)
>>  		sysctl_numa_balancing_mode = NUMA_BALANCING_NORMAL;
>>  	else
>>  		sysctl_numa_balancing_mode = NUMA_BALANCING_DISABLED;
>>  	__set_numabalancing_state(enabled);
>> +	mutex_unlock(&numabalancing_mutex);
>>  }
> 
> Doesn't sysctl_numa_balancing() directly call __set_numabalancing_state()
> and this bit is only used by check_numabalancing_enable() on the mm side
> during early init?
> 
> I think you should move this serialization into sysctl_numa_balancing()
> because nothing can race during early init and we only need this for sysfs
> writes.
> 
You're right - my mistake, I'll move numabalancing_mutex into sysctl_numa_balancing()
and drop it from the early-init path. No one races during init, so the lock is only
needed for sysfs writes:

+static DEFINE_MUTEX(numabalancing_mutex);
+
 static int sysctl_numa_balancing(const struct ctl_table *table, int write,
 			  void *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -4666,11 +4668,13 @@ static int sysctl_numa_balancing(const struct ctl_table *table, int write,
 	if (err < 0)
 		return err;
 	if (write) {
+		mutex_lock(&numabalancing_mutex);
 		if (!(sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING) &&
 		    (state & NUMA_BALANCING_MEMORY_TIERING))
 			reset_memory_tiering();
 		sysctl_numa_balancing_mode = state;
 		__set_numabalancing_state(state);
+		mutex_unlock(&numabalancing_mutex);
 	}
 	return err;
 }
Re: [PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key
Posted by K Prateek Nayak 1 month, 4 weeks ago
Hello Chen,

On 8/4/2026 2:05 PM, chenjinghuang wrote:
> You're right - my mistake, I'll move numabalancing_mutex into sysctl_numa_balancing()
> and drop it from the early-init path. No one races during init, so the lock is only
> needed for sysfs writes:
> 
> +static DEFINE_MUTEX(numabalancing_mutex);
> +
>  static int sysctl_numa_balancing(const struct ctl_table *table, int write,
>  			  void *buffer, size_t *lenp, loff_t *ppos)
>  {
> @@ -4666,11 +4668,13 @@ static int sysctl_numa_balancing(const struct ctl_table *table, int write,
>  	if (err < 0)
>  		return err;
>  	if (write) {
> +		mutex_lock(&numabalancing_mutex);

nit. You can just use a:

		guard(mutex)(&numabalancing_mutex); 

>  		if (!(sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING) &&
>  		    (state & NUMA_BALANCING_MEMORY_TIERING))
>  			reset_memory_tiering();
>  		sysctl_numa_balancing_mode = state;
>  		__set_numabalancing_state(state);
> +		mutex_unlock(&numabalancing_mutex);

... and save on the need to explicitly call unlock here.

>  	}
>  	return err;
>  }

-- 
Thanks and Regards,
Prateek
Re: [PATCH] sched/numa: Prevent race on sysctl_numa_balancing static key
Posted by chenjinghuang 1 month, 4 weeks ago
On 8/4/2026 4:44 PM, K Prateek Nayak wrote:
> Hello Chen,
> 
> On 8/4/2026 2:05 PM, chenjinghuang wrote:
>> You're right - my mistake, I'll move numabalancing_mutex into sysctl_numa_balancing()
>> and drop it from the early-init path. No one races during init, so the lock is only
>> needed for sysfs writes:
>>
>> +static DEFINE_MUTEX(numabalancing_mutex);
>> +
>>  static int sysctl_numa_balancing(const struct ctl_table *table, int write,
>>  			  void *buffer, size_t *lenp, loff_t *ppos)
>>  {
>> @@ -4666,11 +4668,13 @@ static int sysctl_numa_balancing(const struct ctl_table *table, int write,
>>  	if (err < 0)
>>  		return err;
>>  	if (write) {
>> +		mutex_lock(&numabalancing_mutex);
> 
> nit. You can just use a:
> 
> 		guard(mutex)(&numabalancing_mutex); 
> 
>>  		if (!(sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING) &&
>>  		    (state & NUMA_BALANCING_MEMORY_TIERING))
>>  			reset_memory_tiering();
>>  		sysctl_numa_balancing_mode = state;
>>  		__set_numabalancing_state(state);
>> +		mutex_unlock(&numabalancing_mutex);
> 
> ... and save on the need to explicitly call unlock here.
> 
>>  	}
>>  	return err;
>>  }
> 
Thanks Prateek, I'll update the patch accordingly.