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:
parent
9cdfc15e31
commit
97973b1833
4 changed files with 85 additions and 27 deletions
|
@ -156,20 +156,29 @@ not recommended when the configuration is not known, or at least limited to
|
||||||
specific directives.
|
specific directives.
|
||||||
|
|
||||||
*-F* _level_::
|
*-F* _level_::
|
||||||
This option configures a system call filter when *chronyd* is compiled with
|
This option configures system call filters loaded by *chronyd* processes if it
|
||||||
support for the Linux secure computing (seccomp) facility. In level 1 the
|
was compiled with support for the Linux secure computing (seccomp) facility.
|
||||||
process is killed when a forbidden system call is made, in level -1 the SIGSYS
|
Three levels are defined: 0, 1, 2. The filters are disabled at level 0. At
|
||||||
signal is thrown instead and in level 0 the filter is disabled. The default
|
levels 1 and 2, *chronyd* will be killed if it makes a system call which is
|
||||||
value is 0.
|
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
|
At level 1, the filters allow only selected system calls that are normally
|
||||||
version of the system where *chrony* is installed as the filter needs to allow
|
expected to be made by *chronyd*. Other system calls are blocked. This level is
|
||||||
also system calls made from libraries that *chronyd* is using (e.g. libc) and
|
recommended only if it is known to work on the version of the system where
|
||||||
different versions or implementations of the libraries might make different
|
*chrony* is installed. The filters need to allow also system calls made by
|
||||||
system calls. If the filter is missing some system call, *chronyd* could be
|
libraries that *chronyd* is using (e.g. libc), but different versions or
|
||||||
killed even in normal operation.
|
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_::
|
*-P* _priority_::
|
||||||
On Linux, this option will select the SCHED_FIFO real-time scheduler at the
|
On Linux, this option will select the SCHED_FIFO real-time scheduler at the
|
||||||
|
|
75
sys_linux.c
75
sys_linux.c
|
@ -486,7 +486,7 @@ void check_seccomp_applicability(void)
|
||||||
void
|
void
|
||||||
SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
|
SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
|
||||||
{
|
{
|
||||||
const int syscalls[] = {
|
const int allowed[] = {
|
||||||
/* Clock */
|
/* Clock */
|
||||||
SCMP_SYS(adjtimex),
|
SCMP_SYS(adjtimex),
|
||||||
SCMP_SYS(clock_adjtime),
|
SCMP_SYS(clock_adjtime),
|
||||||
|
@ -614,6 +614,20 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
|
||||||
SCMP_SYS(uname),
|
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[] = {
|
const int socket_domains[] = {
|
||||||
AF_NETLINK, AF_UNIX, AF_INET,
|
AF_NETLINK, AF_UNIX, AF_INET,
|
||||||
#ifdef FEAT_IPV6
|
#ifdef FEAT_IPV6
|
||||||
|
@ -666,31 +680,65 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
unsigned int default_action, deny_action;
|
||||||
scmp_filter_ctx *ctx;
|
scmp_filter_ctx *ctx;
|
||||||
int i;
|
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) {
|
if (context == SYS_MAIN_PROCESS) {
|
||||||
/* Check if the chronyd configuration is supported */
|
/* Check if the chronyd configuration is supported */
|
||||||
check_seccomp_applicability();
|
check_seccomp_applicability();
|
||||||
|
|
||||||
/* Start the helper process, which will run without any seccomp filter. It
|
/* At level 1, start a helper process which will not have a seccomp filter.
|
||||||
will be used for getaddrinfo(), for which it's difficult to maintain a
|
It will be used for getaddrinfo(), for which it is difficult to maintain
|
||||||
list of required system calls (with glibc it depends on what NSS modules
|
a list of required system calls (with glibc it depends on what NSS
|
||||||
are installed and enabled on the system). */
|
modules are installed and enabled on the system). */
|
||||||
PRV_StartHelper();
|
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)
|
if (ctx == NULL)
|
||||||
LOG_FATAL("Failed to initialize seccomp");
|
LOG_FATAL("Failed to initialize seccomp");
|
||||||
|
|
||||||
/* Add system calls that are always allowed */
|
if (default_action != SCMP_ACT_ALLOW) {
|
||||||
for (i = 0; i < (sizeof (syscalls) / sizeof (*syscalls)); i++) {
|
for (i = 0; i < sizeof (allowed) / sizeof (*allowed); i++) {
|
||||||
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls[i], 0) < 0)
|
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, allowed[i], 0) < 0)
|
||||||
goto add_failed;
|
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 */
|
/* Allow opening sockets in selected domains */
|
||||||
for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
|
for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
|
||||||
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
|
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)
|
if (seccomp_load(ctx) < 0)
|
||||||
LOG_FATAL("Failed to load seccomp rules");
|
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);
|
seccomp_release(ctx);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
|
||||||
|
|
||||||
test_start "system call filter in non-destructive tests"
|
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:"
|
test_message 1 1 "level $level:"
|
||||||
for test in 0[0-8][0-9]-*[^_]; do
|
for test in 0[0-8][0-9]-*[^_]; do
|
||||||
test_message 2 0 "$test"
|
test_message 2 0 "$test"
|
||||||
|
|
|
@ -6,7 +6,7 @@ check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
|
||||||
|
|
||||||
test_start "system call filter in destructive tests"
|
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:"
|
test_message 1 1 "level $level:"
|
||||||
for test in 1[0-8][0-9]-*[^_]; do
|
for test in 1[0-8][0-9]-*[^_]; do
|
||||||
test_message 2 0 "$test"
|
test_message 2 0 "$test"
|
||||||
|
|
Loading…
Reference in a new issue