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); } }