diff options
author | Luca Boccassi <bluca@debian.org> | 2023-02-15 19:59:28 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-15 19:59:28 +0000 |
commit | 010c73c68e68a3519168c42453c4ba69d9f3618e (patch) | |
tree | d7ae33779c1fa3f7d46e7a3d1f0b047c10319be7 /src/test | |
parent | 6f97aae029c0fe44d64dc5ff202ab0125571211f (diff) | |
parent | b7cca6cc5a60444f1ae3f1379a9c3bc7358cc0eb (diff) | |
download | systemd-010c73c68e68a3519168c42453c4ba69d9f3618e.tar.gz |
Merge pull request #26307 from yuwata/test-execute-credentials
test-execute: drop capabilities when testing with user manager
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/test-execute.c | 359 |
1 files changed, 233 insertions, 126 deletions
diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 52a38f7c52..e19565ef92 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <stdio.h> +#include <sys/mount.h> #include <sys/prctl.h> #include <sys/types.h> @@ -18,6 +19,7 @@ #include "manager.h" #include "missing_prctl.h" #include "mkdir.h" +#include "mount-util.h" #include "path-util.h" #include "process-util.h" #include "rm-rf.h" @@ -34,6 +36,8 @@ #include "user-util.h" #include "virt.h" +#define PRIVATE_UNIT_DIR "/run/test-execute-unit-dir" + static char *user_runtime_unit_dir = NULL; static bool can_unshare; @@ -261,6 +265,12 @@ static void test_exec_cpuaffinity(Manager *m) { test(m, "exec-cpuaffinity3.service", 0, CLD_EXITED); } +static void test_exec_credentials(Manager *m) { + test(m, "exec-set-credential.service", 0, CLD_EXITED); + test(m, "exec-load-credential.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_CREDENTIALS, CLD_EXITED); + test(m, "exec-credentials-dir-specifier.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_CREDENTIALS, CLD_EXITED); +} + static void test_exec_workingdirectory(Manager *m) { assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0); @@ -413,7 +423,7 @@ static void test_exec_privatedevices(Manager *m) { test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); test(m, "exec-privatedevices-no.service", 0, CLD_EXITED); test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); - test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_GROUP, CLD_EXITED); /* We use capsh to test if the capabilities are * properly set, so be sure that it exists */ @@ -424,9 +434,9 @@ static void test_exec_privatedevices(Manager *m) { } test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED); - test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED); + test(m, "exec-privatedevices-no-capability-mknod.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED); - test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED); + test(m, "exec-privatedevices-no-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); } static void test_exec_protecthome(Manager *m) { @@ -456,7 +466,7 @@ static void test_exec_protectkernelmodules(Manager *m) { return; } - test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED); + test(m, "exec-protectkernelmodules-no-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED); test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); } @@ -777,7 +787,7 @@ static void test_exec_systemcallfilter_system(Manager *m) { return; } - test(m, "exec-systemcallfilter-system-user.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-system-user.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); if (!check_nobody_user_and_group()) { log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); @@ -789,12 +799,12 @@ static void test_exec_systemcallfilter_system(Manager *m) { return; } - test(m, "exec-systemcallfilter-system-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-system-user-" NOBODY_USER_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); #endif } static void test_exec_user(Manager *m) { - test(m, "exec-user.service", 0, CLD_EXITED); + test(m, "exec-user.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); if (!check_nobody_user_and_group()) { log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); @@ -806,11 +816,11 @@ static void test_exec_user(Manager *m) { return; } - test(m, "exec-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + test(m, "exec-user-" NOBODY_USER_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); } static void test_exec_group(Manager *m) { - test(m, "exec-group.service", 0, CLD_EXITED); + test(m, "exec-group.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); if (!check_nobody_user_and_group()) { log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); @@ -822,16 +832,17 @@ static void test_exec_group(Manager *m) { return; } - test(m, "exec-group-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); + test(m, "exec-group-" NOBODY_GROUP_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); } static void test_exec_supplementarygroups(Manager *m) { - test(m, "exec-supplementarygroups.service", 0, CLD_EXITED); - test(m, "exec-supplementarygroups-single-group.service", 0, CLD_EXITED); - test(m, "exec-supplementarygroups-single-group-user.service", 0, CLD_EXITED); - test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", 0, CLD_EXITED); - test(m, "exec-supplementarygroups-multiple-groups-withgid.service", 0, CLD_EXITED); - test(m, "exec-supplementarygroups-multiple-groups-withuid.service", 0, CLD_EXITED); + int status = MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP; + test(m, "exec-supplementarygroups.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group-user.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withgid.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withuid.service", status, CLD_EXITED); } static char* private_directory_bad(Manager *m) { @@ -863,14 +874,16 @@ static void test_exec_dynamicuser(Manager *m) { return; } - test(m, "exec-dynamicuser-fixeduser.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + int status = can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NAMESPACE : EXIT_GROUP; + + test(m, "exec-dynamicuser-fixeduser.service", status, CLD_EXITED); if (check_user_has_group_with_same_name("adm")) - test(m, "exec-dynamicuser-fixeduser-adm.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-dynamicuser-fixeduser-adm.service", status, CLD_EXITED); if (check_user_has_group_with_same_name("games")) - test(m, "exec-dynamicuser-fixeduser-games.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); - test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); - test(m, "exec-dynamicuser-supplementarygroups.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); - test(m, "exec-dynamicuser-statedir.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-dynamicuser-fixeduser-games.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-supplementarygroups.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-statedir.service", status, CLD_EXITED); (void) rm_rf("/var/lib/quux", REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); @@ -882,7 +895,7 @@ static void test_exec_dynamicuser(Manager *m) { (void) rm_rf("/var/lib/private/waldo", REMOVE_ROOT|REMOVE_PHYSICAL); test(m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); - test(m, "exec-dynamicuser-statedir-migrate-step2.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-dynamicuser-statedir-migrate-step2.service", status, CLD_EXITED); test(m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); @@ -890,9 +903,9 @@ static void test_exec_dynamicuser(Manager *m) { (void) rm_rf("/var/lib/private/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf("/var/lib/private/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); - test(m, "exec-dynamicuser-runtimedirectory1.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); - test(m, "exec-dynamicuser-runtimedirectory2.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); - test(m, "exec-dynamicuser-runtimedirectory3.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-dynamicuser-runtimedirectory1.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-runtimedirectory2.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-runtimedirectory3.service", status, CLD_EXITED); } static void test_exec_environment(Manager *m) { @@ -958,9 +971,12 @@ static void test_exec_umask(Manager *m) { } static void test_exec_runtimedirectory(Manager *m) { + (void) rm_rf("/run/test-exec_runtimedirectory2", REMOVE_ROOT|REMOVE_PHYSICAL); test(m, "exec-runtimedirectory.service", 0, CLD_EXITED); + (void) rm_rf("/run/test-exec_runtimedirectory2", REMOVE_ROOT|REMOVE_PHYSICAL); + test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED); - test(m, "exec-runtimedirectory-owner.service", 0, CLD_EXITED); + test(m, "exec-runtimedirectory-owner.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); if (!check_nobody_user_and_group()) { log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); @@ -972,7 +988,7 @@ static void test_exec_runtimedirectory(Manager *m) { return; } - test(m, "exec-runtimedirectory-owner-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); + test(m, "exec-runtimedirectory-owner-" NOBODY_GROUP_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); } static void test_exec_capabilityboundingset(Manager *m) { @@ -1046,7 +1062,7 @@ static void test_exec_privatenetwork(Manager *m) { return; } - test(m, "exec-privatenetwork-yes.service", can_unshare ? 0 : EXIT_NETWORK, CLD_EXITED); + test(m, "exec-privatenetwork-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE, CLD_EXITED); } static void test_exec_oomscoreadjust(Manager *m) { @@ -1056,7 +1072,7 @@ static void test_exec_oomscoreadjust(Manager *m) { log_notice("Testing in container, skipping remaining tests in %s", __func__); return; } - test(m, "exec-oomscoreadjust-negative.service", 0, CLD_EXITED); + test(m, "exec-oomscoreadjust-negative.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); } static void test_exec_ioschedulingclass(Manager *m) { @@ -1068,7 +1084,7 @@ static void test_exec_ioschedulingclass(Manager *m) { log_notice("Testing in container, skipping remaining tests in %s", __func__); return; } - test(m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED); + test(m, "exec-ioschedulingclass-realtime.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_IOPRIO, CLD_EXITED); } static void test_exec_unsetenvironment(Manager *m) { @@ -1077,9 +1093,12 @@ static void test_exec_unsetenvironment(Manager *m) { static void test_exec_specifier(Manager *m) { test(m, "exec-specifier.service", 0, CLD_EXITED); + if (MANAGER_IS_SYSTEM(m)) + test(m, "exec-specifier-system.service", 0, CLD_EXITED); + else + test(m, "exec-specifier-user.service", 0, CLD_EXITED); test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED); test(m, "exec-specifier-interpolation.service", 0, CLD_EXITED); - test(m, "exec-specifier-credentials-dir.service", 0, CLD_EXITED); } static void test_exec_standardinput(Manager *m) { @@ -1112,7 +1131,7 @@ static void test_exec_umask_namespace(Manager *m) { log_notice("Testing without inaccessible, skipping %s", __func__); return; } - test(m, "exec-umask-namespace.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-umask-namespace.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NAMESPACE : EXIT_GROUP, CLD_EXITED); } typedef struct test_entry { @@ -1122,40 +1141,28 @@ typedef struct test_entry { #define entry(x) {x, #x} -static int run_tests(LookupScope scope, const test_entry tests[], char **patterns) { +static void run_tests(LookupScope scope, char **patterns) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_free_ char *unit_paths = NULL; _cleanup_(manager_freep) Manager *m = NULL; int r; - assert_se(tests); - - r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m); - m->default_std_output = EXEC_OUTPUT_NULL; /* don't rely on host journald */ - if (manager_errno_skip_test(r)) - return log_tests_skipped_errno(r, "manager_new"); - assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); - - for (const test_entry *test = tests; test->f; test++) - if (strv_fnmatch_or_empty(patterns, test->name, FNM_NOESCAPE)) - test->f(m); - else - log_info("Skipping %s because it does not match any pattern.", test->name); - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; - - static const test_entry user_tests[] = { + static const test_entry tests[] = { entry(test_exec_basic), entry(test_exec_ambientcapabilities), entry(test_exec_bindpaths), entry(test_exec_capabilityboundingset), entry(test_exec_condition), entry(test_exec_cpuaffinity), + entry(test_exec_credentials), + entry(test_exec_dynamicuser), entry(test_exec_environment), entry(test_exec_environmentfile), + entry(test_exec_execsearchpath), + entry(test_exec_execsearchpath_environment), + entry(test_exec_execsearchpath_environment_files), + entry(test_exec_execsearchpath_passenvironment), + entry(test_exec_execsearchpath_specifier), entry(test_exec_group), entry(test_exec_ignoresigpipe), entry(test_exec_inaccessiblepaths), @@ -1174,6 +1181,7 @@ int main(int argc, char *argv[]) { entry(test_exec_readwritepaths), entry(test_exec_restrictnamespaces), entry(test_exec_runtimedirectory), + entry(test_exec_specifier), entry(test_exec_standardinput), entry(test_exec_standardoutput), entry(test_exec_standardoutput_append), @@ -1181,35 +1189,15 @@ int main(int argc, char *argv[]) { entry(test_exec_supplementarygroups), entry(test_exec_systemcallerrornumber), entry(test_exec_systemcallfilter), + entry(test_exec_systemcallfilter_system), entry(test_exec_temporaryfilesystem), entry(test_exec_umask), + entry(test_exec_umask_namespace), entry(test_exec_unsetenvironment), entry(test_exec_user), entry(test_exec_workingdirectory), - entry(test_exec_execsearchpath), - entry(test_exec_execsearchpath_environment), - entry(test_exec_execsearchpath_environment_files), - entry(test_exec_execsearchpath_passenvironment), - {}, - }; - static const test_entry system_tests[] = { - entry(test_exec_dynamicuser), - entry(test_exec_specifier), - entry(test_exec_execsearchpath_specifier), - entry(test_exec_systemcallfilter_system), - entry(test_exec_umask_namespace), {}, }; - int r; - - test_setup_logging(LOG_DEBUG); - -#if HAS_FEATURE_ADDRESS_SANITIZER - if (strstr_ptr(ci_environment(), "travis") || strstr_ptr(ci_environment(), "github-actions")) { - log_notice("Running on Travis CI/GH Actions under ASan, skipping, see https://github.com/systemd/systemd/issues/10696"); - return EXIT_TEST_SKIP; - } -#endif assert_se(unsetenv("USER") == 0); assert_se(unsetenv("LOGNAME") == 0); @@ -1217,68 +1205,187 @@ int main(int argc, char *argv[]) { assert_se(unsetenv("HOME") == 0); assert_se(unsetenv("TMPDIR") == 0); - can_unshare = have_namespaces(); - - /* It is needed otherwise cgroup creation fails */ - if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) - return log_tests_skipped("not privileged"); - - r = enter_cgroup_subroot(NULL); - if (r == -ENOMEDIUM) - return log_tests_skipped("cgroupfs not available"); - - if (path_is_read_only_fs("/sys") > 0) - return log_tests_skipped("/sys is mounted read-only"); + /* Unset VARx, especially, VAR1, VAR2 and VAR3, which are used in the PassEnvironment test cases, + * otherwise (and if they are present in the environment), `manager_default_environment` will copy + * them into the default environment which is passed to each created job, which will make the tests + * that expect those not to be present to fail. */ + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); - _cleanup_free_ char *unit_dir = NULL, *unit_paths = NULL; - assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0); assert_se(runtime_dir = setup_fake_runtime_dir()); assert_se(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user")); - assert_se(unit_paths = strjoin(unit_dir, ":", user_runtime_unit_dir)); + assert_se(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir)); assert_se(set_unit_path(unit_paths) >= 0); - /* Unset VAR1, VAR2 and VAR3 which are used in the PassEnvironment test - * cases, otherwise (and if they are present in the environment), - * `manager_default_environment` will copy them into the default - * environment which is passed to each created job, which will make the - * tests that expect those not to be present to fail. - */ - assert_se(unsetenv("VAR1") == 0); - assert_se(unsetenv("VAR2") == 0); - assert_se(unsetenv("VAR3") == 0); + r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m); + if (manager_errno_skip_test(r)) + return (void) log_tests_skipped_errno(r, "manager_new"); + assert_se(r >= 0); + + m->default_std_output = EXEC_OUTPUT_NULL; /* don't rely on host journald */ + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* Uncomment below if you want to make debugging logs stored to journal. */ + //manager_override_log_target(m, LOG_TARGET_AUTO); + //manager_override_log_level(m, LOG_DEBUG); + + for (const test_entry *test = tests; test->f; test++) + if (strv_fnmatch_or_empty(patterns, test->name, FNM_NOESCAPE)) + test->f(m); + else + log_info("Skipping %s because it does not match any pattern.", test->name); +} + +static int prepare_ns(const char *process_name) { + int r; + + r = safe_fork(process_name, + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + assert_se(r >= 0); + if (r == 0) { + _cleanup_free_ char *unit_dir = NULL; - r = run_tests(LOOKUP_SCOPE_USER, user_tests, argv + 1); - if (r != 0) - return r; + /* Make "/" read-only. */ + assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT, NULL) >= 0); - r = run_tests(LOOKUP_SCOPE_SYSTEM, system_tests, argv + 1); - if (r != 0) - return r; + /* Creating a new user namespace in the above means all MS_SHARED mounts become MS_SLAVE. + * Let's put them back to MS_SHARED here, since that's what we want as defaults. (This will + * not reconnect propagation, but simply create new peer groups for all our mounts). */ + assert_se(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL) >= 0); + + assert_se(mkdir_p(PRIVATE_UNIT_DIR, 0755) >= 0); + + /* Mount tmpfs on the following directories to make not StateDirectory= or friends disturb the host. */ + FOREACH_STRING(p, "/root", "/tmp", "/var/tmp", "/var/lib", PRIVATE_UNIT_DIR) + assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); + + /* Copy unit files to make them accessible even when unprivileged. */ + assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0); + assert_se(copy_directory(unit_dir, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0); + + /* Prepare credstore like tmpfiles.d/credstore.conf for LoadCredential= tests. */ + FOREACH_STRING(p, "/run/credstore", "/run/credstore.encrypted") { + assert_se(mkdir_p(p, 0) >= 0); + assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000") >= 0); + } + + assert_se(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE) >= 0); + } + + return r; +} + +TEST(run_tests_root) { + _cleanup_strv_free_ char **filters = NULL; + + if (!have_namespaces()) + return (void) log_tests_skipped("unshare() is disabled"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + + if (prepare_ns("(test-execute-root)") == 0) { + can_unshare = true; + run_tests(LOOKUP_SCOPE_SYSTEM, filters); + _exit(EXIT_SUCCESS); + } +} + +TEST(run_tests_without_unshare) { + if (!have_namespaces()) { + /* unshare() is already filtered. */ + can_unshare = false; + run_tests(LOOKUP_SCOPE_SYSTEM, strv_skip(saved_argv, 1)); + return; + } #if HAVE_SECCOMP + _cleanup_strv_free_ char **filters = NULL; + int r; + /* The following tests are for 1beab8b0d0ff2d7d1436b52d4a0c3d56dc908962. */ - if (!is_seccomp_available()) { - log_notice("Seccomp not available, skipping unshare() filtered tests."); - return 0; - } + if (!is_seccomp_available()) + return (void) log_tests_skipped("Seccomp not available, cannot run unshare() filtered tests"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); - _cleanup_hashmap_free_ Hashmap *s = NULL; - assert_se(s = hashmap_new(NULL)); - r = seccomp_syscall_resolve_name("unshare"); - assert_se(r != __NR_SCMP_ERROR); - assert_se(hashmap_put(s, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)) >= 0); - assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true) >= 0); - assert_se(unshare(CLONE_NEWNS) < 0); - assert_se(errno == EOPNOTSUPP); + if (prepare_ns("(test-execute-without-unshare)") == 0) { + _cleanup_hashmap_free_ Hashmap *s = NULL; - can_unshare = false; + r = seccomp_syscall_resolve_name("unshare"); + assert_se(r != __NR_SCMP_ERROR); + assert_se(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)) >= 0); + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true) >= 0); - r = run_tests(LOOKUP_SCOPE_USER, user_tests, argv + 1); - if (r != 0) - return r; + /* Check unshare() is actually filtered. */ + assert_se(unshare(CLONE_NEWNS) < 0); + assert_se(errno == EOPNOTSUPP); - return run_tests(LOOKUP_SCOPE_SYSTEM, system_tests, argv + 1); + can_unshare = false; + run_tests(LOOKUP_SCOPE_SYSTEM, filters); + _exit(EXIT_SUCCESS); + } #else - return 0; + log_tests_skipped("Built without seccomp support, cannot run unshare() filtered tests"); +#endif +} + +TEST(run_tests_unprivileged) { + _cleanup_strv_free_ char **filters = NULL; + + if (!have_namespaces()) + return (void) log_tests_skipped("unshare() is disabled"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + + if (prepare_ns("(test-execute-unprivileged)") == 0) { + assert_se(capability_bounding_set_drop(0, /* right_now = */ true) >= 0); + + can_unshare = false; + run_tests(LOOKUP_SCOPE_USER, filters); + _exit(EXIT_SUCCESS); + } +} + +static int intro(void) { +#if HAS_FEATURE_ADDRESS_SANITIZER + if (strstr_ptr(ci_environment(), "travis") || strstr_ptr(ci_environment(), "github-actions")) + return log_tests_skipped("Running on Travis CI/GH Actions under ASan, see https://github.com/systemd/systemd/issues/10696"); #endif + /* It is needed otherwise cgroup creation fails */ + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) + return log_tests_skipped("not privileged"); + + if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + if (path_is_read_only_fs("/sys") > 0) + return log_tests_skipped("/sys is mounted read-only"); + + /* Create dummy network interface for testing PrivateNetwork=yes */ + (void) system("ip link add dummy-test-exec type dummy"); + + return EXIT_SUCCESS; } + +static int outro(void) { + (void) system("ip link del dummy-test-exec"); + (void) rmdir(PRIVATE_UNIT_DIR); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, outro); |