diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index d58defcc42..49b72131d8 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -234,7 +234,7 @@ static int parse_one_option(const char *option) { return log_oom(); } else if ((val = startswith(option, "header="))) { - if (!STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2, CRYPT_TCRYPT)) + if (!arg_type || !STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2, CRYPT_TCRYPT)) arg_type = ANY_LUKS; if (!path_is_absolute(val)) diff --git a/test/TEST-21-DFUZZER/test.sh b/test/TEST-21-DFUZZER/test.sh index eb0a6549e0..4b1919b86e 100755 --- a/test/TEST-21-DFUZZER/test.sh +++ b/test/TEST-21-DFUZZER/test.sh @@ -18,7 +18,7 @@ if ! get_bool "${TEST_PREFER_NSPAWN:=}"; then TEST_NO_NSPAWN=1 fi -command -v dfuzzer >/dev/null || exit 0 +test_require_bin dfuzzer if ! get_bool "$IS_BUILT_WITH_ASAN"; then echo "systemd is built without ASan, skipping..." diff --git a/test/TEST-24-CRYPTSETUP/test.sh b/test/TEST-24-CRYPTSETUP/test.sh index 87ffaea453..d0ec63d870 100755 --- a/test/TEST-24-CRYPTSETUP/test.sh +++ b/test/TEST-24-CRYPTSETUP/test.sh @@ -4,6 +4,7 @@ set -e TEST_DESCRIPTION="cryptsetup systemd setup" IMAGE_NAME="cryptsetup" +IMAGE_ADDITIONAL_DATA_SIZE=100 TEST_NO_NSPAWN=1 TEST_FORCE_NEWIMAGE=1 @@ -12,28 +13,29 @@ TEST_FORCE_NEWIMAGE=1 PART_UUID="deadbeef-dead-dead-beef-000000000000" DM_NAME="test24_varcrypt" -KERNEL_APPEND+=" rd.luks=1 luks.name=$PART_UUID=$DM_NAME luks.key=$PART_UUID=/keyfile:LABEL=varcrypt_keydev" +KERNEL_OPTIONS=( + "rd.luks=1" + "luks.name=$PART_UUID=$DM_NAME" + "luks.key=$PART_UUID=/keyfile:LABEL=varcrypt_keydev" + "luks.options=$PART_UUID=x-initrd.attach" +) +KERNEL_APPEND+=" ${KERNEL_OPTIONS[*]}" QEMU_OPTIONS+=" -drive format=raw,cache=unsafe,file=${STATEDIR:?}/keydev.img" check_result_qemu() { - local ret=1 + local ret mount_initdir - [[ -e "${initdir:?}/testok" ]] && ret=0 - [[ -f "$initdir/failed" ]] && cp -a "$initdir/failed" "${TESTDIR:?}" cryptsetup luksOpen "${LOOPDEV:?}p2" "${DM_NAME:?}" <"$TESTDIR/keyfile" mount "/dev/mapper/$DM_NAME" "$initdir/var" - save_journal "$initdir/var/log/journal" - check_coverage_reports "${initdir:?}" || ret=5 + + check_result_common "${initdir:?}" && ret=0 || ret=$? + _umount_dir "$initdir/var" _umount_dir "$initdir" cryptsetup luksClose "/dev/mapper/$DM_NAME" - [[ -f "$TESTDIR/failed" ]] && cat "$TESTDIR/failed" - echo "${JOURNAL_LIST:-No journals were saved}" - - test -s "$TESTDIR/failed" && ret=1 return $ret } @@ -61,6 +63,7 @@ test_create_image() { mkdir -p "$STATEDIR/keydev" mount "$STATEDIR/keydev.img" "$STATEDIR/keydev" echo -n test >"$STATEDIR/keydev/keyfile" + sync "$STATEDIR/keydev" umount "$STATEDIR/keydev" cat >>"$initdir/etc/fstab" </dev/null 2>&1 || exit 0 +test_require_bin mksquashfs test_append_files() { inst_binary unsquashfs diff --git a/test/TEST-46-HOMED/test.sh b/test/TEST-46-HOMED/test.sh index 0daf211103..3bf3891380 100755 --- a/test/TEST-46-HOMED/test.sh +++ b/test/TEST-46-HOMED/test.sh @@ -11,10 +11,7 @@ TEST_DESCRIPTION="testing homed" . "${TEST_BASE_DIR:?}/test-functions" get_bool "${NO_BUILD:-}" && HOMECTL_BIN="homectl" || HOMECTL_BIN="${BUILD_DIR:?}/homectl" -if ! command -v "$HOMECTL_BIN" >/dev/null; then - echo "Built without systemd-homed, skipping the test" - exit 0 -fi +test_require_bin "$HOMECTL_BIN" # Need loop devices for mounting images test_append_files() { diff --git a/test/TEST-50-DISSECT/test.sh b/test/TEST-50-DISSECT/test.sh index 2d44ca2b6e..bcc81749af 100755 --- a/test/TEST-50-DISSECT/test.sh +++ b/test/TEST-50-DISSECT/test.sh @@ -12,9 +12,7 @@ TEST_INSTALL_VERITY_MINIMAL=1 # shellcheck source=test/test-functions . "${TEST_BASE_DIR:?}/test-functions" -command -v mksquashfs >/dev/null 2>&1 || exit 0 -command -v veritysetup >/dev/null 2>&1 || exit 0 -command -v sfdisk >/dev/null 2>&1 || exit 0 +test_require_bin mksquashfs veritysetup sfdisk test_append_files() { instmods squashfs =squashfs diff --git a/test/TEST-58-REPART/test.sh b/test/TEST-58-REPART/test.sh index 7549de568c..d6e6ac0b22 100755 --- a/test/TEST-58-REPART/test.sh +++ b/test/TEST-58-REPART/test.sh @@ -4,6 +4,7 @@ set -e TEST_DESCRIPTION="test systemd-repart" IMAGE_NAME="repart" +IMAGE_ADDITIONAL_ROOT_SIZE=1000 TEST_FORCE_NEWIMAGE=1 # shellcheck source=test/test-functions diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh index f448a4a5f1..f3ac1399fc 100755 --- a/test/TEST-70-TPM2/test.sh +++ b/test/TEST-70-TPM2/test.sh @@ -5,41 +5,23 @@ set -e TEST_DESCRIPTION="cryptenroll/cryptsetup with TPM2 devices" IMAGE_NAME="tpm2" TEST_NO_NSPAWN=1 +TEST_SETUP_SWTPM=1 TEST_REQUIRE_INSTALL_TESTS=0 # shellcheck source=test/test-functions . "${TEST_BASE_DIR:?}/test-functions" -command -v swtpm >/dev/null 2>&1 || exit 0 -command -v tpm2_pcrextend >/dev/null 2>&1 || exit 0 +test_require_bin swtpm tpm2_pcrextend test_append_files() { - local workspace="${1:?}" + local workspace="${1:?}" - instmods tpm tpm_tis tpm_ibmvtpm - install_dmevent - generate_module_dependencies - inst_binary tpm2_pcrextend - inst_binary tpm2_pcrread - inst_binary openssl + instmods tpm tpm_tis tpm_ibmvtpm + install_dmevent + generate_module_dependencies + inst_binary tpm2_pcrextend + inst_binary tpm2_pcrread + inst_binary openssl } -TEST_70_TPM_DEVICE="tpm-tis" -if [[ "$(uname -m)" == "ppc64le" ]]; then - # tpm-spapr support was introduced in qemu 5.0.0. Skip test for old qemu versions. - qemu_min_version "5.0.0" || exit 0 - TEST_70_TPM_DEVICE="tpm-spapr" -fi - -TEST_70_at_exit() { - [[ -n "${TEST_70_SWTPM_PID:-}" ]] && kill "$TEST_70_SWTPM_PID" &>/dev/null - [[ -n "${TEST_70_TPM_STATE:-}" ]] && rm -rf "$TEST_70_TPM_STATE" -} - -TEST_70_TPM_STATE="$(mktemp -d)" -swtpm socket --tpm2 --tpmstate dir="$TEST_70_TPM_STATE" --ctrl type=unixio,path="$TEST_70_TPM_STATE/sock" & -TEST_70_SWTPM_PID=$! -add_at_exit_handler TEST_70_at_exit -QEMU_OPTIONS+=" -chardev socket,id=chrtpm,path=$TEST_70_TPM_STATE/sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device $TEST_70_TPM_DEVICE,tpmdev=tpm0" - do_test "$@" diff --git a/test/TEST-75-RESOLVED/test.sh b/test/TEST-75-RESOLVED/test.sh index 6c63db65fb..cbb9e3dbbf 100755 --- a/test/TEST-75-RESOLVED/test.sh +++ b/test/TEST-75-RESOLVED/test.sh @@ -9,10 +9,7 @@ NSPAWN_ARGUMENTS="--private-network" # shellcheck source=test/test-functions . "${TEST_BASE_DIR:?}/test-functions" -if ! command -v knotd >/dev/null; then - echo "This test requires Knot DNS server, skipping..." - exit 0 -fi +test_require_bin knotd # We need at least Knot 3.0 which support (among others) the ds-push directive if ! knotc -c "${TEST_BASE_DIR:?}/knot-data/knot.conf" conf-check; then diff --git a/test/test-functions b/test/test-functions index 4df6184d00..8437e6b687 100644 --- a/test/test-functions +++ b/test/test-functions @@ -69,21 +69,14 @@ _at_exit() { # Run the EXIT handlers in reverse order for ((i = ${#_AT_EXIT_HANDLERS[@]} - 1; i >= 0; i--)); do ddebug "Running EXIT handler '${_AT_EXIT_HANDLERS[$i]}'" - "${_AT_EXIT_HANDLERS[$i]}" + eval "${_AT_EXIT_HANDLERS[$i]}" done } trap _at_exit EXIT add_at_exit_handler() { - local handler="${1?}" - - if [[ "$(type -t "$handler")" != "function" ]]; then - dfatal "'$handler' is not a function" - exit 1 - fi - - _AT_EXIT_HANDLERS+=("$handler") + _AT_EXIT_HANDLERS+=("${1:?}") } # Decide if we can (and want to) run qemu with KVM acceleration. @@ -333,6 +326,17 @@ if get_bool "$IS_BUILT_WITH_ASAN"; then echo "Detected ASan RT '$ASAN_RT_NAME' located at '$ASAN_RT_PATH'" fi +test_require_bin() { + local bin + + for bin in "$@"; do + if ! command -v "$bin" >/dev/null; then + echo "Required binary $bin not available, skipping the test" + exit 0 + fi + done +} + find_qemu_bin() { QEMU_BIN="${QEMU_BIN:-""}" # SUSE and Red Hat call the binary qemu-kvm. Debian and Gentoo call it kvm. @@ -364,6 +368,48 @@ find_qemu_bin() { fi } +qemu_setup_swtpm_socket() { + local pid state_dir tpm_device + + if ! tpm_device="$(qemu_get_tpm_device)"; then + dinfo "Found QEMU version is too old for TPM2 on ppc64le" + exit 0 + fi + + state_dir="$(mktemp -d)" + swtpm socket --tpm2 --tpmstate dir="$state_dir" --ctrl type=unixio,path="$state_dir/sock" & + pid=$! + if ! kill -0 "$pid"; then + echo >&2 "Failed to setup swtpm socket" + return 1 + fi + + dinfo "Started swtpm as PID $pid with state dir $state_dir" + + add_at_exit_handler "kill -TERM $pid 2>/dev/null; rm -rf '$state_dir'" + + QEMU_OPTIONS+=" -chardev socket,id=chrtpm,path=$state_dir/sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device $tpm_device,tpmdev=tpm0" + dinfo "Configured emulated TPM2 device $tpm_device" + + return 0 +} + +qemu_get_tpm_device() { + local tpm_device="tpm-tis" + + if [[ "$(uname -m)" == "ppc64le" ]]; then + # tpm-spapr support was introduced in qemu 5.0.0 + if ! qemu_min_version "5.0.0"; then + return 1 + fi + + tpm_device="tpm-spapr" + fi + + echo "$tpm_device" + return 0 +} + # Compares argument #1=X.Y.Z (X&Y&Z = numeric) to the version of the installed qemu # returns 0 if newer or equal # returns 1 if older @@ -454,6 +500,10 @@ run_qemu() { find_qemu_bin || return 1 + if get_bool "${TEST_SETUP_SWTPM:-}"; then + qemu_setup_swtpm_socket || return 1 + fi + # Umount initdir to avoid concurrent access to the filesystem _umount_dir "$initdir" @@ -1373,7 +1423,7 @@ cleanup_loopdev() { add_at_exit_handler cleanup_loopdev create_empty_image() { - if [ -z "${IMAGE_NAME:=}" ]; then + if [[ -z "${IMAGE_NAME:=}" ]]; then echo "create_empty_image: \$IMAGE_NAME not set" exit 1 fi @@ -1383,21 +1433,24 @@ create_empty_image() { local data_size=100 if ! get_bool "$NO_BUILD"; then if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then - root_size=$((root_size+=200)) + root_size=$((root_size + 200)) fi if meson configure "${BUILD_DIR:?}" | grep 'link-.*-shared' | awk '{ print $2 }' | grep -q 'false'; then - root_size=$((root_size+=200)) + root_size=$((root_size + 200)) fi if get_bool "$IS_BUILT_WITH_COVERAGE"; then - root_size=$((root_size+=250)) + root_size=$((root_size + 250)) fi if get_bool "$IS_BUILT_WITH_ASAN"; then root_size=$((root_size * 2)) fi fi - if [ "$IMAGE_NAME" = "repart" ]; then - root_size=$((root_size+=1000)) + if [[ "${IMAGE_ADDITIONAL_ROOT_SIZE:-0}" -gt 0 ]]; then + root_size=$((root_size + IMAGE_ADDITIONAL_ROOT_SIZE)) + fi + if [[ "${IMAGE_ADDITIONAL_DATA_SIZE:-0}" -gt 0 ]]; then + data_size=$((data_size + IMAGE_ADDITIONAL_DATA_SIZE)) fi echo "Setting up ${IMAGE_PUBLIC:?} (${root_size} MB)" @@ -1406,8 +1459,8 @@ create_empty_image() { # Create the blank file to use as a root filesystem truncate -s "${root_size}M" "$IMAGE_PUBLIC" - LOOPDEV=$(losetup --show -P -f "$IMAGE_PUBLIC") - [ -b "$LOOPDEV" ] || return 1 + LOOPDEV="$(losetup --show -P -f "$IMAGE_PUBLIC")" + [[ -b "$LOOPDEV" ]] || return 1 # Create two partitions - a root one and a data one (utilized by some tests) sfdisk "$LOOPDEV" </failed; echo OK >/testok' +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh Type=oneshot diff --git a/test/units/testsuite-24.sh b/test/units/testsuite-24.sh new file mode 100755 index 0000000000..391dcf999d --- /dev/null +++ b/test/units/testsuite-24.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# TODO: +# - /proc/cmdline parsing +# - figure out token support (apart from TPM2, as that's covered by TEST-70-TPM2) +# - this might help https://www.qemu.org/docs/master/system/devices/ccid.html +# - expect + interactive auth? + +# We set up an encrypted /var partition which should get mounted automatically +# on boot +mountpoint /var + +systemctl --state=failed --no-legend --no-pager | tee /failed +if [[ -s /failed ]]; then + echo >&2 "Found units in failed state" + exit 1 +fi + +at_exit() { + set +e + + mountpoint -q /proc/cmdline && umount /proc/cmdline + rm -f /etc/crypttab + [[ -e /tmp/crypttab.bak ]] && cp -fv /tmp/crypttab.bak /etc/crypttab + [[ -n "${STORE_LOOP:-}" ]] && losetup -d "$STORE_LOOP" + [[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR" + + systemctl daemon-reload +} + +trap at_exit EXIT + +cryptsetup_start_and_check() { + local expect_fail=0 + local ec volume unit + + if [[ "${1:?}" == "-f" ]]; then + expect_fail=1 + shift + fi + + for volume in "$@"; do + unit="systemd-cryptsetup@$volume.service" + + # The unit existence check should always pass + [[ "$(systemctl show -P LoadState "$unit")" == loaded ]] + systemctl list-unit-files "$unit" + + systemctl start "$unit" && ec=0 || ec=$? + if [[ "$expect_fail" -ne 0 ]]; then + if [[ "$ec" -eq 0 ]]; then + echo >&2 "Unexpected pass when starting $unit" + return 1 + fi + + return 0 + fi + + if [[ "$ec" -ne 0 ]]; then + echo >&2 "Unexpected fail when starting $unit" + return 1 + fi + + systemctl status "$unit" + test -e "/dev/mapper/$volume" + systemctl stop "$unit" + test ! -e "/dev/mapper/$volume" + done + + return 0 +} + +# Note: some stuff (especially TPM-related) is already tested by TEST-70-TPM2, +# so focus more on other areas instead + +# Use a common workdir to make the cleanup easier +WORKDIR="$(mktemp -d)" + +# Prepare a couple of LUKS2-encrypted disk images +# +# 1) Image with an empty password +IMAGE_EMPTY="$WORKDIR/empty.img)" +IMAGE_EMPTY_KEYFILE="$WORKDIR/empty.keyfile" +IMAGE_EMPTY_KEYFILE_ERASE="$WORKDIR/empty-erase.keyfile" +IMAGE_EMPTY_KEYFILE_ERASE_FAIL="$WORKDIR/empty-erase-fail.keyfile)" +truncate -s 32M "$IMAGE_EMPTY" +echo -n passphrase >"$IMAGE_EMPTY_KEYFILE" +chmod 0600 "$IMAGE_EMPTY_KEYFILE" +cryptsetup luksFormat --batch-mode \ + --pbkdf pbkdf2 \ + --pbkdf-force-iterations 1000 \ + --use-urandom \ + "$IMAGE_EMPTY" "$IMAGE_EMPTY_KEYFILE" +PASSWORD=passphrase NEWPASSWORD="" systemd-cryptenroll --password "$IMAGE_EMPTY" +# Duplicate the key file to test keyfile-erase as well +cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE" +# The key should get erased even on a failed attempt, so test that too +cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" + +# 2) Image with a detached header and a key file offset + size +IMAGE_DETACHED="$WORKDIR/detached.img" +IMAGE_DETACHED_KEYFILE="$WORKDIR/detached.keyfile" +IMAGE_DETACHED_KEYFILE2="$WORKDIR/detached.keyfile2" +IMAGE_DETACHED_HEADER="$WORKDIR/detached.header" +truncate -s 32M "$IMAGE_DETACHED" +dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE" count=64 bs=1 +dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE2" count=32 bs=1 +chmod 0600 "$IMAGE_DETACHED_KEYFILE" "$IMAGE_DETACHED_KEYFILE2" +cryptsetup luksFormat --batch-mode \ + --pbkdf pbkdf2 \ + --pbkdf-force-iterations 1000 \ + --use-urandom \ + --header "$IMAGE_DETACHED_HEADER" \ + --keyfile-offset 32 \ + --keyfile-size 16 \ + "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE" +# Also, add a second key file to key slot 8 +# Note: --key-slot= behaves as --new-key-slot= when used alone for backwards compatibility +cryptsetup luksAddKey --batch-mode \ + --header "$IMAGE_DETACHED_HEADER" \ + --key-file "$IMAGE_DETACHED_KEYFILE" \ + --keyfile-offset 32 \ + --keyfile-size 16 \ + --key-slot 8 \ + "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE2" + +# Prepare a couple of dummy devices we'll store a copy of the detached header +# and one of the keys on to test if systemd-cryptsetup correctly mounts them +# when necessary +STORE_IMAGE="$WORKDIR/store.img" +truncate -s 64M "$STORE_IMAGE" +STORE_LOOP="$(losetup --show --find --partscan "$STORE_IMAGE")" +sfdisk "$STORE_LOOP" </etc/crypttab </tmp/cmdline.tmp +mount --bind /tmp/cmdline.tmp /proc/cmdline +# Run the systemd-cryptsetup-generator once explicitly, to collect coverage, +# as during daemon-reload we run generators in a sandbox +mkdir -p /tmp/systemd-cryptsetup-generator.out +/usr/lib/systemd/system-generators/systemd-cryptsetup-generator /tmp/systemd-cryptsetup-generator.out/ +systemctl daemon-reload +systemctl list-unit-files "systemd-cryptsetup@*" + +cryptsetup_start_and_check empty_key +test -e "$IMAGE_EMPTY_KEYFILE_ERASE" +cryptsetup_start_and_check empty_key_erase +test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE" +test -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" +cryptsetup_start_and_check -f empty_key_erase_fail +test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" +cryptsetup_start_and_check -f empty_fail{0..1} +cryptsetup_start_and_check empty{0..1} +# First, check if we correctly fail without any key +cryptsetup_start_and_check -f empty_nokey +# And now provide the key via /{etc,run}/cryptsetup-keys.d/ +mkdir -p /run/cryptsetup-keys.d +cp "$IMAGE_EMPTY_KEYFILE" /run/cryptsetup-keys.d/empty_nokey.key +cryptsetup_start_and_check empty_nokey + +cryptsetup_start_and_check detached +cryptsetup_start_and_check detached_store{0..2} +cryptsetup_start_and_check -f detached_fail{0..4} +cryptsetup_start_and_check detached_slot{0..1} +cryptsetup_start_and_check -f detached_slot_fail + +echo OK >/testok