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 <rbranco@suse.de>
(cherry picked from commit 590ff29a641a4ddb736ea59303af9721fc6c1400)
This commit is contained in:
Ricardo Branco
2026-10-16 17:24:52 +01:00
committed by Luca Boccassi
parent 5e4d9b64f5
commit 2d3dd0a4fc
4 changed files with 50 additions and 1 deletions
+8
View File
@@ -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, &param));
}
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);
+12
View File
@@ -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;
+26
View File
@@ -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);
}
}