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:
committed by
Luca Boccassi
parent
5e4d9b64f5
commit
2d3dd0a4fc
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user