Re: [PATCH] sched/deadline: Fix spurious -EPERM for migration-disabled tasks

From: Juri Lelli

Date: Tue Nov 03 2026 - 02:09:57 EST


Hello,

On 02/11/26 14:54, Andrea Parri wrote:
> sched_setattr() can refuse to make a task SCHED_DEADLINE with -EPERM,
> even though the task's affinity covers the whole root domain. A retry
> can succeed, but the error blames an affinity the task does not have.
>
> __sched_setscheduler() tests p->cpus_ptr, which is not always the task's
> affinity. When a task is scheduled out inside a migrate_disable() section,
> migrate_disable_switch() points p->cpus_ptr at the current CPU until
> migrate_enable(). On PREEMPT_RT every spinlock_t holder is in such a
> section, so ordinary kernel work opens the window. Elsewhere, seccomp
> filters run in one.
>
> Test p->cpus_mask instead, which keeps the task's affinity and is what
> sched_getaffinity() reports. This should be safe because an admitted
> task's cpus_mask covers the root domain, so set_cpus_allowed_dl() moves
> no bandwidth when migrate_enable() restores p->cpus_ptr.
>
> The following program, run as root, had 553679, 453720 and 424623 of its
> 1000000 requests refused on three boots of a 4-CPU PREEMPT_RT guest, and
> 236182, 217174 and 237183 without PREEMPT_RT. With this applied the count
> is 0, also with lockdep, and a task pinned to one CPU is still refused:
>
> #define _GNU_SOURCE
> #include <errno.h>
> #include <linux/filter.h>
> #include <linux/seccomp.h>
> #include <pthread.h>
> #include <stddef.h>
> #include <stdint.h>
> #include <stdio.h>
> #include <sys/syscall.h>
> #include <unistd.h>
>
> #define REQUESTS 1000000
> #define INSNS 300
>
> struct attr {
> uint32_t size, policy;
> uint64_t flags;
> int32_t nice;
> uint32_t prio;
> uint64_t runtime, deadline, period;
> };
>
> static volatile int stop;
> static volatile pid_t target;
>
> static long set(struct attr a)
> {
> a.size = sizeof(a);
> return syscall(SYS_sched_setattr, target, &a, 0);
> }
>
> static void *worker(void *arg)
> {
> struct sock_filter f[INSNS + 1];
> struct sock_fprog prog = { .len = INSNS + 1, .filter = f };
> int i;
>
> /* Reading the arguments keeps seccomp from caching the result */
> for (i = 0; i < INSNS; i++)
> f[i] = (struct sock_filter)
> BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
> offsetof(struct seccomp_data, args));
> f[INSNS] = (struct sock_filter)
> BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
> syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog);
>
> target = syscall(SYS_gettid);
> while (!stop)
> syscall(SYS_getppid);
> return arg;
> }
>
> static void *hog(void *arg)
> {
> while (!stop)
> ;
> return arg;
> }
>
> int main(void)
> {
> pthread_t t[9];
> long refused = 0;
> int i;
>
> pthread_create(&t[0], NULL, worker, NULL);
> while (!target)
> usleep(100);
> for (i = 1; i < 9; i++)
> pthread_create(&t[i], NULL, hog, NULL);
> for (i = 0; i < REQUESTS; i++) {
> /* SCHED_DEADLINE, 2us every 1s */
> if (set((struct attr){ .policy = 6, .runtime = 2000,
> .deadline = 1000000000,
> .period = 1000000000 }) && errno == EPERM)
> refused++;
> set((struct attr){ .policy = 0 }); /* SCHED_OTHER */
> }
> stop = 1;
> for (i = 0; i < 9; i++)
> pthread_join(t[i], NULL);
> printf("%ld of %d requests refused\n", refused, REQUESTS);
> return 0;
> }
>
> An LLM helped find this bug and write this patch and its test.
>
> Fixes: af449901b84c ("sched: Add migrate_disable()")
> Signed-off-by: Andrea Parri <parri.andrea@xxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx

The fix makes sense to me.

Acked-by: Juri Lelli <juri.lelli@xxxxxxxxxx>

I only wonder if we want the reproducer in the changelog. An alternative
might be to move it to https://github.com/jlelli/sched-deadline-tests
where I keep the rest of SCHED_DEADLINE tests. I can probably do that if
you agree and prefer.

Thanks!
Juri