]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
sys_linux: add second scfilter level
authorMiroslav Lichvar <mlichvar@redhat.com>
Wed, 5 May 2021 09:21:39 +0000 (11:21 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 6 May 2021 11:37:21 +0000 (13:37 +0200)
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.

doc/chronyd.adoc
sys_linux.c
test/system/099-scfilter
test/system/199-scfilter

index 9c47b3f10bb15465d4a85dda13ff54c6ed13f04b..123c329e56ed809fa3bac8d6f1cf49540a5563a3 100644 (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
index 57b4e0f0c8f818f7fe80f6d7f65d1ac9c183997b..9f3171020013723e45460e1879be2264fbbc413f 100644 (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). */
-    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;
 
index b3f26fd03c899211c3dd3bc706d9dbc400d9c548..6b098ace4b1038bcd5036fae143a3ae740e620b0 100755 (executable)
@@ -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"
index 749d1596adc64c6b7419351ae5d731dd044fd029..29b7cc39ffd3dadd92f20ccf9060918e7f40aa7d 100755 (executable)
@@ -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"