1
0
mirror of https://github.com/opencontainers/runc.git synced 2025-11-09 13:00:56 +03:00

Fix bind mounts of filesystems with certain options set

Currently bind mounts of filesystems with nodev, nosuid, noexec,
noatime, relatime, strictatime, nodiratime options set fail in rootless
mode if the same options are not set for the bind mount.
For ro filesystems this was resolved by #2570 by remounting again
with ro set.

Follow the same approach for nodev, nosuid, noexec, noatime, relatime,
strictatime, nodiratime but allow to revert back to the old behaviour
via the new `--no-mount-fallback` command line option.

Add a testcase to verify that bind mounts of filesystems with nodev,
nosuid, noexec, noatime options set work in rootless mode.
Add a testcase that mounts a nodev, nosuid, noexec, noatime filesystem
with a ro flag.
Add two further testcases that ensure that the above testcases would
fail if the `--no-mount-fallback` command line option is set.

* contrib/completions/bash/runc:
      Add `--no-mount-fallback` command line option for bash completion.

* create.go:
      Add `--no-mount-fallback` command line option.

* restore.go:
      Add `--no-mount-fallback` command line option.

* run.go:
      Add `--no-mount-fallback` command line option.

* libcontainer/configs/config.go:
      Add `NoMountFallback` field to the `Config` struct to store
      the command line option value.

* libcontainer/specconv/spec_linux.go:
      Add `NoMountFallback` field to the `CreateOpts` struct to store
      the command line option value and store it in the libcontainer
      config.

* utils_linux.go:
      Store the command line option value in the `CreateOpts` struct.

* libcontainer/rootfs_linux.go:
      In case that `--no-mount-fallback` is not set try to remount the
      bind filesystem again with the options nodev, nosuid, noexec,
      noatime, relatime, strictatime or nodiratime if they are set on
      the source filesystem.

* tests/integration/mounts_sshfs.bats:
      Add testcases and rework sshfs setup to allow specifying
      different mount options depending on the test case.

Signed-off-by: Ruediger Pluem <ruediger.pluem@vodafone.com>
This commit is contained in:
Ruediger Pluem
2023-05-09 10:51:39 +02:00
committed by Kir Kolyshkin
parent 465cb34a4b
commit da780e4d27
9 changed files with 121 additions and 19 deletions

View File

@@ -461,6 +461,7 @@ _runc_run() {
--no-subreaper --no-subreaper
--no-pivot --no-pivot
--no-new-keyring --no-new-keyring
--no-mount-fallback
" "
local options_with_args=" local options_with_args="
@@ -567,6 +568,7 @@ _runc_create() {
--help --help
--no-pivot --no-pivot
--no-new-keyring --no-new-keyring
--no-mount-fallback
" "
local options_with_args=" local options_with_args="
@@ -627,6 +629,7 @@ _runc_restore() {
--no-pivot --no-pivot
--auto-dedup --auto-dedup
--lazy-pages --lazy-pages
--no-mount-fallback
" "
local options_with_args=" local options_with_args="

View File

@@ -51,6 +51,10 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Name: "preserve-fds", Name: "preserve-fds",
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)", Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
}, },
cli.BoolFlag{
Name: "no-mount-fallback",
Usage: "Do not fallback when the specific configuration is not applicable (e.g., do not try to remount a bind mount again after the first attempt failed on source filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set)",
},
}, },
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil { if err := checkArgs(context, 1, exactArgs); err != nil {

View File

@@ -212,6 +212,10 @@ type Config struct {
// RootlessCgroups is set when unlikely to have the full access to cgroups. // RootlessCgroups is set when unlikely to have the full access to cgroups.
// When RootlessCgroups is set, cgroups errors are ignored. // When RootlessCgroups is set, cgroups errors are ignored.
RootlessCgroups bool `json:"rootless_cgroups,omitempty"` RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
// Do not try to remount a bind mount again after the first attempt failed on source
// filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set
NoMountFallback bool `json:"no_mount_fallback,omitempty"`
} }
type ( type (

View File

@@ -35,6 +35,7 @@ type mountConfig struct {
cgroup2Path string cgroup2Path string
rootlessCgroups bool rootlessCgroups bool
cgroupns bool cgroupns bool
noMountFallback bool
} }
// mountEntry contains mount data specific to a mount point. // mountEntry contains mount data specific to a mount point.
@@ -83,6 +84,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig, mountFds mountFds) (
cgroup2Path: iConfig.Cgroup2Path, cgroup2Path: iConfig.Cgroup2Path,
rootlessCgroups: iConfig.RootlessCgroups, rootlessCgroups: iConfig.RootlessCgroups,
cgroupns: config.Namespaces.Contains(configs.NEWCGROUP), cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
noMountFallback: config.NoMountFallback,
} }
for i, m := range config.Mounts { for i, m := range config.Mounts {
entry := mountEntry{Mount: m} entry := mountEntry{Mount: m}
@@ -512,7 +514,7 @@ func mountToRootfs(c *mountConfig, m mountEntry) error {
// first check that we have non-default options required before attempting a remount // first check that we have non-default options required before attempting a remount
if m.Flags&^(unix.MS_REC|unix.MS_REMOUNT|unix.MS_BIND) != 0 { if m.Flags&^(unix.MS_REC|unix.MS_REMOUNT|unix.MS_BIND) != 0 {
// only remount if unique mount options are set // only remount if unique mount options are set
if err := remount(m, rootfs); err != nil { if err := remount(m, rootfs, c.noMountFallback); err != nil {
return err return err
} }
} }
@@ -1101,24 +1103,33 @@ func writeSystemProperty(key, value string) error {
return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644) return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644)
} }
func remount(m mountEntry, rootfs string) error { func remount(m mountEntry, rootfs string, noMountFallback bool) error {
return utils.WithProcfd(rootfs, m.Destination, func(dstFD string) error { return utils.WithProcfd(rootfs, m.Destination, func(dstFD string) error {
flags := uintptr(m.Flags | unix.MS_REMOUNT) flags := uintptr(m.Flags | unix.MS_REMOUNT)
err := mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "") err := mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "")
if err == nil { if err == nil {
return nil return nil
} }
// Check if the source has ro flag... // Check if the source has flags set according to noMountFallback
src := m.src() src := m.src()
var s unix.Statfs_t var s unix.Statfs_t
if err := unix.Statfs(src, &s); err != nil { if err := unix.Statfs(src, &s); err != nil {
return &os.PathError{Op: "statfs", Path: src, Err: err} return &os.PathError{Op: "statfs", Path: src, Err: err}
} }
if s.Flags&unix.MS_RDONLY != unix.MS_RDONLY { var checkflags int
if noMountFallback {
// Check for ro only
checkflags = unix.MS_RDONLY
} else {
// Check for ro, nodev, noexec, nosuid, noatime, relatime, strictatime,
// nodiratime
checkflags = unix.MS_RDONLY | unix.MS_NODEV | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_RELATIME | unix.MS_STRICTATIME | unix.MS_NODIRATIME
}
if int(s.Flags)&checkflags == 0 {
return err return err
} }
// ... and retry the mount with ro flag set. // ... and retry the mount with flags found above.
flags |= unix.MS_RDONLY flags |= uintptr(int(s.Flags) & checkflags)
return mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "") return mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "")
}) })
} }

View File

@@ -312,6 +312,7 @@ type CreateOpts struct {
Spec *specs.Spec Spec *specs.Spec
RootlessEUID bool RootlessEUID bool
RootlessCgroups bool RootlessCgroups bool
NoMountFallback bool
} }
// getwd is a wrapper similar to os.Getwd, except it always gets // getwd is a wrapper similar to os.Getwd, except it always gets
@@ -358,6 +359,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
NoNewKeyring: opts.NoNewKeyring, NoNewKeyring: opts.NoNewKeyring,
RootlessEUID: opts.RootlessEUID, RootlessEUID: opts.RootlessEUID,
RootlessCgroups: opts.RootlessCgroups, RootlessCgroups: opts.RootlessCgroups,
NoMountFallback: opts.NoMountFallback,
} }
for _, m := range spec.Mounts { for _, m := range spec.Mounts {

View File

@@ -98,6 +98,10 @@ using the runc checkpoint command.`,
Value: "", Value: "",
Usage: "Specify an LSM mount context to be used during restore.", Usage: "Specify an LSM mount context to be used during restore.",
}, },
cli.BoolFlag{
Name: "no-mount-fallback",
Usage: "Do not fallback when the specific configuration is not applicable (e.g., do not try to remount a bind mount again after the first attempt failed on source filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set)",
},
}, },
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil { if err := checkArgs(context, 1, exactArgs); err != nil {

4
run.go
View File

@@ -64,6 +64,10 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Name: "preserve-fds", Name: "preserve-fds",
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)", Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
}, },
cli.BoolFlag{
Name: "no-mount-fallback",
Usage: "Do not fallback when the specific configuration is not applicable (e.g., do not try to remount a bind mount again after the first attempt failed on source filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set)",
},
}, },
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil { if err := checkArgs(context, 1, exactArgs); err != nil {

View File

@@ -3,7 +3,20 @@
load helpers load helpers
function setup() { function setup() {
# Create a ro fuse-sshfs mount; skip the test if it's not working. setup_busybox
update_config '.process.args = ["/bin/echo", "Hello World"]'
}
function teardown() {
# Some distros do not have fusermount installed
# as a dependency of fuse-sshfs, and good ol' umount works.
fusermount -u "$DIR" || umount "$DIR"
teardown_bundle
}
function setup_sshfs() {
# Create a fuse-sshfs mount; skip the test if it's not working.
local sshfs="sshfs local sshfs="sshfs
-o UserKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no -o StrictHostKeyChecking=no
@@ -12,23 +25,13 @@ function setup() {
DIR="$BATS_RUN_TMPDIR/fuse-sshfs" DIR="$BATS_RUN_TMPDIR/fuse-sshfs"
mkdir -p "$DIR" mkdir -p "$DIR"
if ! $sshfs -o ro rootless@localhost: "$DIR"; then if ! $sshfs -o "$1" rootless@localhost: "$DIR"; then
skip "test requires working sshfs mounts" skip "test requires working sshfs mounts"
fi fi
setup_busybox
update_config '.process.args = ["/bin/echo", "Hello World"]'
}
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]" { @test "runc run [rw bind mount of a ro fuse sshfs mount]" {
setup_sshfs "ro"
update_config ' .mounts += [{ update_config ' .mounts += [{
type: "bind", type: "bind",
source: "'"$DIR"'", source: "'"$DIR"'",
@@ -36,6 +39,72 @@ function teardown() {
options: ["rw", "rprivate", "nosuid", "nodev", "rbind"] options: ["rw", "rprivate", "nosuid", "nodev", "rbind"]
}]' }]'
runc run --no-mount-fallback test_busybox
[ "$status" -eq 0 ]
}
@test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" {
setup_sshfs "nodev,nosuid,noexec,noatime"
# The "sync" option is used to trigger a remount with the below options.
# It serves no further purpose. Otherwise only a bind mount without
# applying the below options will be done.
update_config ' .mounts += [{
type: "bind",
source: "'"$DIR"'",
destination: "/mnt",
options: ["dev", "suid", "exec", "atime", "rprivate", "rbind", "sync"]
}]'
runc run test_busybox runc run test_busybox
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@test "runc run [ro bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" {
setup_sshfs "nodev,nosuid,noexec,noatime"
update_config ' .mounts += [{
type: "bind",
source: "'"$DIR"'",
destination: "/mnt",
options: ["rbind", "ro"]
}]'
runc run test_busybox
[ "$status" -eq 0 ]
}
@test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount without fallback]" {
setup_sshfs "nodev,nosuid,noexec,noatime"
# The "sync" option is used to trigger a remount with the below options.
# It serves no further purpose. Otherwise only a bind mount without
# applying the below options will be done.
update_config ' .mounts += [{
type: "bind",
source: "'"$DIR"'",
destination: "/mnt",
options: ["dev", "suid", "exec", "atime", "rprivate", "rbind", "sync"]
}]'
runc run --no-mount-fallback test_busybox
# The above will fail as we added --no-mount-fallback which causes us not to
# try to remount a bind mount again after the first attempt failed on source
# filesystems that have nodev, noexec, nosuid, noatime set.
[ "$status" -ne 0 ]
[[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]]
}
@test "runc run [ro bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount without fallback]" {
setup_sshfs "nodev,nosuid,noexec,noatime"
update_config ' .mounts += [{
type: "bind",
source: "'"$DIR"'",
destination: "/mnt",
options: ["rbind", "ro"]
}]'
runc run --no-mount-fallback test_busybox
# The above will fail as we added --no-mount-fallback which causes us not to
# try to remount a bind mount again after the first attempt failed on source
# filesystems that have nodev, noexec, nosuid, noatime set.
[ "$status" -ne 0 ]
[[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]]
}

View File

@@ -175,6 +175,7 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcon
Spec: spec, Spec: spec,
RootlessEUID: os.Geteuid() != 0, RootlessEUID: os.Geteuid() != 0,
RootlessCgroups: rootlessCg, RootlessCgroups: rootlessCg,
NoMountFallback: context.Bool("no-mount-fallback"),
}) })
if err != nil { if err != nil {
return nil, err return nil, err