diff --git a/doc/chronyd.adoc b/doc/chronyd.adoc index 9c47b3f..123c329 100644 --- a/doc/chronyd.adoc +++ b/doc/chronyd.adoc @@ -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 diff --git a/sys_linux.c b/sys_linux.c index 57b4e0f..9f31710 100644 --- a/sys_linux.c +++ b/sys_linux.c @@ -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). */ - PRV_StartHelper(); + /* 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) - goto add_failed; + 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_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 (context == SYS_MAIN_PROCESS) { + 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; diff --git a/test/system/099-scfilter b/test/system/099-scfilter index b3f26fd..6b098ac 100755 --- a/test/system/099-scfilter +++ b/test/system/099-scfilter @@ -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" diff --git a/test/system/199-scfilter b/test/system/199-scfilter index 749d159..29b7cc3 100755 --- a/test/system/199-scfilter +++ b/test/system/199-scfilter @@ -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"