]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test-fd-util: extend close_all_fds() test to trigger all fallback codepaths 20344/head
authorLennart Poettering <lennart@poettering.net>
Thu, 12 Aug 2021 09:22:50 +0000 (11:22 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 27 Oct 2021 16:02:49 +0000 (18:02 +0200)
This extends the close_all_fds() logic to overmount /proc with an empty
tmpfs, and/or to block close_range() via seccomp, so that we run the
test case for the function with the fallback paths.

This should make sure that we don't regress in limited environments or
older kernels.

src/test/meson.build
src/test/test-fd-util.c

index 292b6329e46bbee55157848123d22922b2bf8445..7b4df45aac9e0a30ea3ef1931cd325f5557c8f2b 100644 (file)
@@ -227,7 +227,9 @@ tests += [
 
         [['src/test/test-proc-cmdline.c']],
 
-        [['src/test/test-fd-util.c']],
+        [['src/test/test-fd-util.c'],
+         [],
+         [libseccomp]],
 
         [['src/test/test-web-util.c']],
 
index 6001f8e057a9391d491b478fc0bce742754097b1..0aa229fbc9a0e573a7c17d7b32187791d615df41 100644 (file)
@@ -9,10 +9,13 @@
 #include "fileio.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "missing_syscall.h"
+#include "mount-util.h"
 #include "path-util.h"
 #include "process-util.h"
 #include "random-util.h"
 #include "rlimit-util.h"
+#include "seccomp-util.h"
 #include "serialize.h"
 #include "string-util.h"
 #include "tests.h"
@@ -213,7 +216,7 @@ static size_t validate_fds(
         return c; /* Return number of fds >= 0 in the array */
 }
 
-static void test_close_all_fds(void) {
+static void test_close_all_fds_inner(void) {
         _cleanup_free_ int *fds = NULL, *keep = NULL;
         size_t n_fds, n_keep;
         int max_fd;
@@ -225,6 +228,15 @@ static void test_close_all_fds(void) {
         max_fd = get_max_fd();
         assert_se(max_fd > 10);
 
+        if (max_fd > 7000) {
+                /* If the worst fallback is activated we need to iterate through all possible fds, hence,
+                 * let's lower the limit a small bit, so that we don't run for too long. Yes, this undoes the
+                 * rlimit_nofile_bump() call above partially. */
+
+                (void) setrlimit_closest(RLIMIT_NOFILE, &(struct rlimit) { 7000, 7000 });
+                max_fd = 7000;
+        }
+
         /* Try to use 5000 fds, but when we can't bump the rlimit to make that happen use the whole limit minus 10 */
         n_fds = MIN(((size_t) max_fd & ~1U) - 10U, 5000U);
         assert_se((n_fds & 1U) == 0U); /* make sure even number of fds */
@@ -278,6 +290,99 @@ static void test_close_all_fds(void) {
         log_open();
 }
 
+static int seccomp_prohibit_close_range(void) {
+#if defined(HAVE_SECCOMP) && defined(__SNR_close_range)
+        _cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL;
+        int r;
+
+        r = seccomp_init_for_arch(&seccomp, SCMP_ARCH_NATIVE, SCMP_ACT_ALLOW);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to acquire seccomp context, ignoring: %m");
+
+        r = seccomp_rule_add_exact(
+                        seccomp,
+                        SCMP_ACT_ERRNO(EPERM),
+                        SCMP_SYS(close_range),
+                        0);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to add close_range() rule, ignoring: %m");
+
+        r = seccomp_load(seccomp);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to apply close_range() restrictions, ignoring: %m");
+
+        return 0;
+#else
+        return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Seccomp support or close_range() syscall definition not availeble.");
+#endif
+}
+
+static void test_close_all_fds(void) {
+        int r;
+
+        /* Runs the test four times. Once as is. Once with close_range() syscall blocked via seccomp, once
+         * with /proc overmounted, and once with the combination of both. This should trigger all fallbacks in
+         * the close_range_all() function. */
+
+        r = safe_fork("(caf-plain)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
+        if (r == 0) {
+                test_close_all_fds_inner();
+                _exit(EXIT_SUCCESS);
+        }
+        assert_se(r >= 0);
+
+        if (geteuid() != 0) {
+                log_notice("Lacking privileges, skipping running tests with blocked close_range() and with /proc/ overnmounted.");
+                return;
+        }
+
+        r = safe_fork("(caf-noproc)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL);
+        if (r == 0) {
+                r = mount_nofollow_verbose(LOG_WARNING, "tmpfs", "/proc", "tmpfs", 0, NULL);
+                if (r < 0)
+                        log_notice("Overmounting /proc didn#t work, skipping close_all_fds() with masked /proc/.");
+                else
+                        test_close_all_fds_inner();
+                _exit(EXIT_SUCCESS);
+        }
+        assert_se(r >= 0);
+
+        if (!is_seccomp_available()) {
+                log_notice("Seccomp not available, skipping seccomp tests in %s", __func__);
+                return;
+        }
+
+        r = safe_fork("(caf-seccomp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
+        if (r == 0) {
+                r = seccomp_prohibit_close_range();
+                if (r < 0)
+                        log_notice("Applying seccomp filter didn't work, skipping close_all_fds() test with masked close_range().");
+                else
+                        test_close_all_fds_inner();
+
+                _exit(EXIT_SUCCESS);
+        }
+        assert_se(r >= 0);
+
+        r = safe_fork("(caf-scnp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL);
+        if (r == 0) {
+                r = seccomp_prohibit_close_range();
+                if (r < 0)
+                        log_notice("Applying seccomp filter didn't work, skipping close_all_fds() test with masked close_range().");
+                else {
+                        r = mount_nofollow_verbose(LOG_WARNING, "tmpfs", "/proc", "tmpfs", 0, NULL);
+                        if (r < 0)
+                                log_notice("Overmounting /proc didn#t work, skipping close_all_fds() with masked /proc/.");
+                        else
+                                test_close_all_fds_inner();
+                }
+
+                test_close_all_fds_inner();
+                _exit(EXIT_SUCCESS);
+        }
+        assert_se(r >= 0);
+}
+
 static void test_format_proc_fd_path(void) {
         assert_se(streq_ptr(FORMAT_PROC_FD_PATH(0), "/proc/self/fd/0"));
         assert_se(streq_ptr(FORMAT_PROC_FD_PATH(1), "/proc/self/fd/1"));