From fae8941196354274b91081d1237bd293a5aaa8f3 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Fri, 30 Aug 2024 05:53:10 -0600 Subject: [PATCH] tests: Add mount propagation test This tests the current behavior of making /sysroot a private mount so that submounts on /var do not propagate back to /sysroot. It also shows how submounts of /sysroot do not propagate into separate mount namespaces for the same reason. --- .../kolainst/destructive/mount-propagation.sh | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 tests/kolainst/destructive/mount-propagation.sh diff --git a/tests/kolainst/destructive/mount-propagation.sh b/tests/kolainst/destructive/mount-propagation.sh new file mode 100755 index 0000000000..b7fc87e696 --- /dev/null +++ b/tests/kolainst/destructive/mount-propagation.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# https://bugzilla.redhat.com/show_bug.cgi?id=1498281 +set -xeuo pipefail + +. ${KOLA_EXT_DATA}/libinsttest.sh + +require_writable_sysroot + +get_mount() { + local target=${1:?No target specified} + local pid=${2:?No PID specified} + + # findmnt always looks at /proc/self/mountinfo, so we have to first enter the + # mount namespace of the desired process. + nsenter --target "${pid}" --mount -- \ + findmnt --json --list --output +PROPAGATION,OPT-FIELDS \ + | jq ".filesystems[] | select(.target == \"${target}\")" +} + +assert_has_mount() { + local target=${1:?No target specified} + local pid=${2:-$$} + local mount + + mount=$(get_mount "${target}" "${pid}") + if [ -n "${mount}" ]; then + echo -e "Process ${pid} has mount '${target}':\n${mount}" + else + cat "/proc/${pid}/mountinfo" >&2 + fatal "Mount '${target}' not found in process ${pid}" + fi +} + +assert_not_has_mount() { + local target=${1:?No target specified} + local pid=${2:-$$} + local mount + + mount=$(get_mount "${target}" "${pid}") + if [ -n "${mount}" ]; then + cat "/proc/${pid}/mountinfo" >&2 + fatal "Mount '${target}' found in process ${pid}" + else + echo "Process ${pid} does not have mount '${target}'" + fi +} + +test_mounts() { + local stateroot + + echo "Root namespace mountinfo:" + cat "/proc/$$/mountinfo" + + echo "Sub namespace mountinfo:" + cat "/proc/${ns_pid}/mountinfo" + + # Make sure the 2 PIDs are really in different mount namespaces. + root_ns=$(readlink "/proc/$$/ns/mnt") + sub_ns=$(readlink "/proc/${ns_pid}/ns/mnt") + assert_not_streq "${root_ns}" "${sub_ns}" + + stateroot=$(rpmostree_query_json '.deployments[0].osname') + + # Check the mounts exist in the root namespace and the /var/foo mount has not + # propagated back to /sysroot. + assert_has_mount /var/foo + assert_has_mount /sysroot/bar + assert_not_has_mount "/sysroot/ostree/deploy/${stateroot}/var/foo" + + # Repeat with the sub mount namespace. Since /sysroot is marked private, + # /sysroot/bar will not be propagated into it. + assert_has_mount /var/foo "${ns_pid}" + assert_not_has_mount /sysroot/bar "${ns_pid}" + assert_not_has_mount "/sysroot/ostree/deploy/${stateroot}/var/foo" "${ns_pid}" +} + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + mkdir -p /var/foo /sysroot/bar + + # Create a process in a separate mount namespace to see if the mounts + # propagate into it correctly. + unshare -m --propagation unchanged -- sleep infinity & + ns_pid=$! + + mount -t tmpfs foo /var/foo + mount -t tmpfs bar /sysroot/bar + + test_mounts + + # Now setup for the same test but with the mounts made early via fstab. + cat >> /etc/fstab <<"EOF" +foo /var/foo tmpfs defaults 0 0 +bar /sysroot/bar tmpfs defaults 0 0 +EOF + + # We want to start a process in a separate namespace after ostree-remount + # has completed but before systemd starts the fstab generated mount units. + cat > /etc/systemd/system/test-mounts.service <<"EOF" +[Unit] +DefaultDependencies=no +After=ostree-remount.service +Before=var-foo.mount sysroot-bar.mount +RequiresMountsFor=/var /sysroot +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +Type=exec +ExecStart=/usr/bin/sleep infinity +ProtectSystem=strict + +[Install] +WantedBy=local-fs.target +EOF + systemctl enable test-mounts.service + + /tmp/autopkgtest-reboot 2 + ;; + 2) + # Check that the test service is running and get its PID. + ns_state=$(systemctl show -P ActiveState test-mounts.service) + assert_streq "${ns_state}" active + ns_pid=$(systemctl show -P MainPID test-mounts.service) + + # Make sure that test-mounts.service started after ostree-remount.service + # but before /var/foo and /sysroot/bar were mounted so that we can see if + # the mounts were propagated into its mount namespace. + remount_finished=$(journalctl -o json -g Finished -u ostree-remount.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + test_starting=$(journalctl -o json -g Starting -u test-mounts.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + test_started=$(journalctl -o json -g Started -u test-mounts.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + foo_mounting=$(journalctl -o json -g Mounting -u var-foo.mount | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + bar_mounting=$(journalctl -o json -g Mounting -u sysroot-bar.mount | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + test "${remount_finished}" -lt "${test_starting}" + test "${test_started}" -lt "${foo_mounting}" + test "${test_started}" -lt "${bar_mounting}" + + test_mounts + ;; + *) fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}" ;; +esac