diff options
Diffstat (limited to 'test')
27 files changed, 421 insertions, 140 deletions
diff --git a/test/README.testsuite b/test/README.testsuite index 22da1cd..6320e94 100644 --- a/test/README.testsuite +++ b/test/README.testsuite @@ -86,6 +86,45 @@ mkosi in the systemd reposistory, so any local modifications to the mkosi configuration (e.g. in `mkosi.local.conf`) are automatically picked up and used by the integration tests as well. +## Iterating on an integration test + +To iterate on an integration test, let's first get a shell in the integration test environment by running +the following: + +```shell +$ meson compile -C build mkosi && SYSTEMD_INTEGRATION_TESTS=1 TEST_SHELL=1 meson test -C build --no-rebuild -i TEST-01-BASIC +``` + +This will get us a shell in the integration test environment after booting the machine without running the +integration test itself. After booting, we can verify the integration test passes by running it manually, +for example with `systemctl start TEST-01-BASIC`. + +Now you can extend the test in whatever way you like to add more coverage of existing features or to add +coverage for a new feature. Once you've finished writing the logic and want to rerun the test, run the +the following on the host: + +```shell +$ mkosi -t none +``` + +This will rebuild the distribution packages without rebuilding the entire integration test image. Next, run +the following in the integration test machine: + +```shell +$ systemctl soft-reboot +$ systemctl start TEST-01-BASIC +``` + +A soft-reboot is required to make sure all the leftover state from the previous run of the test is cleaned +up by soft-rebooting into the btrfs snapshot we made before running the test. After the soft-reboot, +re-running the test will first install the new packages we just built, make a new snapshot and finally run +the test again. You can keep running the loop of `mkosi -t none`, `systemctl soft-reboot` and +`systemctl start ...` until the changes to the integration test are working. + +If you're debugging a failing integration test (running `meson test --interactive` without `TEST_SHELL`), +there's no need to run `systemctl start ...`, running `systemctl soft-reboot` on its own is sufficient to +rerun the test. + ## Running the integration tests the old fashioned way The extended testsuite only works with UID=0. It consists of the subdirectories @@ -181,7 +220,7 @@ sec). `NSPAWN_TIMEOUT=infinity`: Set a timeout for tests under systemd-nspawn (defaults to 1800 sec). -`INTERACTIVE_DEBUG=1`: Configure the machine to be more *user-friendly* for +`TEST_SHELL=1`: Configure the machine to be more *user-friendly* for interactive debugging (e.g. by setting a usable default terminal, suppressing the shutdown after the test, etc.). @@ -198,13 +237,15 @@ or Debian's default kernel path and initrd are used by default.) A script will try to find your qemu binary. If you want to specify a different one with `$QEMU_BIN`. +`TEST_SKIP`: takes a space separated list of tests to skip. + ### Debugging the qemu image -If you want to log in the testsuite virtual machine, use `INTERACTIVE_DEBUG=1` +If you want to log in the testsuite virtual machine, use `TEST_SHELL=1` and log in as root: ```shell -$ sudo make -C test/TEST-01-BASIC INTERACTIVE_DEBUG=1 run +$ sudo make -C test/TEST-01-BASIC TEST_SHELL=1 run ``` The root password is empty. @@ -248,7 +289,12 @@ is available at: https://autopkgtest.ubuntu.com/results/autopkgtest-noble-upstream-systemd-ci-systemd-ci/ -paths listed at this URL can be appended to the URL to download them. +paths listed at this URL can be appended to the URL to download them. Unfortunately +there are too many results and the web server cannot list them all at once. Fortunately +there is a workaround: copy the last line on the page, and append it to the URL, with +a '?marker=' prefix, and the web server will show the next page of results. For example: + +https://autopkgtest.ubuntu.com/results/autopkgtest-noble-upstream-systemd-ci-systemd-ci/?marker=noble/amd64/s/systemd-upstream/20240616_211635_5993a@/result.tar The 5 characters at the end of the last directory are not random, but the first 5 characters of a SHA1 hash generated based on the set of parameters given to diff --git a/test/TEST-04-JOURNAL/test.sh b/test/TEST-04-JOURNAL/test.sh index a7aa71f..01d0b66 100755 --- a/test/TEST-04-JOURNAL/test.sh +++ b/test/TEST-04-JOURNAL/test.sh @@ -19,7 +19,7 @@ test_append_files() { # Since we nuke the journal repeatedly during this test, let's redirect # stdout/stderr to the console as well to make the test a bit more debug-able. - if ! get_bool "${INTERACTIVE_DEBUG:-}"; then + if ! get_bool "${TEST_SHELL:-}"; then dropin_dir="${workspace:?}/etc/systemd/system/TEST-04-JOURNAL.service.d/" mkdir -p "$dropin_dir" printf '[Service]\nStandardOutput=journal+console\nStandardError=journal+console' >"$dropin_dir/99-stdout.conf" diff --git a/test/TEST-46-HOMED/test.sh b/test/TEST-46-HOMED/test.sh index 06034b7..973b030 100755 --- a/test/TEST-46-HOMED/test.sh +++ b/test/TEST-46-HOMED/test.sh @@ -24,7 +24,7 @@ test_append_files() { inst_binary ssh inst_binary sshd inst_binary ssh-keygen - image_install -o /usr/lib/ssh/sshd-session + image_install -o /usr/lib/ssh/sshd-session /usr/libexec/openssh/sshd-session } do_test "$@" diff --git a/test/TEST-54-CREDS/meson.build b/test/TEST-54-CREDS/meson.build index f725035..99524b9 100644 --- a/test/TEST-54-CREDS/meson.build +++ b/test/TEST-54-CREDS/meson.build @@ -17,15 +17,15 @@ integration_tests += [ files('systemd.extra-unit.my-service.service'), files('systemd.unit-dropin.my-service.service'), files('systemd.unit-dropin.my-service.service~30-named.service'), + 'smbioscredential=magicdata', + 'binarysmbioscredential=magicbinarydata', + 'sysusers.extra="u credtestuser"', + 'tmpfiles.extra="f /tmp/sourcedfromcredential - - - - tmpfilessecret"', + 'fstab.extra="injected /injected tmpfs X-mount.mkdir 0 0"', + 'getty.ttys.container=idontexist', ], 'qemu-args' : integration_test_template['qemu-args'] + [ '-fw_cfg', 'name=opt/io.systemd.credentials/myqemucredential,string=othervalue', - '-smbios', 'type=11,value=io.systemd.credential:smbioscredential=magicdata', - '-smbios', 'type=11,value=io.systemd.credential.binary:binarysmbioscredential=bWFnaWNiaW5hcnlkYXRh', - '-smbios', 'type=11,value=io.systemd.credential.binary:sysusers.extra=dSBjcmVkdGVzdHVzZXIK', - '-smbios', 'type=11,value=io.systemd.credential.binary:tmpfiles.extra=ZiAvdG1wL3NvdXJjZWRmcm9tY3JlZGVudGlhbCAtIC0gLSAtIHRtcGZpbGVzc2VjcmV0Cg==', - '-smbios', 'type=11,value=io.systemd.credential.binary:fstab.extra=aW5qZWN0ZWQgL2luamVjdGVkIHRtcGZzIFgtbW91bnQubWtkaXIgMCAwCg==', - '-smbios', 'type=11,value=io.systemd.credential:getty.ttys.container=idontexist', ], 'firmware' : 'auto', }, diff --git a/test/TEST-74-AUX-UTILS/test.sh b/test/TEST-74-AUX-UTILS/test.sh index d47a0a2..d9ae70c 100755 --- a/test/TEST-74-AUX-UTILS/test.sh +++ b/test/TEST-74-AUX-UTILS/test.sh @@ -31,7 +31,7 @@ test_append_files() { inst_binary ssh inst_binary sshd inst_binary ssh-keygen - image_install -o /usr/lib/ssh/sshd-session + image_install -o /usr/lib/ssh/sshd-session /usr/libexec/openssh/sshd-session inst_binary usermod instmods vmw_vsock_virtio_transport instmods vsock_loopback diff --git a/test/fuzz/fuzz-systemctl-parse-argv/oss-fuzz-70153 b/test/fuzz/fuzz-systemctl-parse-argv/oss-fuzz-70153 Binary files differnew file mode 100644 index 0000000..cb874fa --- /dev/null +++ b/test/fuzz/fuzz-systemctl-parse-argv/oss-fuzz-70153 diff --git a/test/integration-test-setup.sh b/test/integration-test-setup.sh new file mode 100755 index 0000000..d7c384a --- /dev/null +++ b/test/integration-test-setup.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +case "$1" in + setup) + if [[ -f "$STATE_DIRECTORY/inprogress" ]]; then + exit 0 + fi + + if [[ -d /snapshot ]]; then + echo "Run systemctl soft-reboot first to make sure the test runs within a pristine rootfs" >&2 + exit 1 + fi + + . /usr/lib/os-release + + if test -n "$(shopt -s nullglob; echo /work/build/*.{rpm,deb,pkg.tar})"; then + case "$ID" in + arch) + pacman --upgrade --needed --noconfirm /work/build/*.pkg.tar + ;; + debian|ubuntu) + apt-get install /work/build/*.deb + ;; + opensuse*) + zypper --non-interactive install --allow-unsigned-rpm /work/build/*.rpm + ;; + centos|fedora) + dnf upgrade --assumeyes --disablerepo="*" /work/build/*.rpm + ;; + *) + echo "Unknown distribution $ID" >&2 + exit 1 + esac + fi + + # TODO: Use a proper flat btrfs subvolume layout once we can create subvolumes without privileged in + # systemd-repart (see https://github.com/systemd/systemd/pull/33498). Until that's possible, we nest + # snapshots within each other. + if command -v btrfs >/dev/null && [[ "$(stat --file-system --format %T /)" == "btrfs" ]]; then + btrfs subvolume snapshot / /snapshot + fi + + touch "$STATE_DIRECTORY/inprogress" + ;; + finalize) + # If we're rebooting, the test does a reboot as part of its execution and we shouldn't remove /inprogress. + if ! [[ "$(systemctl list-jobs)" =~ reboot.target|kexec.target|soft-reboot.target ]]; then + rm -f "$STATE_DIRECTORY/inprogress" + fi + ;; + *) + echo "Unknown verb $1" >&2 + exit 1 +esac diff --git a/test/integration-test-wrapper.py b/test/integration-test-wrapper.py index d7a622a..0931043 100755 --- a/test/integration-test-wrapper.py +++ b/test/integration-test-wrapper.py @@ -61,21 +61,35 @@ def main(): print(f"TEST_NO_QEMU=1, skipping {args.name}", file=sys.stderr) exit(77) + if args.name in os.getenv("TEST_SKIP", "").split(): + print(f"Skipping {args.name} due to TEST_SKIP", file=sys.stderr) + exit(77) + keep_journal = os.getenv("TEST_SAVE_JOURNAL", "fail") + shell = bool(int(os.getenv("TEST_SHELL", "0"))) + + if shell and not sys.stderr.isatty(): + print(f"--interactive must be passed to meson test to use TEST_SHELL=1", file=sys.stderr) + exit(1) name = args.name + (f"-{i}" if (i := os.getenv("MESON_TEST_ITERATION")) else "") dropin = textwrap.dedent( """\ - [Unit] - SuccessAction=exit - SuccessActionExitStatus=123 - [Service] StandardOutput=journal+console """ ) + if not shell: + dropin += textwrap.dedent( + f""" + [Unit] + SuccessAction=exit + SuccessActionExitStatus=123 + """ + ) + if os.getenv("TEST_MATCH_SUBTEST"): dropin += textwrap.dedent( f""" @@ -92,6 +106,7 @@ def main(): """ ) + journal_file = None if not sys.stderr.isatty(): dropin += textwrap.dedent( """ @@ -102,14 +117,13 @@ def main(): journal_file = (args.meson_build_dir / (f"test/journal/{name}.journal")).absolute() journal_file.unlink(missing_ok=True) - else: + elif not shell: dropin += textwrap.dedent( """ [Unit] Wants=multi-user.target """ ) - journal_file = None cmd = [ args.mkosi, @@ -140,7 +154,7 @@ def main(): ' '.join([ 'systemd.hostname=H', f"SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/{args.name}.units:/usr/lib/systemd/tests/testdata/units:", - f"systemd.unit={args.unit}", + *([f"systemd.unit={args.unit}"] if not shell else []), 'systemd.mask=systemd-networkd-wait-online.service', *( [ @@ -154,6 +168,7 @@ def main(): ), ]), '--credential', f"journal.storage={'persistent' if sys.stderr.isatty() else args.storage}", + *(['--runtime-build-sources=no'] if not sys.stderr.isatty() else []), 'qemu' if args.vm or os.getuid() != 0 else 'boot', ] @@ -162,8 +177,8 @@ def main(): if journal_file and (keep_journal == "0" or (result.returncode in (args.exit_code, 77) and keep_journal == "fail")): journal_file.unlink(missing_ok=True) - if result.returncode in (args.exit_code, 77): - exit(0 if result.returncode == args.exit_code else 77) + if shell or result.returncode in (args.exit_code, 77): + exit(0 if shell or result.returncode == args.exit_code else 77) if journal_file: ops = [] diff --git a/test/meson.build b/test/meson.build index 173d90c..6acff37 100644 --- a/test/meson.build +++ b/test/meson.build @@ -142,9 +142,11 @@ endif ############################################################ if install_tests - install_data('run-unit-tests.py', - install_mode : 'rwxr-xr-x', - install_dir : testsdir) + foreach script : ['integration-test-setup.sh', 'run-unit-tests.py'] + install_data(script, + install_mode : 'rwxr-xr-x', + install_dir : testsdir) + endforeach endif ############################################################ diff --git a/test/test-functions b/test/test-functions index e219812..04fe20f 100644 --- a/test/test-functions +++ b/test/test-functions @@ -570,7 +570,7 @@ run_qemu() { ${TEST_MATCH_TESTCASE:+"systemd.setenv=TEST_MATCH_TESTCASE=$TEST_MATCH_TESTCASE"} ) - if ! get_bool "$INTERACTIVE_DEBUG" && ! get_bool "$TEST_SKIP_SHUTDOWN"; then + if ! get_bool "$TEST_SHELL" && ! get_bool "$TEST_SKIP_SHUTDOWN"; then kernel_params+=( "panic=1" "softlockup_panic=1" @@ -664,7 +664,7 @@ run_nspawn() { ${TEST_MATCH_TESTCASE:+"systemd.setenv=TEST_MATCH_TESTCASE=$TEST_MATCH_TESTCASE"} ) - if get_bool "$INTERACTIVE_DEBUG"; then + if get_bool "$TEST_SHELL"; then nspawn_options+=("--console=interactive") elif ! get_bool "$TEST_SKIP_SHUTDOWN"; then kernel_params+=("systemd.wants=end.service") @@ -1984,7 +1984,7 @@ install_debug_tools() { dinfo "Install debug tools" image_install -o "${DEBUGTOOLS[@]}" - if get_bool "$INTERACTIVE_DEBUG"; then + if get_bool "$TEST_SHELL"; then # Set default TERM from vt220 to linux, so at least basic key shortcuts work local getty_override="${initdir:?}/etc/systemd/system/serial-getty@.service.d" mkdir -p "$getty_override" diff --git a/test/test-sysusers/test-16.expected-group b/test/test-sysusers/test-16.expected-group new file mode 100644 index 0000000..54918e4 --- /dev/null +++ b/test/test-sysusers/test-16.expected-group @@ -0,0 +1 @@ +foo:x:SYSTEM_UGID_MAX: diff --git a/test/test-sysusers/test-16.expected-passwd b/test/test-sysusers/test-16.expected-passwd new file mode 100644 index 0000000..8823813 --- /dev/null +++ b/test/test-sysusers/test-16.expected-passwd @@ -0,0 +1,2 @@ +foo:x:SYSTEM_UGID_MAX:SYSTEM_UGID_MAX::/:NOLOGIN +bar:x:300:SYSTEM_UGID_MAX::/:NOLOGIN diff --git a/test/test-sysusers/test-16.input b/test/test-sysusers/test-16.input new file mode 100644 index 0000000..2d80d81 --- /dev/null +++ b/test/test-sysusers/test-16.input @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Test fix for https://github.com/systemd/systemd/issues/33547. +# +#Type Name ID +u foo - +u bar 300:foo diff --git a/test/test.service.in b/test/test.service.in index 790c513..48c09ba 100644 --- a/test/test.service.in +++ b/test/test.service.in @@ -7,6 +7,9 @@ Before=getty-pre.target [Service] ExecStartPre=rm -f /failed /testok +ExecStartPre=/usr/lib/systemd/tests/integration-test-setup.sh setup ExecStart=@command@ +ExecStopPost=/usr/lib/systemd/tests/integration-test-setup.sh finalize Type=oneshot MemoryAccounting=@memory-accounting@ +StateDirectory=%N diff --git a/test/units/TEST-04-JOURNAL.bsod.sh b/test/units/TEST-04-JOURNAL.bsod.sh index 83feb89..802d474 100755 --- a/test/units/TEST-04-JOURNAL.bsod.sh +++ b/test/units/TEST-04-JOURNAL.bsod.sh @@ -4,12 +4,12 @@ set -eux set -o pipefail if systemd-detect-virt -cq; then - echo "This test requires a VM, skipping the test" | tee --append /skipped + echo "This test requires a VM, skipping the test" exit 0 fi if [[ ! -x /usr/lib/systemd/systemd-bsod ]]; then - echo "systemd-bsod is not installed, skipping the test" | tee --append /skipped + echo "systemd-bsod is not installed, skipping the test" exit 0 fi diff --git a/test/units/TEST-04-JOURNAL.stopped-socket-activation.sh b/test/units/TEST-04-JOURNAL.stopped-socket-activation.sh new file mode 100755 index 0000000..083f5fa --- /dev/null +++ b/test/units/TEST-04-JOURNAL.stopped-socket-activation.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemctl stop systemd-journald.service +systemd-cat date + +# shellcheck disable=SC2016 +timeout 30 bash -xec 'until test "$(systemctl show -p SubState --value systemd-journald.service)" = "running"; do sleep 1; done' diff --git a/test/units/TEST-07-PID1.issue-31752.sh b/test/units/TEST-07-PID1.issue-31752.sh new file mode 100755 index 0000000..89ec07e --- /dev/null +++ b/test/units/TEST-07-PID1.issue-31752.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Make sure NeedDaemonReload= considers newly created drop-ins. +# Issue: https://github.com/systemd/systemd/issues/31752 + +UNIT=test-issue-31752.service + +cleanup() { + rm -rf /run/systemd/system/"$UNIT" /run/systemd/system/"$UNIT".d + systemctl daemon-reload +} + +trap cleanup EXIT + +cat > /run/systemd/system/"$UNIT" <<EOF +[Service] +ExecStart=/usr/bin/true +RemainAfterExit=yes +EOF + +systemctl daemon-reload +systemctl start "$UNIT" +assert_eq "$(systemctl show -P NeedDaemonReload "$UNIT")" no + +mkdir /run/systemd/system/"$UNIT".d +cat > /run/systemd/system/"$UNIT".d/desc.conf <<EOF +[Unit] +Description=Test NeedDaemonReload status after creating drop-in +EOF + +assert_eq "$(systemctl show -P NeedDaemonReload "$UNIT")" yes + +rm /run/systemd/system/"$UNIT".d/desc.conf + +assert_eq "$(systemctl show -P NeedDaemonReload "$UNIT")" no diff --git a/test/units/TEST-09-REBOOT.sh b/test/units/TEST-09-REBOOT.sh index 85630b6..014ea31 100755 --- a/test/units/TEST-09-REBOOT.sh +++ b/test/units/TEST-09-REBOOT.sh @@ -17,7 +17,11 @@ systemd-cat journalctl --list-boots run_subtests if [[ "$REBOOT_COUNT" -lt "$NUM_REBOOT" ]]; then + SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT=1 + export SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT systemctl_final reboot + # Now block until the reboot killing spree kills us. + exec sleep infinity elif [[ "$REBOOT_COUNT" -gt "$NUM_REBOOT" ]]; then assert_not_reached fi diff --git a/test/units/TEST-17-UDEV.credentials.sh b/test/units/TEST-17-UDEV.credentials.sh index 42d3883..2552dc6 100755 --- a/test/units/TEST-17-UDEV.credentials.sh +++ b/test/units/TEST-17-UDEV.credentials.sh @@ -5,7 +5,7 @@ set -eux set -o pipefail if [[ $(systemctl is-enabled systemd-udev-load-credentials.service) == not-found ]]; then - echo "Missing systemd-udev-load-credentials.service" >>/skipped + echo "Missing systemd-udev-load-credentials.service" exit 0 fi diff --git a/test/units/TEST-19-CGROUP.delegate.sh b/test/units/TEST-19-CGROUP.delegate.sh index 022515f..7f3a705 100755 --- a/test/units/TEST-19-CGROUP.delegate.sh +++ b/test/units/TEST-19-CGROUP.delegate.sh @@ -5,6 +5,8 @@ set -o pipefail # Test cgroup delegation in the unified hierarchy +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh @@ -13,104 +15,109 @@ if [[ "$(get_cgroup_hierarchy)" != unified ]]; then exit 0 fi -at_exit() { - set +e - userdel -r test +testcase_controllers() { + systemd-run --wait \ + --unit=test-0.service \ + --property="DynamicUser=1" \ + --property="Delegate=" \ + test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control + + systemd-run --wait \ + --unit=test-1.service \ + --property="DynamicUser=1" \ + --property="Delegate=memory pids" \ + grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers + + systemd-run --wait \ + --unit=test-2.service \ + --property="DynamicUser=1" \ + --property="Delegate=memory pids" \ + grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers + + # "io" is not among the controllers enabled by default for all units, verify that + grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers + + # Run a service with "io" enabled, and verify it works + systemd-run --wait \ + --unit=test-3.service \ + --property="IOAccounting=yes" \ + --property="Slice=system-foo-bar-baz.slice" \ + grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers + + # We want to check if "io" is removed again from the controllers + # list. However, PID 1 (rightfully) does this asynchronously. In order + # to force synchronization on this, let's start a short-lived service + # which requires PID 1 to refresh the cgroup tree, so that we can + # verify that this all works. + systemd-run --wait --unit=test-4.service true + + # And now check again, "io" should have vanished + grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers } -systemd-run --wait \ - --unit=test-0.service \ - --property="DynamicUser=1" \ - --property="Delegate=" \ - test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ - -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \ - -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control +testcase_attributes() { + # Test if delegation also works for some of the more recent attrs the kernel might or might not support + for attr in cgroup.threads memory.oom.group memory.reclaim ; do + if grep -q "$attr" /sys/kernel/cgroup/delegate ; then + systemd-run --wait \ + --unit=test-0.service \ + --property="MemoryAccounting=1" \ + --property="DynamicUser=1" \ + --property="Delegate=" \ + test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/"$attr" + fi + done +} -# Test if this also works for some of the more recent attrs the kernel might or might not support -for attr in cgroup.threads memory.oom.group memory.reclaim ; do +testcase_scope_unpriv_delegation() { + # Check that unprivileged delegation works for scopes + useradd test + trap "userdel -r test" RETURN + systemd-run --uid=test \ + --property="User=test" \ + --property="Delegate=yes" \ + --slice workload.slice \ + --unit test-workload0.scope\ + --scope \ + test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \ + -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \ + -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control +} - if grep -q "$attr" /sys/kernel/cgroup/delegate ; then +testcase_subgroup() { + # Verify that DelegateSubgroup= affects ownership correctly + unit="test-subgroup-$RANDOM.service" + systemd-run --wait \ + --unit="$unit" \ + --property="DynamicUser=1" \ + --property="Delegate=pids" \ + --property="DelegateSubgroup=foo" \ + test -w "/sys/fs/cgroup/system.slice/$unit" -a \ + -w "/sys/fs/cgroup/system.slice/$unit/foo" + + # Check that for the subgroup also attributes that aren't covered by + # regular (i.e. main cgroup) delegation ownership rules are delegated properly + if test -f /sys/fs/cgroup/cgroup.max.depth; then + unit="test-subgroup-$RANDOM.service" systemd-run --wait \ - --unit=test-0.service \ - --property="MemoryAccounting=1" \ + --unit="$unit" \ --property="DynamicUser=1" \ - --property="Delegate=" \ - test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ - -w /sys/fs/cgroup/system.slice/test-0.service/"$attr" + --property="Delegate=pids" \ + --property="DelegateSubgroup=zzz" \ + test -w "/sys/fs/cgroup/system.slice/$unit/zzz/cgroup.max.depth" fi -done - -systemd-run --wait \ - --unit=test-1.service \ - --property="DynamicUser=1" \ - --property="Delegate=memory pids" \ - grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers - -systemd-run --wait \ - --unit=test-2.service \ - --property="DynamicUser=1" \ - --property="Delegate=memory pids" \ - grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers - -# "io" is not among the controllers enabled by default for all units, verify that -grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers - -# Run a service with "io" enabled, and verify it works -systemd-run --wait \ - --unit=test-3.service \ - --property="IOAccounting=yes" \ - --property="Slice=system-foo-bar-baz.slice" \ - grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers - -# We want to check if "io" is removed again from the controllers -# list. However, PID 1 (rightfully) does this asynchronously. In order -# to force synchronization on this, let's start a short-lived service -# which requires PID 1 to refresh the cgroup tree, so that we can -# verify that this all works. -systemd-run --wait --unit=test-4.service true - -# And now check again, "io" should have vanished -grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers - -# Check that unprivileged delegation works for scopes -useradd test ||: -systemd-run --uid=test \ - --property="User=test" \ - --property="Delegate=yes" \ - --slice workload.slice \ - --unit test-workload0.scope\ - --scope \ - test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \ - -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \ - -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control - -# Verify that DelegateSubgroup= affects ownership correctly -unit="test-subgroup-$RANDOM.service" -systemd-run --wait \ - --unit="$unit" \ - --property="DynamicUser=1" \ - --property="Delegate=pids" \ - --property="DelegateSubgroup=foo" \ - test -w "/sys/fs/cgroup/system.slice/$unit" -a \ - -w "/sys/fs/cgroup/system.slice/$unit/foo" - -# Check that for the subgroup also attributes that aren't covered by -# regular (i.e. main cgroup) delegation ownership rules are delegated properly -if test -f /sys/fs/cgroup/cgroup.max.depth; then + + # Check that the invoked process itself is also in the subgroup unit="test-subgroup-$RANDOM.service" systemd-run --wait \ --unit="$unit" \ --property="DynamicUser=1" \ --property="Delegate=pids" \ - --property="DelegateSubgroup=zzz" \ - test -w "/sys/fs/cgroup/system.slice/$unit/zzz/cgroup.max.depth" -fi + --property="DelegateSubgroup=bar" \ + grep -q -x -F "0::/system.slice/$unit/bar" /proc/self/cgroup +} -# Check that the invoked process itself is also in the subgroup -unit="test-subgroup-$RANDOM.service" -systemd-run --wait \ - --unit="$unit" \ - --property="DynamicUser=1" \ - --property="Delegate=pids" \ - --property="DelegateSubgroup=bar" \ - grep -q -x -F "0::/system.slice/$unit/bar" /proc/self/cgroup +run_testcases diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh index fe410d5..89d6dcd 100755 --- a/test/units/TEST-54-CREDS.sh +++ b/test/units/TEST-54-CREDS.sh @@ -273,8 +273,11 @@ rm -rf /tmp/ts54-creds # Check that globs work as expected mkdir -p /run/credstore echo -n a >/run/credstore/test.creds.first -echo -n b >/run/credstore/test.creds.second +# Make sure that when multiple credentials of the same name are found, the first one is used (/etc/credstore +# is searched before /run/credstore). +echo -n ignored >/run/credstore/test.creds.second mkdir -p /etc/credstore +echo -n b >/etc/credstore/test.creds.second echo -n c >/etc/credstore/test.creds.third systemd-run -p "ImportCredential=test.creds.*" \ --unit=test-54-ImportCredential.service \ diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index 743baad..f8c22ab 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -358,14 +358,14 @@ label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD device: $imgs/zzz unit: sectors first-lba: 2048 -last-lba: 6389726 +last-lba: 6422494 $imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\" $imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\" $imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\" $imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\" $imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\" $imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=2A1D97E1-D0A3-46CC-A26E-ADC643926617, name=\"block-copy\" -$imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\"" +$imgs/zzz7 : start= 6291416, size= 131072, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\"" if systemd-detect-virt --quiet --container; then echo "Skipping encrypt mount tests in container." @@ -566,8 +566,8 @@ EOF output=$(sfdisk --dump "$imgs/zzz") assert_in "$imgs/zzz1 : start= 2048, size= 20480, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=39107B09-615D-48FB-BA37-C663885FCE67, name=\"esp\"" "$output" - assert_in "$imgs/zzz2 : start= 22528, size= 20480, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output" - assert_in "$imgs/zzz3 : start= 43008, size= 20480, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:60\"" "$output" + assert_in "$imgs/zzz2 : start= 22528, size= 65536, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output" + assert_in "$imgs/zzz3 : start= 88064, size= 65536, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:60\"" "$output" if systemd-detect-virt --quiet --container; then echo "Skipping second part of copy blocks tests in container." diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index cb7c8b1..b5dd4df 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -210,7 +210,7 @@ Format=ext4 CopyFiles=/tmp/dditest:/ Encrypt=tpm2 EOF - PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --definitions=/tmp/dditest --empty=create --size=50M /tmp/dditest.raw --tpm2-pcrs= + PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --definitions=/tmp/dditest --empty=create --size=80M /tmp/dditest.raw --tpm2-pcrs= DEVICE="$(systemd-dissect --attach /tmp/dditest.raw)" udevadm wait --settle --timeout=10 "$DEVICE"p1 systemd-cryptsetup attach dditest "$DEVICE"p1 - tpm2-device=auto,headless=yes diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index fd51161..10fa7a9 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -89,6 +89,11 @@ systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2- systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless systemd-cryptsetup detach pcrlock +# Ensure systemd-pcrlock not crashing on empty variant directory +mkdir -p /var/lib/pcrlock.d/123-empty.pcrlock.d +"$SD_PCRLOCK" predict --pcr="$PCRS" +rm -rf /var/lib/pcrlock.d/123-empty.pcrlock.d + # Measure something into PCR 16 (the "debug" PCR), which should make the activation fail "$SD_PCREXTEND" --pcr=16 test70 diff --git a/test/units/TEST-73-LOCALE.sh b/test/units/TEST-73-LOCALE.sh index 06c8c56..0617bd0 100755 --- a/test/units/TEST-73-LOCALE.sh +++ b/test/units/TEST-73-LOCALE.sh @@ -666,14 +666,9 @@ cat >/etc/dbus-1/system.d/systemd-localed-read-only.conf <<EOF "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy user="root"> - <allow send_member="SetLocale"/> - <allow send_member="SetVConsoleKeyboard"/> - <allow send_member="SetX11Keyboard"/> - </policy> - <policy context="default"> - <allow send_member="SetLocale"/> - <allow send_member="SetVConsoleKeyboard"/> - <allow send_member="SetX11Keyboard"/> + <allow send_destination="org.freedesktop.locale1" send_interface="org.freedesktop.locale1" send_member="SetLocale"/> + <allow send_destination="org.freedesktop.locale1" send_interface="org.freedesktop.locale1" send_member="SetVConsoleKeyboard"/> + <allow send_destination="org.freedesktop.locale1" send_interface="org.freedesktop.locale1" send_member="SetX11Keyboard"/> </policy> </busconfig> EOF diff --git a/test/units/TEST-74-AUX-UTILS.firstboot.sh b/test/units/TEST-74-AUX-UTILS.firstboot.sh index 7bab009..d9e5f59 100755 --- a/test/units/TEST-74-AUX-UTILS.firstboot.sh +++ b/test/units/TEST-74-AUX-UTILS.firstboot.sh @@ -14,6 +14,7 @@ fi at_exit() { if [[ -n "${ROOT:-}" ]]; then ls -lR "$ROOT" + grep -r . "$ROOT/etc" || : rm -fr "$ROOT" fi @@ -83,15 +84,42 @@ grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id" rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" systemd-firstboot --root="$ROOT" --root-password=foo grep -q "^root:x:0:0:" "$ROOT/etc/passwd" -grep -q "^root:" "$ROOT/etc/shadow" +grep -q "^root:[^!*]" "$ROOT/etc/shadow" rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" echo "foo" >root.passwd systemd-firstboot --root="$ROOT" --root-password-file=root.passwd grep -q "^root:x:0:0:" "$ROOT/etc/passwd" -grep -q "^root:" "$ROOT/etc/shadow" +grep -q "^root:[^!*]" "$ROOT/etc/shadow" rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" root.passwd -# Set the shell together with the password, as firstboot won't touch -# /etc/passwd if it already exists +# Make sure the root password is set if /etc/passwd and /etc/shadow exist but +# don't have a root entry. +touch "$ROOT/etc/passwd" "$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-password=foo +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:[^!*]" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +# If /etc/passwd and /etc/shadow exist, they will only be updated if the shadow +# password is !unprovisioned. +echo "root:x:0:0:root:/root:/bin/sh" >"$ROOT/etc/passwd" +echo "root:!test:::::::" >"$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-password=foo +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:!test:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +echo "root:x:0:0:root:/root:/bin/sh" >"$ROOT/etc/passwd" +echo "root:!unprovisioned:::::::" >"$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-password=foo +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:[^!*]" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-password-hashed="$ROOT_HASHED_PASSWORD1" +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-shell=/bin/fooshell +grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +grep -q "^root:!\*:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" systemd-firstboot --root="$ROOT" --root-password-hashed="$ROOT_HASHED_PASSWORD1" --root-shell=/bin/fooshell grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow" @@ -176,8 +204,9 @@ mkdir -p "$ROOT/bin" touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell" # Temporarily disable pipefail to avoid `echo: write error: Broken pipe set +o pipefail -# We can do only limited testing here, since it's all an interactive stuff, -# so --prompt and --prompt-root-password are skipped on purpose +# We can do only limited testing here, since it's all an interactive stuff, so +# --prompt is skipped on purpose and only limited --prompt-root-password +# testing can be done. echo -ne "\nfoo\nbar\n" | systemd-firstboot --root="$ROOT" --prompt-locale grep -q "LANG=foo" "$ROOT$LOCALE_PATH" grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH" @@ -193,6 +222,11 @@ echo -ne "\nEurope/Berlin\n" | systemd-firstboot --root="$ROOT" --prompt-timezon readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$" echo -ne "\nfoobar\n" | systemd-firstboot --root="$ROOT" --prompt-hostname grep -q "foobar" "$ROOT/etc/hostname" +# With no root password provided, a locked account should be created. +systemd-firstboot --root="$ROOT" --prompt-root-password </dev/null +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:!\*:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" echo -ne "\n/bin/fooshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" # Existing files should not get overwritten @@ -204,15 +238,46 @@ grep -q "^root:.*:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd" # Re-enable pipefail set -o pipefail +# --prompt-* options with credentials. Unfortunately, with --root the +# --systemd.firstboot kernel command line option is ignored, so that can't be +# --tested. +rm -fr "$ROOT" +mkdir -p "$ROOT/bin" +touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell" +systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.locale:foo \ + -p SetCredential=firstboot.locale-messages:bar \ + -p SetCredential=firstboot.keymap:foo \ + -p SetCredential=firstboot.timezone:Europe/Berlin \ + -p SetCredential=passwd.hashed-password.root:"$ROOT_HASHED_PASSWORD1" \ + -p SetCredential=passwd.shell.root:/bin/fooshell \ + systemd-firstboot \ + --root="$ROOT" \ + --prompt-locale \ + --prompt-keymap \ + --prompt-timezone \ + --prompt-root-password \ + --prompt-root-shell \ + </dev/null +grep -q "LANG=foo" "$ROOT$LOCALE_PATH" +grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH" +grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf" +readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$" +grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow" + # Assorted tests rm -fr "$ROOT" mkdir "$ROOT" systemd-firstboot --root="$ROOT" --setup-machine-id grep -E "[a-z0-9]{32}" "$ROOT/etc/machine-id" +rm -fv "$ROOT/etc/machine-id" systemd-firstboot --root="$ROOT" --delete-root-password -diff <(echo) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow") +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root::" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" (! systemd-firstboot --root="$ROOT" --root-shell=/bin/nonexistentshell) (! systemd-firstboot --root="$ROOT" --machine-id=invalidmachineid) diff --git a/test/units/TEST-82-SOFTREBOOT.sh b/test/units/TEST-82-SOFTREBOOT.sh index f86bc92..9f3d406 100755 --- a/test/units/TEST-82-SOFTREBOOT.sh +++ b/test/units/TEST-82-SOFTREBOOT.sh @@ -19,6 +19,21 @@ at_exit() { trap at_exit EXIT +# Because this test tests soft-reboot, we have to get rid of the symlink we put at +# /run/nextroot to allow rebooting into the previous snapshot if the test fails for +# the duration of the test. However, let's make sure we put the symlink back in place +# if the test fails. +if [[ -L /run/nextroot ]]; then + at_error() { + mountpoint -q /run/nextroot && umount -R /run/nextroot + rm -rf /run/nextroot + ln -sf /snapshot /run/nextroot + } + + trap at_error ERR + rm -f /run/nextroot +fi + systemd-analyze log-level debug export SYSTEMD_LOG_LEVEL=debug |