1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-31 07:51:21 +03:00

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.
This commit is contained in:
Lennart Poettering 2021-08-12 11:22:50 +02:00
parent 73fc0cbc87
commit b689197241
2 changed files with 109 additions and 2 deletions

View 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']],

View 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"));