Skip to content

Can't pivot_root inside the fake machine, because the root is the initial rootfs #273

Description

@smcv

To reproduce

Have fakemachine, bubblewrap and hostname installed. Have this script in repro.sh:

#!/bin/sh
set -eu
bwrap --dev-bind / / hostname

Make it executable, and run it under fakemachine:

$ chmod +x repro.sh
$ fakemachine -v "$(pwd)" "$(pwd)/repro.sh"

Expected result

hostname(1) is run inside the fake machine, and outputs fakemachine.

Actual result

$ fakemachine -v "$(pwd)" "$(pwd)/repro.sh"
Running /home/smcv/tmp/fm/repro.sh using kvm backend
bwrap: pivot_root: Invalid argument

pivot_root(2) documents no fewer than six reasons why it can fail with EINVAL. After some head scratching, I isolated this to: "The current root is on the rootfs (initial ramfs) mount".

(This simplified reproducer runs https://github.com/containers/bubblewrap, but what I actually wanted to do was to run podman, and that fails similarly.)

Workaround

Have this in make-root-pivotable.sh:

#!/bin/sh
# Copyright 2026 Collabora Ltd
# SPDX-License-Identifier: MIT

set -eu
#set -x

# fakemachine runs our payload code with the initramfs as the root,
# and "The rootfs (initial ramfs) cannot be pivot_root()ed" — pivot_root(2).
# To work around this, we duplicate the entire fakemachine filesystem,
# except for the rootfs itself and /mnt, into /mnt.
install -d /mnt
mount -t tmpfs -o mode=0755 workaround /mnt

for member in /*; do
    if [ "$member" = /mnt ]; then
        install -d "/mnt/$member"
    elif [ -L "$member" ]; then
        ln -fns -- "$(readlink -- "$member")" "/mnt/$member"
    elif [ -d "$member" ]; then
        install -d "/mnt/$member"
        mount --rbind -- "$member" "/mnt/$member"
    else
        : > "/mnt/$member"
        mount --rbind -- "$member" "/mnt/$member"
    fi
done

exec unshare -m -R /mnt -w "$(pwd)" -- "$@"

Then run

$ chmod +x repro.sh make-root-pivotable.sh
fakemachine -v "$(pwd)" "$(pwd)/make-root-pivotable.sh" "$(pwd)/repro.sh"

This creates a new filesystem hierarchy in /mnt, creates a new mount namespace, pivots the root into it, and then runs the payload command that I originally wanted to run inside that namespace. Quite a tower of abstractions, but it works.

Possible solution (untested)

It would be great if fakemachine could do this internally, or do something equivalent but more efficient internally. Perhaps instead of running directly from the initramfs, it could behave a little bit more like a distro initramfs:

  1. create a directory like /root and mount a tmpfs on it
  2. copy or bind-mount the various /etc files into /root
  3. mount static volumes into /root instead of the rootfs
  4. mount hard-coded filesystems like /proc and /sys into /root, too
  5. in /init, instead of exec /lib/systemd/systemd, behave more like the examples in pivot_root(8), pivoting into the new root before running systemd

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions