diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index ac9dc5116e..fcf5c710f1 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -638,7 +638,7 @@ int mount_all(const char *dest, r = chase(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].where); + return log_error_errno(r, "Failed to resolve %s%s: %m", strempty(dest), mount_table[k].where); /* Skip this entry if it is not a remount. */ if (mount_table[k].what) { @@ -697,7 +697,7 @@ int mount_all(const char *dest, * for those. */ r = chase(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].what); + return log_error_errno(r, "Failed to resolve %s%s: %m", strempty(dest), mount_table[k].what); } r = mount_verbose_full( diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index d898f0d4c9..b1c4f1cad8 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -752,38 +752,48 @@ int remove_veth_links(const char *primary, char **pairs) { } static int network_iface_pair_parse(const char* iftype, char ***l, const char *p, const char* ifprefix) { - _cleanup_free_ char *a = NULL, *b = NULL; int r; - r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return log_error_errno(r, "Failed to extract first word in %s parameter: %m", iftype); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Short read while reading %s parameter: %m", iftype); - if (!ifname_valid(a)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s, interface name not valid: %s", iftype, a); + for (;;) { + _cleanup_free_ char *word = NULL, *a = NULL, *b = NULL; + const char *interface; - if (isempty(p)) { - if (ifprefix) - b = strjoin(ifprefix, a); - else - b = strdup(a); - } else - b = strdup(p); - if (!b) - return log_oom(); + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to parse interface name: %m"); + if (r == 0) + break; - if (!ifname_valid(b)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s, interface name not valid: %s", iftype, b); + interface = word; + r = extract_first_word(&interface, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract first word in %s parameter: %m", iftype); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Short read while reading %s parameter: %m", iftype); + if (!ifname_valid(a)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s, interface name not valid: %s", iftype, a); - r = strv_push_pair(l, a, b); - if (r < 0) - return log_oom(); + if (isempty(interface)) { + if (ifprefix) + b = strjoin(ifprefix, a); + else + b = strdup(a); + } else + b = strdup(interface); + if (!b) + return log_oom(); + + if (!ifname_valid(b)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s, interface name not valid: %s", iftype, b); + + r = strv_consume_pair(l, TAKE_PTR(a), TAKE_PTR(b)); + if (r < 0) + return log_oom(); + } - a = b = NULL; return 0; } diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 5e21538597..61798e166c 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -90,7 +90,7 @@ static int oci_unsupported(const char *name, JsonVariant *v, JsonDispatchFlags f } static int oci_terminal(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - Settings *s = userdata; + Settings *s = ASSERT_PTR(userdata); /* If not specified, or set to true, we'll default to either an interactive or a read-only * console. If specified as false, we'll forcibly move to "pipe" mode though. */ @@ -115,6 +115,7 @@ static int oci_console_dimension(const char *name, JsonVariant *variant, JsonDis } static int oci_console_size(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + Settings *s = ASSERT_PTR(userdata); static const JsonDispatch table[] = { { "height", JSON_VARIANT_UNSIGNED, oci_console_dimension, offsetof(Settings, console_height), JSON_MANDATORY }, @@ -122,7 +123,7 @@ static int oci_console_size(const char *name, JsonVariant *v, JsonDispatchFlags {} }; - return json_dispatch(v, table, oci_unexpected, flags, userdata); + return json_dispatch(v, table, oci_unexpected, flags, s); } static int oci_absolute_path(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { @@ -186,9 +187,8 @@ static int oci_args(const char *name, JsonVariant *v, JsonDispatchFlags flags, v static int oci_rlimit_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { const char *z; - int t, *type = userdata; - - assert_se(type); + int *type = ASSERT_PTR(userdata); + int t; z = startswith(json_variant_string(v), "RLIMIT_"); if (!z) @@ -206,7 +206,8 @@ static int oci_rlimit_type(const char *name, JsonVariant *v, JsonDispatchFlags f } static int oci_rlimit_value(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - rlim_t z, *value = ASSERT_PTR(userdata); + rlim_t *value = ASSERT_PTR(userdata); + rlim_t z; if (json_variant_is_negative(v)) z = RLIM_INFINITY; @@ -227,7 +228,6 @@ static int oci_rlimit_value(const char *name, JsonVariant *v, JsonDispatchFlags } static int oci_rlimits(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - Settings *s = ASSERT_PTR(userdata); JsonVariant *e; int r; @@ -276,7 +276,8 @@ static int oci_rlimits(const char *name, JsonVariant *v, JsonDispatchFlags flags } static int oci_capability_array(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - uint64_t *mask = userdata, m = 0; + uint64_t *mask = ASSERT_PTR(userdata); + uint64_t m = 0; JsonVariant *e; JSON_VARIANT_ARRAY_FOREACH(e, v) { @@ -347,10 +348,10 @@ static int oci_oom_score_adj(const char *name, JsonVariant *v, JsonDispatchFlags } static int oci_uid_gid(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - uid_t *uid = userdata, u; + uid_t *uid = ASSERT_PTR(userdata); + uid_t u; uint64_t k; - assert(uid); assert_cc(sizeof(uid_t) == sizeof(gid_t)); k = json_variant_unsigned(v); @@ -395,6 +396,7 @@ static int oci_supplementary_gids(const char *name, JsonVariant *v, JsonDispatch } static int oci_user(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + static const JsonDispatch table[] = { { "uid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(Settings, uid), JSON_MANDATORY }, { "gid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(Settings, gid), JSON_MANDATORY }, @@ -427,7 +429,7 @@ static int oci_process(const char *name, JsonVariant *v, JsonDispatchFlags flags } static int oci_root(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - Settings *s = userdata; + Settings *s = ASSERT_PTR(userdata); int r; static const JsonDispatch table[] = { @@ -511,13 +513,15 @@ typedef struct oci_mount_data { char **options; } oci_mount_data; -static void cleanup_oci_mount_data(oci_mount_data *data) { +static void oci_mount_data_done(oci_mount_data *data) { free(data->destination); free(data->source); - strv_free(data->options); free(data->type); + strv_free(data->options); } +DEFINE_TRIVIAL_DESTRUCTOR(oci_mount_data_donep, oci_mount_data, oci_mount_data_done); + static int oci_mounts(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { Settings *s = ASSERT_PTR(userdata); JsonVariant *e; @@ -533,8 +537,8 @@ static int oci_mounts(const char *name, JsonVariant *v, JsonDispatchFlags flags, }; _cleanup_free_ char *joined_options = NULL; + _cleanup_(oci_mount_data_donep) oci_mount_data data = {}; CustomMount *m; - _cleanup_(cleanup_oci_mount_data) oci_mount_data data = {}; r = json_dispatch(e, table, oci_unexpected, flags, &data); if (r < 0) @@ -610,20 +614,27 @@ static int oci_namespace_type(const char *name, JsonVariant *v, JsonDispatchFlag return 0; } +struct namespace_data { + unsigned long type; + char *path; +}; + +static void namespace_data_done(struct namespace_data *p) { + assert(p); + + free(p->path); +} + +DEFINE_TRIVIAL_DESTRUCTOR(namespace_data_donep, struct namespace_data, namespace_data_done); + static int oci_namespaces(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - Settings *s = userdata; + Settings *s = ASSERT_PTR(userdata); unsigned long n = 0; JsonVariant *e; int r; - assert_se(s); - JSON_VARIANT_ARRAY_FOREACH(e, v) { - - struct namespace_data { - unsigned long type; - char *path; - } data = {}; + _cleanup_(namespace_data_donep) struct namespace_data data = {}; static const JsonDispatch table[] = { { "type", JSON_VARIANT_STRING, oci_namespace_type, offsetof(struct namespace_data, type), JSON_MANDATORY }, @@ -632,26 +643,19 @@ static int oci_namespaces(const char *name, JsonVariant *v, JsonDispatchFlags fl }; r = json_dispatch(e, table, oci_unexpected, flags, &data); - if (r < 0) { - free(data.path); + if (r < 0) return r; - } if (data.path) { - if (data.type != CLONE_NEWNET) { - free(data.path); + if (data.type != CLONE_NEWNET) return json_log(e, flags, SYNTHETIC_ERRNO(EOPNOTSUPP), "Specifying namespace path for non-network namespace is not supported."); - } - if (s->network_namespace_path) { - free(data.path); + if (s->network_namespace_path) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "Network namespace path specified more than once, refusing."); - } - free(s->network_namespace_path); - s->network_namespace_path = data.path; + free_and_replace(s->network_namespace_path, data.path); } if (FLAGS_SET(n, data.type)) @@ -675,10 +679,10 @@ static int oci_namespaces(const char *name, JsonVariant *v, JsonDispatchFlags fl } static int oci_uid_gid_range(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - uid_t *uid = userdata, u; + uid_t *uid = ASSERT_PTR(userdata); + uid_t u; uint64_t k; - assert(uid); assert_cc(sizeof(uid_t) == sizeof(gid_t)); /* This is very much like oci_uid_gid(), except the checks are a bit different, as this is a UID range rather @@ -777,11 +781,9 @@ static int oci_device_type(const char *name, JsonVariant *v, JsonDispatchFlags f } static int oci_device_major(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - unsigned *u = userdata; + unsigned *u = ASSERT_PTR(userdata); uint64_t k; - assert_se(u); - k = json_variant_unsigned(v); if (!DEVICE_MAJOR_VALID(k)) return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE), @@ -792,11 +794,9 @@ static int oci_device_major(const char *name, JsonVariant *v, JsonDispatchFlags } static int oci_device_minor(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - unsigned *u = userdata; + unsigned *u = ASSERT_PTR(userdata); uint64_t k; - assert_se(u); - k = json_variant_unsigned(v); if (!DEVICE_MINOR_VALID(k)) return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE), @@ -807,11 +807,10 @@ static int oci_device_minor(const char *name, JsonVariant *v, JsonDispatchFlags } static int oci_device_file_mode(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - mode_t *mode = userdata, m; + mode_t *mode = ASSERT_PTR(userdata); + mode_t m; uint64_t k; - assert(mode); - k = json_variant_unsigned(v); m = (mode_t) k; @@ -931,7 +930,7 @@ static int oci_cgroups_path(const char *name, JsonVariant *v, JsonDispatchFlags } static int oci_cgroup_device_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - mode_t *mode = userdata; + mode_t *mode = ASSERT_PTR(userdata); const char *n; assert_se(n = json_variant_string(v)); @@ -958,7 +957,7 @@ struct device_data { }; static int oci_cgroup_device_access(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - struct device_data *d = userdata; + struct device_data *d = ASSERT_PTR(userdata); bool r = false, w = false, m = false; const char *s; size_t i; @@ -984,7 +983,6 @@ static int oci_cgroup_device_access(const char *name, JsonVariant *v, JsonDispat } static int oci_cgroup_devices(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - _cleanup_free_ struct device_data *list = NULL; Settings *s = ASSERT_PTR(userdata); size_t n_list = 0, i; @@ -1187,7 +1185,7 @@ static int oci_cgroup_memory(const char *name, JsonVariant *v, JsonDispatchFlags {} }; - Settings *s = userdata; + Settings *s = ASSERT_PTR(userdata); int r; r = json_dispatch(v, table, oci_unexpected, flags, &data); @@ -1303,7 +1301,7 @@ static int oci_cgroup_cpu(const char *name, JsonVariant *v, JsonDispatchFlags fl .period = UINT64_MAX, }; - Settings *s = userdata; + Settings *s = ASSERT_PTR(userdata); int r; r = json_dispatch(v, table, oci_unexpected, flags, &data); @@ -1741,13 +1739,15 @@ struct syscall_rule { size_t n_arguments; }; -static void syscall_rule_free(struct syscall_rule *rule) { +static void syscall_rule_done(struct syscall_rule *rule) { assert(rule); strv_free(rule->names); free(rule->arguments); }; +DEFINE_TRIVIAL_DESTRUCTOR(syscall_rule_donep, struct syscall_rule, syscall_rule_done); + static int oci_seccomp_action(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { uint32_t *action = ASSERT_PTR(userdata); int r; @@ -1831,18 +1831,17 @@ static int oci_seccomp_syscalls(const char *name, JsonVariant *v, JsonDispatchFl { "args", JSON_VARIANT_ARRAY, oci_seccomp_args, 0, 0 }, {} }; - struct syscall_rule rule = { + _cleanup_(syscall_rule_donep) struct syscall_rule rule = { .action = UINT32_MAX, }; r = json_dispatch(e, table, oci_unexpected, flags, &rule); if (r < 0) - goto fail_rule; + return r; if (strv_isempty(rule.names)) { json_log(e, flags, 0, "System call name list is empty."); - r = -EINVAL; - goto fail_rule; + return -EINVAL; } STRV_FOREACH(i, rule.names) { @@ -1856,15 +1855,8 @@ static int oci_seccomp_syscalls(const char *name, JsonVariant *v, JsonDispatchFl r = seccomp_rule_add_array(sc, rule.action, nr, rule.n_arguments, rule.arguments); if (r < 0) - goto fail_rule; + return r; } - - syscall_rule_free(&rule); - continue; - - fail_rule: - syscall_rule_free(&rule); - return r; } return 0; @@ -2031,7 +2023,7 @@ static int oci_linux(const char *name, JsonVariant *v, JsonDispatchFlags flags, } static int oci_hook_timeout(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - usec_t *u = userdata; + usec_t *u = ASSERT_PTR(userdata); uint64_t k; k = json_variant_unsigned(v); diff --git a/test/TEST-13-NSPAWN/test.sh b/test/TEST-13-NSPAWN/test.sh index 47862f1dc2..2e94156432 100755 --- a/test/TEST-13-NSPAWN/test.sh +++ b/test/TEST-13-NSPAWN/test.sh @@ -30,7 +30,8 @@ test_append_files() { seq \ sleep \ stat \ - touch + touch \ + true cp /etc/os-release "$container/usr/lib/os-release" cat >"$container/sbin/init" < not found" issues in the future export PATH="/sbin:/bin:/usr/sbin:/usr/bin" -mount -t proc proc /proc -mount -t sysfs sysfs /sys +mountpoint -q /proc || mount -t proc proc /proc +mountpoint -q /sys || mount -t sysfs sysfs /sys mount -o remount,rw / DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT ASAN_RT_PATH=$ASAN_RT_PATH" @@ -961,7 +962,7 @@ install_fs_tools() { install_modules() { dinfo "Install modules" - instmods dummy vfat veth + instmods bridge dummy ipvlan macvlan vfat veth instmods loop =block instmods nls_ascii =nls instmods overlay =overlayfs @@ -2655,13 +2656,14 @@ inst_binary() { # Same as above, but we need to wrap certain libraries unconditionally # - # chown, getent, login, su, useradd, userdel - dlopen() (not only) systemd's PAM modules + # chown, getent, login, setfacl, su, useradd, userdel + # - dlopen() (not only) systemd's PAM modules # ls, mkfs.*, mksquashfs, mkswap, setpriv, stat - # - pull in nss_systemd with certain options (like ls -l) when - # nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux) + # - pull in nss_systemd with certain options (like ls -l) when + # nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux) # delv, dig - pull in nss_resolve if `resolve` is in nsswitch.conf # tar - called by machinectl in TEST-25 - bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|setpriv|stat|su|tar|useradd|userdel)$' + bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|setfacl|setpriv|stat|su|tar|useradd|userdel)$' if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$bin" =~ $bin_rx ]]; then wrap_binary=1 fi diff --git a/test/units/testsuite-13.machinectl.sh b/test/units/testsuite-13.machinectl.sh index 7f8408bd84..966b11bc7f 100755 --- a/test/units/testsuite-13.machinectl.sh +++ b/test/units/testsuite-13.machinectl.sh @@ -15,6 +15,7 @@ at_exit() { machinectl status long-running >/dev/null && machinectl kill --signal=KILL long-running mountpoint -q /var/lib/machines && timeout 10 sh -c "while ! umount /var/lib/machines; do sleep .5; done" [[ -n "${NSPAWN_FRAGMENT:-}" ]] && rm -f "/etc/systemd/nspawn/$NSPAWN_FRAGMENT" "/var/lib/machines/$NSPAWN_FRAGMENT" + rm -f /run/systemd/nspawn/*.nspawn } trap at_exit EXIT diff --git a/test/units/testsuite-13.nspawn-oci.sh b/test/units/testsuite-13.nspawn-oci.sh index de60ff6e75..6329d25f3b 100755 --- a/test/units/testsuite-13.nspawn-oci.sh +++ b/test/units/testsuite-13.nspawn-oci.sh @@ -18,6 +18,7 @@ at_exit() { [[ -n "${DEV:-}" ]] && rm -f "$DEV" [[ -n "${NETNS:-}" ]] && umount "$NETNS" && rm -f "$NETNS" [[ -n "${TMPDIR:-}" ]] && rm -fr "$TMPDIR" + rm -f /run/systemd/nspawn/*.nspawn } trap at_exit EXIT @@ -382,4 +383,84 @@ touch /opt/readonly/foo && exit 1 exit 0 EOF -systemd-nspawn --oci-bundle="$OCI" +timeout 30 systemd-nspawn --oci-bundle="$OCI" + +# Test a couple of invalid configs +INVALID_SNIPPETS=( + # Invalid object + '"foo" : { }' + '"process" : { "foo" : [ ] }' + # Non-absolute mount + '"mounts" : [ { "destination" : "foo", "type" : "tmpfs", "source" : "tmpfs" } ]' + # Invalid rlimit + '"process" : { "rlimits" : [ { "type" : "RLIMIT_FOO", "soft" : 0, "hard" : 0 } ] }' + # rlimit without RLIMIT_ prefix + '"process" : { "rlimits" : [ { "type" : "CORE", "soft" : 0, "hard" : 0 } ] }' + # Invalid env assignment + '"process" : { "env" : [ "foo" ] }' + '"process" : { "env" : [ "foo=bar", 1 ] }' + # Invalid process args + '"process" : { "args" : [ ] }' + '"process" : { "args" : [ "" ] }' + '"process" : { "args" : [ "foo", 1 ] }' + # Invalid capabilities + '"process" : { "capabilities" : { "bounding" : [ 1 ] } }' + '"process" : { "capabilities" : { "bounding" : [ "FOO_BAR" ] } }' + # Unsupported option (without JSON_PERMISSIVE) + '"linux" : { "resources" : { "cpu" : { "realtimeRuntime" : 1 } } }' + # Invalid namespace + '"linux" : { "namespaces" : [ { "type" : "foo" } ] }' + # Namespace path for a non-network namespace + '"linux" : { "namespaces" : [ { "type" : "user", "path" : "/foo/bar" } ] }' + # Duplicate namespace + '"linux" : { "namespaces" : [ { "type" : "ipc" }, { "type" : "ipc" } ] }' + # Invalid device type + '"linux" : { "devices" : [ { "type" : "foo", "path" : "/dev/foo" } ] }' + # Invalid cgroups path + '"linux" : { "cgroupsPath" : "/foo/bar/baz" }' + '"linux" : { "cgroupsPath" : "foo/bar/baz" }' + # Invalid sysctl assignments + '"linux" : { "sysctl" : { "vm.swappiness" : 60 } }' + '"linux" : { "sysctl" : { "foo..bar" : "baz" } }' + # Invalid seccomp assignments + '"linux" : { "seccomp" : { } }' + '"linux" : { "seccomp" : { "defaultAction" : 1 } }' + '"linux" : { "seccomp" : { "defaultAction" : "foo" } }' + '"linux" : { "seccomp" : { "defaultAction" : "SCMP_ACT_ALLOW", "syscalls" : [ { "action" : "SCMP_ACT_ERRNO", "names" : [ ] } ] } }' + # Invalid masked paths + '"linux" : { "maskedPaths" : [ "/foo", 1 ] }' + '"linux" : { "maskedPaths" : [ "/foo", "bar" ] }' + # Invalid read-only paths + '"linux" : { "readonlyPaths" : [ "/foo", 1 ] }' + '"linux" : { "readonlyPaths" : [ "/foo", "bar" ] }' + # Invalid hooks + '"hooks" : { "prestart" : [ { "path" : "/bin/sh", "timeout" : 0 } ] }' + # Invalid annotations + '"annotations" : { "" : "bar" }' + '"annotations" : { "foo" : 1 }' +) + +for snippet in "${INVALID_SNIPPETS[@]}"; do + : "Snippet: $snippet" + cat >"$OCI/config.json" <"$OCI/config.json" <"/run/systemd/nspawn/$container.nspawn" <"$root/entrypoint.sh" <<\EOF +#!/bin/bash -ex + +[[ "$1" == "foo bar" ]] +[[ "$2" == "bar baz" ]] + +[[ "$USER" == root ]] +[[ "$FOO" == bar ]] +[[ "$BAZ" == "hello world" ]] +[[ "$PWD" == /tmp ]] +[[ "$("/run/systemd/nspawn/$container.nspawn" <"$root/etc/passwd" + (! systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + true) + rm -f "$root/etc/passwd" + + echo "nspawn-bind-user-2:x:1000:" >"$root/etc/group" + (! systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + true) + rm -f "$root/etc/group" + + rm -fr "$root" +} + +testcase_bind_tmp_path() { # https://github.com/systemd/systemd/issues/4789 local root @@ -278,7 +514,7 @@ testcase_check_bind_tmp_path() { rm -fr "$root" /tmp/bind } -testcase_check_norbind() { +testcase_norbind() { # https://github.com/systemd/systemd/issues/13170 local root @@ -298,14 +534,14 @@ testcase_check_norbind() { rm -fr "$root" /tmp/binddir/ } -check_rootidmap_cleanup() { +rootidmap_cleanup() { local dir="${1:?}" mountpoint -q "$dir/bind" && umount "$dir/bind" rm -fr "$dir" } -testcase_check_rootidmap() { +testcase_rootidmap() { local root cmd permissions local owner=1000 @@ -315,7 +551,7 @@ testcase_check_rootidmap() { dd if=/dev/zero of=/tmp/rootidmap/ext4.img bs=4k count=2048 mkfs.ext4 /tmp/rootidmap/ext4.img mount /tmp/rootidmap/ext4.img /tmp/rootidmap/bind - trap "check_rootidmap_cleanup /tmp/rootidmap/" RETURN + trap "rootidmap_cleanup /tmp/rootidmap/" RETURN touch /tmp/rootidmap/bind/file chown -R "$owner:$owner" /tmp/rootidmap/bind @@ -342,7 +578,7 @@ testcase_check_rootidmap() { fi } -testcase_check_notification_socket() { +testcase_notification_socket() { # https://github.com/systemd/systemd/issues/4944 local root local cmd='echo a | nc -U -u -w 1 /run/host/notify' @@ -352,12 +588,14 @@ testcase_check_notification_socket() { systemd-nspawn --register=no --directory="$root" bash -x -c "$cmd" systemd-nspawn --register=no --directory="$root" -U bash -x -c "$cmd" + + rm -fr "$root" } -testcase_check_os_release() { +testcase_os_release() { local root entrypoint os_release_source - root="$(mktemp -d /var/lib/machines/testsuite-13.check-os-release.XXX)" + root="$(mktemp -d /var/lib/machines/testsuite-13.os-release.XXX)" create_dummy_container "$root" entrypoint="$root/entrypoint.sh" cat >"$entrypoint" <<\EOF @@ -395,11 +633,11 @@ EOF rm -fr "$root" } -testcase_check_machinectl_bind() { +testcase_machinectl_bind() { local service_path service_name root container_name ec local cmd='for i in $(seq 1 20); do if test -f /tmp/marker; then exit 0; fi; sleep .5; done; exit 1;' - root="$(mktemp -d /var/lib/machines/testsuite-13.check-machinectl-bind.XXX)" + root="$(mktemp -d /var/lib/machines/testsuite-13.machinectl-bind.XXX)" create_dummy_container "$root" container_name="$(basename "$root")" @@ -425,7 +663,7 @@ EOF return "$ec" } -testcase_check_selinux() { +testcase_selinux() { # Basic test coverage to avoid issues like https://github.com/systemd/systemd/issues/19976 if ! command -v selinuxenabled >/dev/null || ! selinuxenabled; then echo >&2 "SELinux is not enabled, skipping SELinux-related tests" @@ -434,7 +672,7 @@ testcase_check_selinux() { local root - root="$(mktemp -d /var/lib/machines/testsuite-13.check-selinux.XXX)" + root="$(mktemp -d /var/lib/machines/testsuite-13.selinux.XXX)" create_dummy_container "$root" chcon -R -t container_t "$root" @@ -447,17 +685,19 @@ testcase_check_selinux() { rm -fr "$root" } -testcase_check_ephemeral_config() { +testcase_ephemeral_config() { # https://github.com/systemd/systemd/issues/13297 local root container_name - root="$(mktemp -d /var/lib/machines/testsuite-13.check-ephemeral-config.XXX)" + root="$(mktemp -d /var/lib/machines/testsuite-13.ephemeral-config.XXX)" create_dummy_container "$root" - container_name="${root##*/}" + container_name="$(basename "$root")" mkdir -p /run/systemd/nspawn/ + rm -f "/etc/systemd/nspawn/$container_name.nspawn" cat >"/run/systemd/nspawn/$container_name.nspawn" <"/run/systemd/nspawn/${container:?}.nspawn" +} + create_dummy_container() { local root="${1:?}" @@ -91,4 +108,5 @@ create_dummy_container() { mkdir -p "$root" cp -a /testsuite-13-container-template/* "$root" + coverage_create_nspawn_dropin "$root" }