sys_linux: add second scfilter level

Add level "2" to enable a filter which blocks only specific system calls
like fork and exec* instead of blocking everything unknown. It should
be reliable with respect to changes in libraries, but it provides only a
very limited protection.
This commit is contained in:
Miroslav Lichvar 2021-05-05 11:21:39 +02:00
parent 9cdfc15e31
commit 97973b1833
4 changed files with 85 additions and 27 deletions

View file

@ -156,20 +156,29 @@ not recommended when the configuration is not known, or at least limited to
specific directives.
*-F* _level_::
This option configures a system call filter when *chronyd* is compiled with
support for the Linux secure computing (seccomp) facility. In level 1 the
process is killed when a forbidden system call is made, in level -1 the SIGSYS
signal is thrown instead and in level 0 the filter is disabled. The default
value is 0.
This option configures system call filters loaded by *chronyd* processes if it
was compiled with support for the Linux secure computing (seccomp) facility.
Three levels are defined: 0, 1, 2. The filters are disabled at level 0. At
levels 1 and 2, *chronyd* will be killed if it makes a system call which is
blocked by the filters. The level can be specified as a negative number to
trigger the SIGSYS signal instead of SIGKILL, which can be useful for
debugging. The default value is 0.
+
It is recommended to enable the filter only when it is known to work on the
version of the system where *chrony* is installed as the filter needs to allow
also system calls made from libraries that *chronyd* is using (e.g. libc) and
different versions or implementations of the libraries might make different
system calls. If the filter is missing some system call, *chronyd* could be
killed even in normal operation.
At level 1, the filters allow only selected system calls that are normally
expected to be made by *chronyd*. Other system calls are blocked. This level is
recommended only if it is known to work on the version of the system where
*chrony* is installed. The filters need to allow also system calls made by
libraries that *chronyd* is using (e.g. libc), but different versions or
implementations of the libraries might make different system calls. If the
filters are missing a system call, *chronyd* could be killed even in normal
operation.
+
The filter cannot be used with the *mailonchange* directive.
At level 2, the filters block only a small number of specific system calls
(e.g. fork and exec). This approach should avoid false positives, but the
protection of the system against a compromised *chronyd* process is much more
limited.
+
The filters cannot be enabled with the *mailonchange* directive.
*-P* _priority_::
On Linux, this option will select the SCHED_FIFO real-time scheduler at the

View file

@ -486,7 +486,7 @@ void check_seccomp_applicability(void)
void
SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
{
const int syscalls[] = {
const int allowed[] = {
/* Clock */
SCMP_SYS(adjtimex),
SCMP_SYS(clock_adjtime),
@ -614,6 +614,20 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
SCMP_SYS(uname),
};
const int denied_any[] = {
SCMP_SYS(execve),
SCMP_SYS(execveat),
SCMP_SYS(fork),
SCMP_SYS(ptrace),
SCMP_SYS(vfork),
};
const int denied_ntske[] = {
SCMP_SYS(ioctl),
SCMP_SYS(setsockopt),
SCMP_SYS(socket),
};
const int socket_domains[] = {
AF_NETLINK, AF_UNIX, AF_INET,
#ifdef FEAT_IPV6
@ -666,31 +680,65 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
#endif
};
unsigned int default_action, deny_action;
scmp_filter_ctx *ctx;
int i;
/* Sign of the level determines the deny action (kill or SIGSYS).
At level 1, selected syscalls are allowed, others are denied.
At level 2, selected syscalls are denied, others are allowed. */
deny_action = level > 0 ? SCMP_ACT_KILL : SCMP_ACT_TRAP;
if (level < 0)
level = -level;
switch (level) {
case 1:
default_action = deny_action;
break;
case 2:
default_action = SCMP_ACT_ALLOW;
break;
default:
LOG_FATAL("Unsupported filter level");
}
if (context == SYS_MAIN_PROCESS) {
/* Check if the chronyd configuration is supported */
check_seccomp_applicability();
/* Start the helper process, which will run without any seccomp filter. It
will be used for getaddrinfo(), for which it's difficult to maintain a
list of required system calls (with glibc it depends on what NSS modules
are installed and enabled on the system). */
/* At level 1, start a helper process which will not have a seccomp filter.
It will be used for getaddrinfo(), for which it is difficult to maintain
a list of required system calls (with glibc it depends on what NSS
modules are installed and enabled on the system). */
if (default_action != SCMP_ACT_ALLOW)
PRV_StartHelper();
}
ctx = seccomp_init(level > 0 ? SCMP_ACT_KILL : SCMP_ACT_TRAP);
ctx = seccomp_init(default_action);
if (ctx == NULL)
LOG_FATAL("Failed to initialize seccomp");
/* Add system calls that are always allowed */
for (i = 0; i < (sizeof (syscalls) / sizeof (*syscalls)); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls[i], 0) < 0)
if (default_action != SCMP_ACT_ALLOW) {
for (i = 0; i < sizeof (allowed) / sizeof (*allowed); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, allowed[i], 0) < 0)
goto add_failed;
}
} else {
for (i = 0; i < sizeof (denied_any) / sizeof (*denied_any); i++) {
if (seccomp_rule_add(ctx, deny_action, denied_any[i], 0) < 0)
goto add_failed;
}
if (context == SYS_MAIN_PROCESS) {
if (context == SYS_NTSKE_HELPER) {
for (i = 0; i < sizeof (denied_ntske) / sizeof (*denied_ntske); i++) {
if (seccomp_rule_add(ctx, deny_action, denied_ntske[i], 0) < 0)
goto add_failed;
}
}
}
if (default_action != SCMP_ACT_ALLOW && context == SYS_MAIN_PROCESS) {
/* Allow opening sockets in selected domains */
for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
@ -727,7 +775,8 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
if (seccomp_load(ctx) < 0)
LOG_FATAL("Failed to load seccomp rules");
LOG(context == SYS_MAIN_PROCESS ? LOGS_INFO : LOGS_DEBUG, "Loaded seccomp filter");
LOG(context == SYS_MAIN_PROCESS ? LOGS_INFO : LOGS_DEBUG,
"Loaded seccomp filter (level %d)", level);
seccomp_release(ctx);
return;

View file

@ -6,7 +6,7 @@ check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
test_start "system call filter in non-destructive tests"
for level in "-1" "1"; do
for level in "-1" "1" "-2" "2"; do
test_message 1 1 "level $level:"
for test in 0[0-8][0-9]-*[^_]; do
test_message 2 0 "$test"

View file

@ -6,7 +6,7 @@ check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
test_start "system call filter in destructive tests"
for level in "-1" "1"; do
for level in "-1" "1" "-2" "2"; do
test_message 1 1 "level $level:"
for test in 1[0-8][0-9]-*[^_]; do
test_message 2 0 "$test"