| #!/usr/bin/env bats |
| |
| load helpers |
| |
| function setup() { |
| setup_busybox |
| } |
| |
| function teardown() { |
| if [ -v DIR ]; then |
| # Some distros do not have fusermount installed |
| # as a dependency of fuse-sshfs, and good ol' umount works. |
| fusermount -u "$DIR" || umount "$DIR" |
| unset DIR |
| fi |
| |
| teardown_bundle |
| } |
| |
| function sshfs_has_flag() { |
| if [ -v DIR ]; then |
| awk '$2 == "'"$DIR"'" { print $4 }' </proc/self/mounts | grep -E "\b$1\b" |
| return "$?" |
| fi |
| } |
| |
| function setup_sshfs() { |
| # Create a fuse-sshfs mount (or, failing that, a tmpfs mount). |
| local sshfs="sshfs |
| -o UserKnownHostsFile=/dev/null |
| -o StrictHostKeyChecking=no |
| -o PasswordAuthentication=no" |
| |
| if ! [ -v DIR ]; then |
| DIR="$BATS_RUN_TMPDIR/fuse-sshfs" |
| mkdir -p "$DIR" |
| # Make sure we clear all superblock flags to make sure bind-mounts can |
| # unset these flags. |
| if ! $sshfs -o rw,suid,dev,exec,atime rootless@localhost: "$DIR"; then |
| # fallback to tmpfs if running in without sshfs |
| mount -t tmpfs -o rw,suid,dev,exec,diratime,strictatime tmpfs "$DIR" |
| fi |
| fi |
| # Reset atime flags. "diratime" is quite a strange flag, so we need to make |
| # sure it's cleared before we apply the requested flags. |
| mount --bind -o remount,diratime,atime,strictatime "$DIR" |
| # We need to set the mount flags separately on the mount because some mount |
| # flags (such as "ro") are set on the superblock if you do them in the |
| # initial mount, which means that they cannot be cleared by bind-mounts. |
| # |
| # This also lets us reconfigure the per-mount settings on each call. |
| mount --bind -o "remount,$1" "$DIR" |
| echo "configured $DIR with mount --bind -o remount,$1" >&2 |
| awk '$2 == "'"$DIR"'"' </proc/self/mounts >&2 |
| } |
| |
| function setup_sshfs_bind_flags() { |
| host_flags="$1" # ro,nodev,nosuid |
| bind_flags="$2" # ro,nosuid,bind |
| |
| setup_sshfs "$host_flags" |
| |
| cat >"rootfs/find-tmp.awk" <<-'EOF' |
| #!/bin/awk -f |
| $2 == "/mnt" { print $4 } |
| EOF |
| chmod +x "rootfs/find-tmp.awk" |
| |
| update_config '.process.args = ["sh", "-c", "/find-tmp.awk </proc/self/mounts"]' |
| update_config '.mounts = (.mounts | map(select(.destination != "/mnt"))) + [{ |
| "source": "'"$DIR"'", |
| "destination": "/mnt", |
| "type": "bind", |
| "options": '"$(jq -cRM 'split(",")' <<<"$bind_flags")"' |
| }]' |
| } |
| |
| function pass_sshfs_bind_flags() { |
| setup_sshfs_bind_flags "$@" |
| |
| runc run test_busybox |
| [ "$status" -eq 0 ] |
| mnt_flags="$output" |
| } |
| |
| function fail_sshfs_bind_flags() { |
| setup_sshfs_bind_flags "$@" |
| |
| runc run test_busybox |
| [ "$status" -ne 0 ] |
| [[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]] |
| } |
| |
| @test "runc run [mount(8)-like behaviour: --bind with no options]" { |
| requires root |
| |
| pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind" |
| # If no flags were specified alongside bind, we keep all existing flags. |
| # Unspecified flags must be cleared (rw default). |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq rw <<<"$mnt_flags" |
| run -0 grep -wq noexec <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| # On old systems, mount doesn't know about nosymfollow, which turns the |
| # flag into a data argument (which is ignored by MS_REMOUNT). |
| if sshfs_has_flag nosymfollow; then run -0 grep -wq nosymfollow <<<"$mnt_flags"; fi |
| |
| # Now try with a user namespace. The results should be the same as above. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind" |
| # If no flags were specified alongside bind, we keep all existing flags. |
| # Unspecified flags must be cleared (rw default). |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq rw <<<"$mnt_flags" |
| run -0 grep -wq noexec <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| # On old systems, mount doesn't know about nosymfollow, which turns the |
| # flag into a data argument (which is ignored by MS_REMOUNT). |
| if sshfs_has_flag nosymfollow; then run -0 grep -wq nosymfollow <<<"$mnt_flags"; fi |
| } |
| |
| # This behaviour does not match mount(8), but is preferable to the alternative. |
| # See <https://github.com/util-linux/util-linux/issues/2433>. |
| @test "runc run [mount(8)-unlike behaviour: --bind with clearing flag]" { |
| requires root |
| |
| pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind,dev" |
| # Unspecified flags must be cleared as well. |
| run ! grep -wq ro <<<"$mnt_flags" |
| run -0 grep -wq rw <<<"$mnt_flags" |
| run ! grep -wq noexec <<<"$mnt_flags" |
| run ! grep -wq nosymfollow <<<"$mnt_flags" |
| # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" |
| # way will be ignored! |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind,dev" |
| # Lockable flags must be kept, because we didn't request them explicitly. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq rw <<<"$mnt_flags" |
| run -0 grep -wq noexec <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| # nosymfollow is not lockable, so it must be cleared. |
| run ! grep -wq nosymfollow <<<"$mnt_flags" |
| } |
| |
| @test "runc run [implied-rw bind mount of a ro fuse sshfs mount]" { |
| requires root |
| |
| pass_sshfs_bind_flags "ro" "bind,nosuid,nodev,rprivate" |
| # Unspecified flags must be cleared (rw default). |
| run ! grep -wq ro <<<"$mnt_flags" |
| run -0 grep -wq rw <<<"$mnt_flags" |
| # The new flags must be applied. |
| run -0 grep -wq nosuid <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| |
| # Now try with a user namespace. The results should be the same as above. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| pass_sshfs_bind_flags "ro" "bind,nosuid,nodev,rprivate" |
| # "ro" must still be set (inherited). |
| run -0 grep -wq ro <<<"$mnt_flags" |
| # The new flags must be applied. |
| run -0 grep -wq nosuid <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| } |
| |
| @test "runc run [explicit-rw bind mount of a ro fuse sshfs mount]" { |
| requires root |
| |
| # Try to overwrite MS_RDONLY. As we are running in a userns-less container, |
| # we can overwrite MNT_LOCKED flags. |
| pass_sshfs_bind_flags "ro" "bind,rw,nosuid,nodev,rprivate" |
| # "ro" must be cleared and replaced with "rw". |
| run ! grep -wq ro <<<"$mnt_flags" |
| run -0 grep -wq rw <<<"$mnt_flags" |
| # The new flags must be applied. |
| run -0 grep -wq nosuid <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # This must fail because we explicitly requested a mount with a MNT_LOCKED |
| # mount option cleared (when the source mount has those mounts enabled), |
| # namely MS_RDONLY. |
| fail_sshfs_bind_flags "ro" "bind,rw,nosuid,nodev,rprivate" |
| } |
| |
| @test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" { |
| requires root |
| |
| # When running without userns, overwriting host flags should work. |
| pass_sshfs_bind_flags "nosuid,nodev,noexec,noatime" "bind,dev,suid,exec,atime" |
| # Unspecified flags must be cleared (rw default). |
| run ! grep -wq ro <<<"$mnt_flags" |
| run -0 grep -wq rw <<<"$mnt_flags" |
| # Check that the flags were actually cleared by the mount. |
| run ! grep -wq nosuid <<<"$mnt_flags" |
| run ! grep -wq nodev <<<"$mnt_flags" |
| run ! grep -wq noexec <<<"$mnt_flags" |
| # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" |
| # way will be ignored! |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # This must fail because we explicitly requested a mount with MNT_LOCKED |
| # mount options cleared (when the source mount has those mounts enabled). |
| fail_sshfs_bind_flags "nodev,nosuid,nosuid,noatime" "bind,dev,suid,exec,atime" |
| } |
| |
| # Test to ensure we don't regress bind-mounting /etc/resolv.conf with |
| # containerd <https://github.com/containerd/containerd/pull/8309>. |
| @test "runc run [ro bind mount of a nodev,nosuid,noexec fuse sshfs mount]" { |
| requires root |
| |
| # Setting flags that are not locked should work. |
| pass_sshfs_bind_flags "rw,nodev,nosuid,nodev,noexec,noatime" "bind,ro" |
| # The flagset should be the union of the two. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| # Unspecified flags must be cleared. |
| run ! grep -wq nosuid <<<"$mnt_flags" |
| run ! grep -wq nodev <<<"$mnt_flags" |
| run ! grep -wq noexec <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # Setting flags that are not locked should work. |
| pass_sshfs_bind_flags "rw,nodev,nosuid,nodev,noexec,noatime" "bind,ro" |
| # The flagset should be the union of the two. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| # (Unspecified MNT_LOCKED flags are inherited.) |
| run -0 grep -wq nosuid <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| run -0 grep -wq noexec <<<"$mnt_flags" |
| } |
| |
| @test "runc run [ro,symfollow bind mount of a rw,nodev,nosymfollow fuse sshfs mount]" { |
| requires root |
| |
| pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,ro,symfollow" |
| # Must switch to ro. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq rw <<<"$mnt_flags" |
| # Unspecified flags must be cleared. |
| run ! grep -wq nodev <<<"$mnt_flags" |
| # nosymfollow must also be cleared. |
| run ! grep -wq nosymfollow <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # Unsetting flags that are not lockable should work. |
| pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,ro,symfollow" |
| # The flagset should be the union of the two. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| # nosymfollow is not lockable, so it must be cleared. |
| run ! grep -wq nosymfollow <<<"$mnt_flags" |
| |
| # Implied unsetting of non-lockable flags should also work. |
| pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,rw" |
| # The flagset should be the union of the two. |
| run -0 grep -wq rw <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| # nosymfollow is not lockable, so it must be cleared. |
| run ! grep -wq nosymfollow <<<"$mnt_flags" |
| } |
| |
| @test "runc run [ro,noexec bind mount of a nosuid,noatime fuse sshfs mount]" { |
| requires root |
| |
| # Setting flags that are not locked should work. |
| pass_sshfs_bind_flags "nodev,nosuid,noatime" "bind,ro,exec" |
| # The flagset must match the requested set. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq noexec <<<"$mnt_flags" |
| # Unspecified flags must be cleared. |
| run ! grep -wq nosuid <<<"$mnt_flags" |
| run ! grep -wq nodev <<<"$mnt_flags" |
| # FIXME: As with mount(8), runc keeps the old atime setting by default. |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # Setting flags that are not locked should work. |
| pass_sshfs_bind_flags "nodev,nosuid,noatime" "bind,ro,exec" |
| # The flagset should be the union of the two. |
| run -0 grep -wq ro <<<"$mnt_flags" |
| run ! grep -wq noexec <<<"$mnt_flags" |
| # (Unspecified MNT_LOCKED flags are inherited.) |
| run -0 grep -wq nosuid <<<"$mnt_flags" |
| run -0 grep -wq nodev <<<"$mnt_flags" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| } |
| |
| @test "runc run [bind mount {no,rel,strict}atime semantics]" { |
| requires root |
| |
| function is_strictatime() { |
| # There is no "strictatime" in /proc/self/mounts. |
| run ! grep -wq noatime <<<"${1:-$mnt_flags}" |
| run ! grep -wq relatime <<<"${1:-$mnt_flags}" |
| run ! grep -wq nodiratime <<<"${1:-$mnt_flags}" |
| } |
| |
| # FIXME: As with mount(8), runc keeps the old atime setting by default. |
| pass_sshfs_bind_flags "noatime" "bind" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run ! grep -wq relatime <<<"$mnt_flags" |
| |
| # FIXME: As with mount(8), runc keeps the old atime setting by default. |
| pass_sshfs_bind_flags "noatime" "bind,norelatime" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run ! grep -wq relatime <<<"$mnt_flags" |
| |
| # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" |
| # way will be ignored! |
| pass_sshfs_bind_flags "noatime" "bind,atime" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run ! grep -wq relatime <<<"$mnt_flags" |
| |
| # ... but explicitly setting a different flag works. |
| pass_sshfs_bind_flags "noatime" "bind,relatime" |
| run ! grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq relatime <<<"$mnt_flags" |
| |
| # Setting a flag that mount(8) would combine should result in only the |
| # requested flag being set. |
| pass_sshfs_bind_flags "noatime" "bind,nodiratime" |
| run ! grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| # MS_DIRATIME implies MS_RELATIME by default. |
| run -0 grep -wq relatime <<<"$mnt_flags" |
| |
| # Clearing flags that mount(8) would not clear works. |
| pass_sshfs_bind_flags "nodiratime" "bind,strictatime" |
| is_strictatime "$mnt_flags" |
| |
| # nodiratime is a little weird -- it implies relatime unless you set |
| # another option (noatime or strictatime). But, runc also has norelatime -- |
| # so nodiratime,norelatime should _probably_ result in the same thing as |
| # nodiratime,strictatime. |
| pass_sshfs_bind_flags "noatime" "bind,nodiratime,strictatime" |
| run ! grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| run ! grep -wq relatime <<<"$mnt_flags" |
| # FIXME FIXME: relatime should not be set in this case. |
| pass_sshfs_bind_flags "noatime" "bind,nodiratime,norelatime" |
| run ! grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| run -0 grep -wq relatime <<<"$mnt_flags" |
| |
| # Now try with a user namespace. |
| update_config ' .linux.namespaces += [{"type": "user"}] |
| | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] |
| | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' |
| |
| # Requesting a mount without specifying any preference for atime works, and |
| # inherits the original flags. |
| |
| pass_sshfs_bind_flags "strictatime" "bind" |
| is_strictatime "$mnt_flags" |
| |
| pass_sshfs_bind_flags "relatime" "bind" |
| run -0 grep -wq relatime <<<"$mnt_flags" |
| |
| pass_sshfs_bind_flags "nodiratime" "bind" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| # MS_DIRATIME implies MS_RELATIME by default. |
| # Let's check either relatime is set or no other option that removes |
| # relatime semantics is set. |
| # The latter case is needed in debian. For more info, see issue: #4093 |
| run -0 grep -wq relatime <<<"$mnt_flags" || |
| (run ! grep -wqE 'strictatime|norelatime|noatime' <<<"$mnt_flags") |
| |
| pass_sshfs_bind_flags "noatime,nodiratime" "bind" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| |
| # An unrelated clear flag has no effect. |
| pass_sshfs_bind_flags "noatime,nodiratime" "bind,norelatime" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| |
| # Attempting to change most *atime flags will fail with user namespaces |
| # because *atime flags are all MNT_LOCKED. |
| fail_sshfs_bind_flags "nodiratime" "bind,strictatime" |
| fail_sshfs_bind_flags "relatime" "bind,strictatime" |
| fail_sshfs_bind_flags "noatime" "bind,strictatime" |
| fail_sshfs_bind_flags "nodiratime" "bind,noatime" |
| fail_sshfs_bind_flags "relatime" "bind,noatime" |
| fail_sshfs_bind_flags "relatime" "bind,nodiratime" |
| # Make sure strictatime sources are correctly handled by runc (the kernel |
| # ignores some other mount flags when passing MS_STRICTATIME). See |
| # remount() in rootfs_linux.go for details. |
| fail_sshfs_bind_flags "strictatime" "bind,relatime" |
| fail_sshfs_bind_flags "strictatime" "bind,noatime" |
| fail_sshfs_bind_flags "strictatime" "bind,nodiratime" |
| # Make sure that runc correctly handles the MS_NOATIME|MS_RELATIME kernel |
| # bug. See remount() in rootfs_linux.go for more details. |
| fail_sshfs_bind_flags "noatime" "bind,relatime" |
| |
| # Attempting to bind-mount a mount with a request to clear the atime |
| # setting that would normally inherited must not work. |
| # FIXME FIXME: All of these cases should fail. |
| pass_sshfs_bind_flags "strictatime" "bind,nostrictatime" |
| is_strictatime "$mnt_flags" |
| pass_sshfs_bind_flags "nodiratime" "bind,diratime" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| pass_sshfs_bind_flags "nodiratime" "bind,norelatime" # MS_DIRATIME implies MS_RELATIME |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| pass_sshfs_bind_flags "relatime" "bind,norelatime" |
| run -0 grep -wq relatime <<<"$mnt_flags" |
| pass_sshfs_bind_flags "noatime" "bind,atime" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| pass_sshfs_bind_flags "noatime,nodiratime" "bind,atime" |
| run -0 grep -wq noatime <<<"$mnt_flags" |
| run -0 grep -wq nodiratime <<<"$mnt_flags" |
| } |