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
+4 -1
View File
@@ -2803,7 +2803,10 @@ RestrictNamespaces=~cgroup net</programlisting>
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.</para>
that actually require them. Note that the scheduling policy passed to
<citerefentry project='man-pages'><refentrytitle>sched_setattr</refentrytitle><manvolnum>2</manvolnum></citerefentry>
cannot be inspected by the system call filter, hence that system call is refused altogether, including
for non-realtime uses. Defaults to off.</para>
<xi:include href="version-info.xml" xpointer="v231"/></listitem>
</varlistentry>
+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);
}
}