From 2d3dd0a4fc8dca1b8a543a35bff61ebf873ad869 Mon Sep 17 00:00:00 2001 From: Ricardo Branco Date: Wed, 14 Oct 2026 13:07:05 +0200 Subject: [PATCH] seccomp: also refuse sched_setattr() in RestrictRealtime= RestrictRealtime= only filtered sched_setscheduler(). sched_setattr() can switch a task to SCHED_FIFO and SCHED_RR as well, and is the only way to get SCHED_DEADLINE, so a process that is otherwise allowed to use realtime scheduling (CAP_SYS_NICE, or a non-zero RLIMIT_RTPRIO) could bypass the restriction. The policy is passed in a struct sched_attr by pointer, which seccomp cannot inspect, so refuse the syscall altogether. This also affects non-realtime callers of sched_setattr() (nice, utilization clamps), which now get the same error as the refused policies. Document this in systemd.exec(5) and extend the test. CPUSchedulingPolicy= is applied through sched_setattr(), so a service manager running under RestrictRealtime=yes (e.g. in a container) could no longer start units with a non-realtime policy. Fall back to sched_setscheduler() on EPERM, which the filter still allows for these policies. This is not possible with CPUSchedulingResetOnFork=yes, as the filter refuses SCHED_RESET_ON_FORK too, so setting the policy still fails in that case. Signed-off-by: Ricardo Branco (cherry picked from commit 590ff29a641a4ddb736ea59303af9721fc6c1400) --- man/systemd.exec.xml | 5 ++++- src/core/exec-invoke.c | 8 ++++++++ src/shared/seccomp-util.c | 12 ++++++++++++ src/test/test-seccomp.c | 26 ++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 2a8ffd8a47..151cff518a 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -2803,7 +2803,10 @@ RestrictNamespaces=~cgroup net for details about these scheduling policies. Realtime scheduling policies may be used to monopolize CPU time for longer periods of time, and may hence be used to lock up or otherwise trigger Denial-of-Service situations on the system. It is hence recommended to restrict access to realtime scheduling to the few programs - that actually require them. Defaults to off. + that actually require them. Note that the scheduling policy passed to + sched_setattr2 + cannot be inspected by the system call filter, hence that system call is refused altogether, including + for non-realtime uses. Defaults to off. diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index c706e017a4..a3b5d02041 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -5622,6 +5622,14 @@ int exec_invoke( }; r = RET_NERRNO(sched_setattr(/* pid= */ 0, &attr, /* flags= */ 0)); + if (r == -EPERM && !context->cpu_sched_reset_on_fork) { + /* RestrictRealtime= refuses sched_setattr() altogether (possibly in an outer + * manager), hence fall back to sched_setscheduler(), which it filters per policy. + * We cannot do that with SCHED_RESET_ON_FORK, which the filter refuses too. */ + struct sched_param param = { .sched_priority = context->cpu_sched_priority }; + + r = RET_NERRNO(sched_setscheduler(/* pid= */ 0, context->cpu_sched_policy, ¶m)); + } if (r == -EINVAL && !sched_policy_supported(context->cpu_sched_policy)) { _cleanup_free_ char *s = NULL; (void) sched_policy_to_string_alloc(context->cpu_sched_policy, &s); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index a32db7a7df..d6d619894a 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -1858,6 +1858,18 @@ int seccomp_restrict_realtime_full(int error_code) { continue; } + /* sched_setattr() can switch to SCHED_FIFO/SCHED_RR and is the only way to get + * SCHED_DEADLINE, but the policy is in a struct passed by pointer, which seccomp cannot + * inspect. Hence refuse the call altogether. */ + r = sym_seccomp_rule_add_exact( + seccomp, + SCMP_ACT_ERRNO(error_code), + SCMP_SYS(sched_setattr), + 0); + if (r < 0) + log_debug_errno(r, "Failed to add sched_setattr() rule for %s, ignoring: %m", + seccomp_arch_to_string(arch)); + r = sym_seccomp_load(seccomp); if (ERRNO_IS_NEG_SECCOMP_FATAL(r)) return r; diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index e991442220..e0cd59aee2 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -545,6 +545,32 @@ TEST(restrict_realtime) { assert_se(sched_setscheduler(0, SCHED_RR, &(struct sched_param) { .sched_priority = 1 }) < 0); assert_se(errno == ENOANO); + /* sched_setattr() is refused entirely, as its policy argument cannot be filtered on */ + struct sched_attr attr = { + .size = sizeof(attr), + .sched_policy = SCHED_FIFO, + .sched_priority = 1, + }; + assert_se(sched_setattr(0, &attr, 0) < 0); + assert_se(errno == ENOANO); + + attr = (struct sched_attr) { + .size = sizeof(attr), + .sched_policy = SCHED_DEADLINE, + .sched_runtime = 1000000, + .sched_deadline = 10000000, + .sched_period = 10000000, + }; + assert_se(sched_setattr(0, &attr, 0) < 0); + assert_se(errno == ENOANO); + + attr = (struct sched_attr) { + .size = sizeof(attr), + .sched_policy = SCHED_OTHER, + }; + assert_se(sched_setattr(0, &attr, 0) < 0); + assert_se(errno == ENOANO); + _exit(EXIT_SUCCESS); } }