From: Lennart Poettering Date: Thu, 12 Aug 2021 09:22:50 +0000 (+0200) Subject: test-fd-util: extend close_all_fds() test to trigger all fallback codepaths X-Git-Tag: v250-rc1~399^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F20344%2Fhead;p=thirdparty%2Fsystemd.git test-fd-util: extend close_all_fds() test to trigger all fallback codepaths 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. --- diff --git a/src/test/meson.build b/src/test/meson.build index 292b6329e46..7b4df45aac9 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -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']], diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 6001f8e057a..0aa229fbc9a 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -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"));