Re: [PATCH] sched/uclamp: Reject clamp values above SCHED_CAPACITY_SCALE
From: Vincent Guittot
Date: Tue Nov 03 2026 - 11:02:20 EST
On Mon, 2 Nov 2026 at 14:48, Andrea Parri <parri.andrea@xxxxxxxxx> wrote:
>
> sched_setattr() accepts sched_util_min and sched_util_max values that do
> not fit in uclamp_se::value. A single thread can do:
>
> sched_setattr(max = 800) returns 0
> sched_setattr(min = 0xfffffffe) returns 0
> sched_getattr() min=2046 max=800
I can reproduce this on arm64
>
> which leaves the minimum above both SCHED_CAPACITY_SCALE and the task's own
> maximum.
>
> The range check in uclamp_validate() relies on the + 1 wrapping -1, the
> reset value, to 0:
>
> if (util_min + 1 > SCHED_CAPACITY_SCALE + 1)
>
> That only works for an unsigned type. util_min and util_max are int and
> SCHED_CAPACITY_SCALE is a long, so every negative value passes the check,
> and so does INT_MAX, whose + 1 wraps.
>
> uclamp_se_set() then truncates what got through to the 11 bits of
> uclamp_se::value, but computes the bucket from the full value. A minimum
> of 0xfffff800 is stored as 0 in the top bucket, where uclamp_rq_max_value()
> takes it for the highest active clamp once a higher value leaves the rq.
> The rq clamp falls below what the remaining tasks requested, and emptying
> their bucket trips the WARN_ON_ONCE() in uclamp_rq_dec_id(), without any
> privilege:
>
> WARNING: kernel/sched/core.c:1862 at dequeue_task+0x2a3/0x2b0, CPU#0: repro/126
> Call Trace:
> __schedule+0x933/0x1320
> schedule+0x36/0x100
> do_nanosleep+0x61/0x190
> hrtimer_nanosleep+0xb5/0x1d0
and this as well
>
> Make util_min and util_max unsigned int, so that -1 is the only value that
> wraps. The pair check below is unaffected, since it skips -1 explicitly and
> anything else is at most SCHED_CAPACITY_SCALE by then.
>
> Requests that were only accepted because of this now fail with -EINVAL.
>
> The following program triggers the warning when run by any user outside
> a child cpu cgroup. With this applied, its 0xfffff800 request fails with
> -EINVAL and the warning no longer appears:
>
> #define _GNU_SOURCE
> #include <pthread.h>
> #include <sched.h>
> #include <stdint.h>
> #include <sys/syscall.h>
> #include <time.h>
> #include <unistd.h>
>
> struct attr {
> uint32_t size, policy;
> uint64_t flags;
> int32_t nice;
> uint32_t prio;
> uint64_t runtime, deadline, period;
> uint32_t util_min, util_max;
> };
>
> static volatile int stop;
>
> static void set_min(uint32_t v)
> {
> /* KEEP_POLICY | KEEP_PARAMS | UTIL_CLAMP_MIN */
> struct attr a = { .size = sizeof(a), .flags = 0x38, .util_min = v };
>
> syscall(SYS_sched_setattr, 0, &a, 0);
> }
>
> static void burn(long us)
> {
> struct timespec s, n;
>
> clock_gettime(CLOCK_MONOTONIC, &s);
> do
> clock_gettime(CLOCK_MONOTONIC, &n);
> while ((n.tv_sec - s.tv_sec) * 1000000 +
> (n.tv_nsec - s.tv_nsec) / 1000 < us);
> }
>
> static void *bogus(void *arg)
> {
> set_min(0xfffff800);
> while (!stop)
> burn(1000);
> return arg;
> }
>
> static void *worker(void *arg)
> {
> long v = (long)arg;
>
> set_min(v);
> while (!stop) {
> burn(v == 800 ? 300 : 1500);
> usleep(100);
> }
> return arg;
> }
>
> int main(void)
> {
> pthread_t t[3];
> cpu_set_t c;
> int i;
>
> CPU_ZERO(&c);
> CPU_SET(0, &c);
> sched_setaffinity(0, sizeof(c), &c);
> pthread_create(&t[0], NULL, bogus, NULL);
> usleep(50000);
> pthread_create(&t[1], NULL, worker, (void *)500L);
> pthread_create(&t[2], NULL, worker, (void *)800L);
> sleep(15);
> stop = 1;
> for (i = 0; i < 3; i++)
> pthread_join(t[i], NULL);
> return 0;
> }
>
> An LLM helped find the warning and write this patch and its tests.
>
> Fixes: 480a6ca2dc6e ("sched/uclamp: Allow to reset a task uclamp constraint value")
> Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
> Link: https://sashiko.dev/#/patchset/20261016062629.2102883-1-sh_def%40163.com
> Signed-off-by: Andrea Parri <parri.andrea@xxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx
Reviewed-by: Vincent Guittot <vincent.guittot@xxxxxxxxxx>
> ---
> This textually conflicts with [1], which rewrites these declarations and
> keeps them int. Whichever goes second needs to keep them unsigned.
>
> Tested on x86_64: 0xfffffffe, 0x80000000 and 0x7fffffff now fail with
> -EINVAL and -1 still resets. Not run on a 32-bit kernel.
>
> [1] https://lore.kernel.org/20261016062629.2102883-1-sh_def@xxxxxxx/
>
> kernel/sched/syscalls.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
> index 45a127deeadd9..abe5ea6b3e36d 100644
> --- a/kernel/sched/syscalls.c
> +++ b/kernel/sched/syscalls.c
> @@ -318,8 +318,8 @@ static inline void __setscheduler_dl_pi(int newprio, int policy,
> static int uclamp_validate(struct task_struct *p,
> const struct sched_attr *attr)
> {
> - int util_min = p->uclamp_req[UCLAMP_MIN].value;
> - int util_max = p->uclamp_req[UCLAMP_MAX].value;
> + unsigned int util_min = p->uclamp_req[UCLAMP_MIN].value;
> + unsigned int util_max = p->uclamp_req[UCLAMP_MAX].value;
>
> if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MIN) {
> util_min = attr->sched_util_min;
> --
> 2.53.0
>