systemd/0785-test-execute-drop-capabilities-when-testing-with-use.patch

889 lines
40 KiB
Diff
Raw Normal View History

From bdde465ad1df52cb9b9fbaab37d5d7541601d774 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 3 Feb 2023 12:28:31 +0900
Subject: [PATCH] test-execute: drop capabilities when testing with user
manager
Before this, tests are split into two categories, system and user, but
both are running in fully privileged environment. Hence, unprivileged
user scope was mostly not covered by the test.
Let's run all tests in both system and user scopes, and drop capabilities
when Manager is running in user scope.
This also makes the host environment protected more from the test run.
(cherry picked from commit 4e032f654b94c2544ccf937209303766dfa66c24)
Related: RHEL-27512
---
src/test/test-execute.c | 351 +++++++++++-------
...dynamicuser-statedir-migrate-step1.service | 16 +-
...dynamicuser-statedir-migrate-step2.service | 32 +-
.../exec-dynamicuser-statedir.service | 122 +++---
.../exec-privatenetwork-yes.service | 1 +
.../exec-specifier-system.service | 11 +
test/test-execute/exec-specifier-user.service | 11 +
test/test-execute/exec-specifier.service | 5 -
test/test-execute/exec-specifier@.service | 5 -
9 files changed, 333 insertions(+), 221 deletions(-)
create mode 100644 test/test-execute/exec-specifier-system.service
create mode 100644 test/test-execute/exec-specifier-user.service
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index ce3489d708..665ae8a833 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"
@@ -35,6 +37,8 @@
#include "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;
@@ -414,7 +418,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 */
@@ -425,9 +429,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) {
@@ -457,7 +461,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);
}
@@ -778,7 +782,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__);
@@ -790,12 +794,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__);
@@ -807,11 +811,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__);
@@ -823,16 +827,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) {
@@ -864,14 +869,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);
@@ -883,7 +890,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);
@@ -891,9 +898,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) {
@@ -959,9 +966,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__);
@@ -973,7 +983,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) {
@@ -1047,7 +1057,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) {
@@ -1057,7 +1067,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) {
@@ -1069,7 +1079,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) {
@@ -1078,9 +1088,13 @@ 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);
+ test(m, "exec-specifier-credentials-dir.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_CREDENTIALS, CLD_EXITED);
}
static void test_exec_standardinput(Manager *m) {
@@ -1113,7 +1127,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 {
@@ -1123,40 +1137,27 @@ 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_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),
@@ -1175,6 +1176,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),
@@ -1182,35 +1184,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);
@@ -1218,68 +1200,185 @@ 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;
+
+ /* Make "/" read-only. */
+ assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT, NULL) >= 0);
+
+ /* 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);
- r = run_tests(LOOKUP_SCOPE_USER, user_tests, argv + 1);
- if (r != 0)
- return r;
+ /* 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);
- r = run_tests(LOOKUP_SCOPE_SYSTEM, system_tests, argv + 1);
- if (r != 0)
- return r;
+ /* 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);
+ }
+ }
+
+ 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);
diff --git a/test/test-execute/exec-dynamicuser-statedir-migrate-step1.service b/test/test-execute/exec-dynamicuser-statedir-migrate-step1.service
index 1c79e4f722..2a5a1e1ff3 100644
--- a/test/test-execute/exec-dynamicuser-statedir-migrate-step1.service
+++ b/test/test-execute/exec-dynamicuser-statedir-migrate-step1.service
@@ -3,14 +3,14 @@
Description=Test DynamicUser= migrate StateDirectory= (preparation)
[Service]
-ExecStart=test -w /var/lib/test-dynamicuser-migrate
-ExecStart=test -w /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=test ! -L /var/lib/test-dynamicuser-migrate
-ExecStart=test ! -L /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=test -d /var/lib/test-dynamicuser-migrate
-ExecStart=test -d /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=touch /var/lib/test-dynamicuser-migrate/yay
-ExecStart=touch /var/lib/test-dynamicuser-migrate2/hoge/yayyay
+ExecStart=test -w %S/test-dynamicuser-migrate
+ExecStart=test -w %S/test-dynamicuser-migrate2/hoge
+ExecStart=test ! -L %S/test-dynamicuser-migrate
+ExecStart=test ! -L %S/test-dynamicuser-migrate2/hoge
+ExecStart=test -d %S/test-dynamicuser-migrate
+ExecStart=test -d %S/test-dynamicuser-migrate2/hoge
+ExecStart=touch %S/test-dynamicuser-migrate/yay
+ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
diff --git a/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service b/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
index 015b74ce22..e89f0c5aae 100644
--- a/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
+++ b/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
@@ -3,22 +3,22 @@
Description=Test DynamicUser= migrate StateDirectory=
[Service]
-ExecStart=test -w /var/lib/test-dynamicuser-migrate
-ExecStart=test -w /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=test -L /var/lib/test-dynamicuser-migrate
-ExecStart=test -L /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=test -d /var/lib/test-dynamicuser-migrate
-ExecStart=test -d /var/lib/test-dynamicuser-migrate2/hoge
-ExecStart=test -f /var/lib/test-dynamicuser-migrate/yay
-ExecStart=test -f /var/lib/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=test -d /var/lib/private/test-dynamicuser-migrate
-ExecStart=test -d /var/lib/private/test-dynamicuser-migrate2/hoge
-ExecStart=test -f /var/lib/private/test-dynamicuser-migrate/yay
-ExecStart=test -f /var/lib/private/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=touch /var/lib/test-dynamicuser-migrate/yay
-ExecStart=touch /var/lib/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=touch /var/lib/private/test-dynamicuser-migrate/yay
-ExecStart=touch /var/lib/private/test-dynamicuser-migrate2/hoge/yayyay
+ExecStart=test -w %S/test-dynamicuser-migrate
+ExecStart=test -w %S/test-dynamicuser-migrate2/hoge
+ExecStart=test -L %S/test-dynamicuser-migrate
+ExecStart=test -L %S/test-dynamicuser-migrate2/hoge
+ExecStart=test -d %S/test-dynamicuser-migrate
+ExecStart=test -d %S/test-dynamicuser-migrate2/hoge
+ExecStart=test -f %S/test-dynamicuser-migrate/yay
+ExecStart=test -f %S/test-dynamicuser-migrate2/hoge/yayyay
+ExecStart=test -d %S/private/test-dynamicuser-migrate
+ExecStart=test -d %S/private/test-dynamicuser-migrate2/hoge
+ExecStart=test -f %S/private/test-dynamicuser-migrate/yay
+ExecStart=test -f %S/private/test-dynamicuser-migrate2/hoge/yayyay
+ExecStart=touch %S/test-dynamicuser-migrate/yay
+ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
+ExecStart=touch %S/private/test-dynamicuser-migrate/yay
+ExecStart=touch %S/private/test-dynamicuser-migrate2/hoge/yayyay
ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
diff --git a/test/test-execute/exec-dynamicuser-statedir.service b/test/test-execute/exec-dynamicuser-statedir.service
index b33b4da74a..b7e36f529e 100644
--- a/test/test-execute/exec-dynamicuser-statedir.service
+++ b/test/test-execute/exec-dynamicuser-statedir.service
@@ -3,71 +3,71 @@
Description=Test DynamicUser= with StateDirectory=
[Service]
-ExecStart=test -w /var/lib/waldo
-ExecStart=test -w /var/lib/quux/pief
-ExecStart=test -w /var/lib/aaa
-ExecStart=test -w /var/lib/aaa/bbb
-ExecStart=test -w /var/lib/aaa/ccc
-ExecStart=test -w /var/lib/xxx
-ExecStart=test -w /var/lib/xxx/yyy
-ExecStart=test -w /var/lib/xxx/zzz
-ExecStart=test -w /var/lib/aaa/111
-ExecStart=test -w /var/lib/aaa/222
-ExecStart=test -w /var/lib/aaa/333
+ExecStart=test -w %S/waldo
+ExecStart=test -w %S/quux/pief
+ExecStart=test -w %S/aaa
+ExecStart=test -w %S/aaa/bbb
+ExecStart=test -w %S/aaa/ccc
+ExecStart=test -w %S/xxx
+ExecStart=test -w %S/xxx/yyy
+ExecStart=test -w %S/xxx/zzz
+ExecStart=test -w %S/aaa/111
+ExecStart=test -w %S/aaa/222
+ExecStart=test -w %S/aaa/333
-ExecStart=test -d /var/lib/waldo
-ExecStart=test -d /var/lib/quux/pief
-ExecStart=test -d /var/lib/aaa
-ExecStart=test -d /var/lib/aaa/bbb
-ExecStart=test -d /var/lib/aaa/ccc
-ExecStart=test -d /var/lib/xxx
-ExecStart=test -d /var/lib/xxx/yyy
-ExecStart=test -d /var/lib/xxx/zzz
-ExecStart=test -L /var/lib/aaa/111
-ExecStart=test -L /var/lib/aaa/222
-ExecStart=test -L /var/lib/aaa/333
+ExecStart=test -d %S/waldo
+ExecStart=test -d %S/quux/pief
+ExecStart=test -d %S/aaa
+ExecStart=test -d %S/aaa/bbb
+ExecStart=test -d %S/aaa/ccc
+ExecStart=test -d %S/xxx
+ExecStart=test -d %S/xxx/yyy
+ExecStart=test -d %S/xxx/zzz
+ExecStart=test -L %S/aaa/111
+ExecStart=test -L %S/aaa/222
+ExecStart=test -L %S/aaa/333
-ExecStart=touch /var/lib/waldo/hoge
-ExecStart=touch /var/lib/quux/pief/hoge
-ExecStart=touch /var/lib/aaa/hoge
-ExecStart=touch /var/lib/aaa/bbb/hoge
-ExecStart=touch /var/lib/aaa/ccc/hoge
-ExecStart=touch /var/lib/xxx/hoge
-ExecStart=touch /var/lib/xxx/yyy/hoge
-ExecStart=touch /var/lib/xxx/zzz/hoge
-ExecStart=touch /var/lib/aaa/111/foo
-ExecStart=touch /var/lib/aaa/222/foo
-ExecStart=touch /var/lib/aaa/333/foo
+ExecStart=touch %S/waldo/hoge
+ExecStart=touch %S/quux/pief/hoge
+ExecStart=touch %S/aaa/hoge
+ExecStart=touch %S/aaa/bbb/hoge
+ExecStart=touch %S/aaa/ccc/hoge
+ExecStart=touch %S/xxx/hoge
+ExecStart=touch %S/xxx/yyy/hoge
+ExecStart=touch %S/xxx/zzz/hoge
+ExecStart=touch %S/aaa/111/foo
+ExecStart=touch %S/aaa/222/foo
+ExecStart=touch %S/aaa/333/foo
-ExecStart=test -f /var/lib/waldo/hoge
-ExecStart=test -f /var/lib/quux/pief/hoge
-ExecStart=test -f /var/lib/aaa/hoge
-ExecStart=test -f /var/lib/aaa/bbb/hoge
-ExecStart=test -f /var/lib/aaa/ccc/hoge
-ExecStart=test -f /var/lib/xxx/hoge
-ExecStart=test -f /var/lib/xxx/yyy/hoge
-ExecStart=test -f /var/lib/xxx/zzz/hoge
-ExecStart=test -f /var/lib/aaa/111/foo
-ExecStart=test -f /var/lib/aaa/222/foo
-ExecStart=test -f /var/lib/aaa/333/foo
-ExecStart=test -f /var/lib/xxx/foo
-ExecStart=test -f /var/lib/xxx/yyy/foo
-ExecStart=test -f /var/lib/xxx/zzz/foo
+ExecStart=test -f %S/waldo/hoge
+ExecStart=test -f %S/quux/pief/hoge
+ExecStart=test -f %S/aaa/hoge
+ExecStart=test -f %S/aaa/bbb/hoge
+ExecStart=test -f %S/aaa/ccc/hoge
+ExecStart=test -f %S/xxx/hoge
+ExecStart=test -f %S/xxx/yyy/hoge
+ExecStart=test -f %S/xxx/zzz/hoge
+ExecStart=test -f %S/aaa/111/foo
+ExecStart=test -f %S/aaa/222/foo
+ExecStart=test -f %S/aaa/333/foo
+ExecStart=test -f %S/xxx/foo
+ExecStart=test -f %S/xxx/yyy/foo
+ExecStart=test -f %S/xxx/zzz/foo
-ExecStart=test -f /var/lib/private/waldo/hoge
-ExecStart=test -f /var/lib/private/quux/pief/hoge
-ExecStart=test -f /var/lib/private/aaa/hoge
-ExecStart=test -f /var/lib/private/aaa/bbb/hoge
-ExecStart=test -f /var/lib/private/aaa/ccc/hoge
-ExecStart=test -f /var/lib/private/xxx/hoge
-ExecStart=test -f /var/lib/private/xxx/yyy/hoge
-ExecStart=test -f /var/lib/private/xxx/zzz/hoge
-ExecStart=test -f /var/lib/private/aaa/111/foo
-ExecStart=test -f /var/lib/private/aaa/222/foo
-ExecStart=test -f /var/lib/private/aaa/333/foo
-ExecStart=test -f /var/lib/private/xxx/foo
-ExecStart=test -f /var/lib/private/xxx/yyy/foo
-ExecStart=test -f /var/lib/private/xxx/zzz/foo
+ExecStart=test -f %S/private/waldo/hoge
+ExecStart=test -f %S/private/quux/pief/hoge
+ExecStart=test -f %S/private/aaa/hoge
+ExecStart=test -f %S/private/aaa/bbb/hoge
+ExecStart=test -f %S/private/aaa/ccc/hoge
+ExecStart=test -f %S/private/xxx/hoge
+ExecStart=test -f %S/private/xxx/yyy/hoge
+ExecStart=test -f %S/private/xxx/zzz/hoge
+ExecStart=test -f %S/private/aaa/111/foo
+ExecStart=test -f %S/private/aaa/222/foo
+ExecStart=test -f %S/private/aaa/333/foo
+ExecStart=test -f %S/private/xxx/foo
+ExecStart=test -f %S/private/xxx/yyy/foo
+ExecStart=test -f %S/private/xxx/zzz/foo
ExecStart=sh -x -c 'test "$$STATE_DIRECTORY" = "%S/aaa:%S/aaa/bbb:%S/aaa/ccc:%S/quux/pief:%S/waldo:%S/xxx:%S/xxx/yyy:%S/xxx/zzz"'
diff --git a/test/test-execute/exec-privatenetwork-yes.service b/test/test-execute/exec-privatenetwork-yes.service
index 0fff048b94..360099d337 100644
--- a/test/test-execute/exec-privatenetwork-yes.service
+++ b/test/test-execute/exec-privatenetwork-yes.service
@@ -4,5 +4,6 @@ Description=Test for PrivateNetwork
[Service]
ExecStart=/bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -Ev ": (lo|(erspan|gre|gretap|ip_vti|ip6_vti|ip6gre|ip6tnl|sit|tunl)0@.*):"'
+ExecStart=/bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -F ": dummy-test-exec:"'
Type=oneshot
PrivateNetwork=yes
diff --git a/test/test-execute/exec-specifier-system.service b/test/test-execute/exec-specifier-system.service
new file mode 100644
index 0000000000..9e8ee567aa
--- /dev/null
+++ b/test/test-execute/exec-specifier-system.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for specifiers (system)
+
+[Service]
+Type=oneshot
+ExecStart=test %t = /run
+ExecStart=test %S = /var/lib
+ExecStart=test %C = /var/cache
+ExecStart=test %L = /var/log
+ExecStart=test %E = /etc
diff --git a/test/test-execute/exec-specifier-user.service b/test/test-execute/exec-specifier-user.service
new file mode 100644
index 0000000000..ee0301a426
--- /dev/null
+++ b/test/test-execute/exec-specifier-user.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for specifiers
+
+[Service]
+Type=oneshot
+ExecStart=sh -c 'test %t = $$XDG_RUNTIME_DIR'
+ExecStart=sh -c 'test %S = %h/.config'
+ExecStart=sh -c 'test %C = %h/.cache'
+ExecStart=sh -c 'test %L = %h/.config/log'
+ExecStart=sh -c 'test %E = %h/.config'
diff --git a/test/test-execute/exec-specifier.service b/test/test-execute/exec-specifier.service
index 2b487bae8c..512f786f83 100644
--- a/test/test-execute/exec-specifier.service
+++ b/test/test-execute/exec-specifier.service
@@ -13,11 +13,6 @@ ExecStart=test %I = ""
ExecStart=test %j = specifier
ExecStart=test %J = specifier
ExecStart=test %f = /exec/specifier
-ExecStart=test %t = /run
-ExecStart=test %S = /var/lib
-ExecStart=test %C = /var/cache
-ExecStart=test %L = /var/log
-ExecStart=test %E = /etc
ExecStart=test %T = /tmp
ExecStart=test %V = /var/tmp
ExecStart=test %d = %t/credentials/%n
diff --git a/test/test-execute/exec-specifier@.service b/test/test-execute/exec-specifier@.service
index 69e969f716..cb9d0a182a 100644
--- a/test/test-execute/exec-specifier@.service
+++ b/test/test-execute/exec-specifier@.service
@@ -13,11 +13,6 @@ ExecStart=test %I = foo/bar
ExecStart=test %j = specifier
ExecStart=test %J = specifier
ExecStart=test %f = /foo/bar
-ExecStart=test %t = /run
-ExecStart=test %S = /var/lib
-ExecStart=test %C = /var/cache
-ExecStart=test %L = /var/log
-ExecStart=test %E = /etc
ExecStart=sh -c 'test %u = $$(id -un)'
ExecStart=sh -c 'test %U = $$(id -u)'
ExecStart=sh -c 'test %g = $$(id -gn)'