diff --git a/.cirrus.yml b/.cirrus.yml index bf9975a4f74..77ce98d21b8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -99,7 +99,7 @@ task: # Work around dnf mirror failures by retrying a few times. for i in $(seq 0 2); do sleep $i - yum install -y -q gcc git iptables jq glibc-static libseccomp-devel make criu && break + yum install -y -q gcc git iptables jq glibc-static libseccomp-devel make criu fuse-sshfs && break done [ $? -eq 0 ] # fail if yum failed # install Go @@ -113,6 +113,12 @@ task: cd - # Add a user for rootless tests useradd -u2000 -m -d/home/rootless -s/bin/bash rootless + # Allow root and rootless itself to execute `ssh rootless@localhost` in tests/rootless.sh + ssh-keygen -t ecdsa -N "" -f /root/rootless.key + mkdir -m 0700 -p /home/rootless/.ssh + cp /root/rootless.key /home/rootless/.ssh/id_ecdsa + cat /root/rootless.key.pub >> /home/rootless/.ssh/authorized_keys + chown -R rootless.rootless /home/rootless # set PATH echo 'export PATH=/usr/local/go/bin:/usr/local/bin:$PATH' >> /root/.bashrc # Setup ssh localhost for terminal emulation (script -e did not work) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5c29356c34..72f6eb377a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,13 +46,13 @@ jobs: curl -fSsl $REPO/Release.key | sudo apt-key add - echo "deb $REPO/ /" | sudo tee /etc/apt/sources.list.d/criu.list sudo apt update - sudo apt install libseccomp-dev criu + sudo apt install libseccomp-dev criu sshfs - name: install deps (criu ${{ matrix.criu }}) if: matrix.criu != '' run: | sudo apt -q update - sudo apt -q install libseccomp-dev \ + sudo apt -q install libseccomp-dev sshfs \ libcap-dev libnet1-dev libnl-3-dev \ libprotobuf-c-dev libprotobuf-dev protobuf-c-compiler protobuf-compiler git clone https://github.com/checkpoint-restore/criu.git ~/criu @@ -81,9 +81,10 @@ jobs: if: matrix.rootless == 'rootless' run: | sudo useradd -u2000 -m -d/home/rootless -s/bin/bash rootless - # Allow root to execute `ssh rootless@localhost` in tests/rootless.sh + # Allow root and rootless itself to execute `ssh rootless@localhost` in tests/rootless.sh ssh-keygen -t ecdsa -N "" -f $HOME/rootless.key sudo mkdir -m 0700 -p /home/rootless/.ssh + sudo cp $HOME/rootless.key /home/rootless/.ssh/id_ecdsa sudo cp $HOME/rootless.key.pub /home/rootless/.ssh/authorized_keys sudo chown -R rootless.rootless /home/rootless diff --git a/Dockerfile b/Dockerfile index 03475b8b3bd..2ba1ba12874 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \ kmod \ pkg-config \ python3-minimal \ + sshfs \ sudo \ uidmap \ && apt-get clean \ diff --git a/Vagrantfile.fedora b/Vagrantfile.fedora index 2c3df9abc19..69d36e672ad 100644 --- a/Vagrantfile.fedora +++ b/Vagrantfile.fedora @@ -27,7 +27,7 @@ Vagrant.configure("2") do |config| cat << EOF | dnf -y --exclude=kernel,kernel-core,systemd,systemd-* shell && break config install_weak_deps false update -install iptables gcc make golang-go glibc-static libseccomp-devel bats jq git-core criu +install iptables gcc make golang-go glibc-static libseccomp-devel bats jq git-core criu fuse-sshfs ts run EOF done @@ -36,9 +36,10 @@ EOF # Add a user for rootless tests useradd -u2000 -m -d/home/rootless -s/bin/bash rootless - # Allow root to execute `ssh rootless@localhost` in tests/rootless.sh + # Allow root and rootless itself to execute `ssh rootless@localhost` in tests/rootless.sh ssh-keygen -t ecdsa -N "" -f /root/rootless.key mkdir -m 0700 -p /home/rootless/.ssh + cp /root/rootless.key /home/rootless/.ssh/id_ecdsa cat /root/rootless.key.pub >> /home/rootless/.ssh/authorized_keys chown -R rootless.rootless /home/rootless diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index 63322d01da3..2832b8291a4 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -1065,7 +1065,22 @@ func remount(m *configs.Mount, rootfs string, mountFd *int) error { } return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error { - return mount(source, m.Destination, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "") + flags := uintptr(m.Flags | unix.MS_REMOUNT) + err := mount(source, m.Destination, procfd, m.Device, flags, "") + if err == nil { + return nil + } + // Check if the source has ro flag... + var s unix.Statfs_t + if err := unix.Statfs(source, &s); err != nil { + return &os.PathError{Op: "statfs", Path: source, Err: err} + } + if s.Flags&unix.MS_RDONLY != unix.MS_RDONLY { + return err + } + // ... and retry the mount with ro flag set. + flags |= unix.MS_RDONLY + return mount(source, m.Destination, procfd, m.Device, flags, "") }) } diff --git a/tests/integration/mounts_sshfs.bats b/tests/integration/mounts_sshfs.bats new file mode 100644 index 00000000000..abf82357d51 --- /dev/null +++ b/tests/integration/mounts_sshfs.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats + +load helpers + +function setup() { + # Create a ro fuse-sshfs mount; skip the test if it's not working. + local sshfs="sshfs + -o UserKnownHostsFile=/dev/null + -o StrictHostKeyChecking=no + -o PasswordAuthentication=no" + + DIR="$BATS_RUN_TMPDIR/fuse-sshfs" + mkdir -p "$DIR" + + if ! $sshfs -o ro rootless@localhost: "$DIR"; then + skip "test requires working sshfs mounts" + fi + + setup_hello +} + +function teardown() { + # New distros (Fedora 35) do not have fusermount installed + # as a dependency of fuse-sshfs, and good ol' umount works. + fusermount -u "$DIR" || umount "$DIR" + + teardown_bundle +} + +@test "runc run [rw bind mount of a ro fuse sshfs mount]" { + update_config ' .mounts += [{ + type: "bind", + source: "'"$DIR"'", + destination: "/mnt", + options: ["rw", "rprivate", "nosuid", "nodev", "rbind"] + }]' + + runc run test_busybox + [ "$status" -eq 0 ] +}