Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

switch-root: use MS_REC for /run, unless we are soft-rebooting #28497

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/core/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,8 @@ static int do_reexecute(
if (switch_root_dir) {
r = switch_root(/* new_root= */ switch_root_dir,
/* old_root_after= */ NULL,
/* flags= */ objective == MANAGER_SWITCH_ROOT ? SWITCH_ROOT_DESTROY_OLD_ROOT : 0);
/* flags= */ (objective == MANAGER_SWITCH_ROOT ? SWITCH_ROOT_DESTROY_OLD_ROOT : 0) |
(objective == MANAGER_SOFT_REBOOT ? SWITCH_ROOT_SKIP_RECURSIVE_RUN : 0));
if (r < 0)
log_error_errno(r, "Failed to switch root, trying to continue: %m");
}
Expand Down
23 changes: 16 additions & 7 deletions src/shared/switch-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ int switch_root(const char *new_root,
const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */
SwitchRootFlags flags) {

/* Stuff mounted below /run we don't save on soft reboot, as it might have lost its relevance, i.e.
* credentials, removable media and such, we rather want that the new boot mounts this fresh.
* But on the switch from initrd we do use MS_REC, as it is expected that mounts set up in /run
* are maintained. */
unsigned long run_mount_flags = MS_BIND|(!FLAGS_SET(flags, SWITCH_ROOT_SKIP_RECURSIVE_RUN) ? MS_REC : 0);
struct {
const char *path;
unsigned long mount_flags;
bool skip_if_run_is_rec; /* For child mounts of /run, if it's moved recursively no need to handle */
} transfer_table[] = {
{ "/dev", MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm + /dev/pts and similar */
{ "/sys", MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */
{ "/proc", MS_BIND|MS_REC }, /* Similar */
{ "/run", MS_BIND }, /* Stuff mounted below this we don't save, as it might have lost its relevance, i.e. credentials, removable media and such, we rather want that the new boot mounts this fresh */
{ SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Credentials passed into the system should survive */
{ ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Similar */
{ "/run/host", MS_BIND|MS_REC }, /* Host supplied hierarchy should also survive */
{ "/dev", MS_BIND|MS_REC, false }, /* Recursive, because we want to save the original /dev/shm + /dev/pts and similar */
{ "/sys", MS_BIND|MS_REC, false }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */
{ "/proc", MS_BIND|MS_REC, false }, /* Similar */
{ "/run", run_mount_flags, false }, /* Recursive except on soft reboot, see above */
{ SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, true }, /* Credentials passed into the system should survive */
{ ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, true }, /* Similar */
{ "/run/host", MS_BIND|MS_REC, true }, /* Host supplied hierarchy should also survive */
};

_cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF;
Expand Down Expand Up @@ -112,6 +118,9 @@ int switch_root(const char *new_root,
FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) {
_cleanup_free_ char *chased = NULL;

if (transfer->skip_if_run_is_rec && !FLAGS_SET(flags, SWITCH_ROOT_SKIP_RECURSIVE_RUN))
continue;

if (access(transfer->path, F_OK) < 0) {
log_debug_errno(errno, "Path '%s' to move to target root directory, not found, ignoring: %m", transfer->path);
continue;
Expand Down
5 changes: 3 additions & 2 deletions src/shared/switch-root.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
#include <stdbool.h>

typedef enum SwitchRootFlags {
SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */
SWITCH_ROOT_DONT_SYNC = 1 << 1, /* don't call sync() immediately before switching root */
SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */
SWITCH_ROOT_DONT_SYNC = 1 << 1, /* don't call sync() immediately before switching root */
SWITCH_ROOT_SKIP_RECURSIVE_RUN = 1 << 2, /* move /run without MS_REC */
} SwitchRootFlags;

int switch_root(const char *new_root, const char *old_root_after, SwitchRootFlags flags);
27 changes: 27 additions & 0 deletions test/TEST-01-BASIC/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,31 @@ test_append_files() {
cp -v "$TEST_UNITS_DIR"/{testsuite-01,end}.service "$TEST_UNITS_DIR/testsuite.target" "$dst"
}

# Setup a one shot service in initrd that creates a dummy bind mount under /run
# to check if the mount persists though the initrd transition. The "check" part
# is in the respective testsuite-01.sh script.
#
# See: https://github.com/systemd/systemd/issues/28452
run_qemu_hook() {
local extra="$WORKDIR/initrd.extra"

mkdir -m 755 "$extra"
mkdir -m 755 "$extra/etc" "$extra/etc/systemd" "$extra/etc/systemd/system" "$extra/etc/systemd/system/initrd.target.wants"

cat >"$extra/etc/systemd/system/initrd-run-mount.service" <<EOF
[Unit]
Description=Create a mount in /run that should survive the transition from initrd

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=sh -xec "mkdir /run/initrd-mount-source /run/initrd-mount-target; mount -v --bind /run/initrd-mount-source /run/initrd-mount-target"
EOF
ln -svrf "$extra/etc/systemd/system/initrd-run-mount.service" "$extra/etc/systemd/system/initrd.target.wants/initrd-run-mount.service"

(cd "$extra" && find . | cpio -o -H newc -R root:root > "$extra.cpio")

INITRD_EXTRA="$extra.cpio"
}

do_test "$@"
5 changes: 5 additions & 0 deletions test/units/testsuite-01.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ if systemd-detect-virt -q --container; then
test ! -e /run/systemd/container
cp -afv /tmp/container /run/systemd/container
else
# We should've created a mount under /run in initrd (see the other half of the test)
# that should've survived the transition from initrd to the real system
test -d /run/initrd-mount-target
mountpoint /run/initrd-mount-target

# We bring the loopback netdev up only during a full setup, so it should
# not get brought back up during reexec if we disable it beforehand
[[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]
Expand Down