Skip to content

Commit b15a6a3

Browse files
authored
Merge pull request #3805 from rpluem-vf/nodev_noexec_nosuid
Allow bind mounts of nodev,nosuid,noexec filesystems
2 parents 465cb34 + da780e4 commit b15a6a3

File tree

9 files changed

+119
-17
lines changed

9 files changed

+119
-17
lines changed

contrib/completions/bash/runc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ _runc_run() {
461461
--no-subreaper
462462
--no-pivot
463463
--no-new-keyring
464+
--no-mount-fallback
464465
"
465466

466467
local options_with_args="
@@ -567,6 +568,7 @@ _runc_create() {
567568
--help
568569
--no-pivot
569570
--no-new-keyring
571+
--no-mount-fallback
570572
"
571573

572574
local options_with_args="
@@ -627,6 +629,7 @@ _runc_restore() {
627629
--no-pivot
628630
--auto-dedup
629631
--lazy-pages
632+
--no-mount-fallback
630633
"
631634

632635
local options_with_args="

create.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ command(s) that get executed on start, edit the args parameter of the spec. See
5151
Name: "preserve-fds",
5252
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
5353
},
54+
cli.BoolFlag{
55+
Name: "no-mount-fallback",
56+
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)",
57+
},
5458
},
5559
Action: func(context *cli.Context) error {
5660
if err := checkArgs(context, 1, exactArgs); err != nil {

libcontainer/configs/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ type Config struct {
212212
// RootlessCgroups is set when unlikely to have the full access to cgroups.
213213
// When RootlessCgroups is set, cgroups errors are ignored.
214214
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
215+
216+
// Do not try to remount a bind mount again after the first attempt failed on source
217+
// filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set
218+
NoMountFallback bool `json:"no_mount_fallback,omitempty"`
215219
}
216220

217221
type (

libcontainer/rootfs_linux.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type mountConfig struct {
3535
cgroup2Path string
3636
rootlessCgroups bool
3737
cgroupns bool
38+
noMountFallback bool
3839
}
3940

4041
// mountEntry contains mount data specific to a mount point.
@@ -83,6 +84,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig, mountFds mountFds) (
8384
cgroup2Path: iConfig.Cgroup2Path,
8485
rootlessCgroups: iConfig.RootlessCgroups,
8586
cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
87+
noMountFallback: config.NoMountFallback,
8688
}
8789
for i, m := range config.Mounts {
8890
entry := mountEntry{Mount: m}
@@ -512,7 +514,7 @@ func mountToRootfs(c *mountConfig, m mountEntry) error {
512514
// first check that we have non-default options required before attempting a remount
513515
if m.Flags&^(unix.MS_REC|unix.MS_REMOUNT|unix.MS_BIND) != 0 {
514516
// only remount if unique mount options are set
515-
if err := remount(m, rootfs); err != nil {
517+
if err := remount(m, rootfs, c.noMountFallback); err != nil {
516518
return err
517519
}
518520
}
@@ -1101,24 +1103,33 @@ func writeSystemProperty(key, value string) error {
11011103
return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644)
11021104
}
11031105

1104-
func remount(m mountEntry, rootfs string) error {
1106+
func remount(m mountEntry, rootfs string, noMountFallback bool) error {
11051107
return utils.WithProcfd(rootfs, m.Destination, func(dstFD string) error {
11061108
flags := uintptr(m.Flags | unix.MS_REMOUNT)
11071109
err := mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "")
11081110
if err == nil {
11091111
return nil
11101112
}
1111-
// Check if the source has ro flag...
1113+
// Check if the source has flags set according to noMountFallback
11121114
src := m.src()
11131115
var s unix.Statfs_t
11141116
if err := unix.Statfs(src, &s); err != nil {
11151117
return &os.PathError{Op: "statfs", Path: src, Err: err}
11161118
}
1117-
if s.Flags&unix.MS_RDONLY != unix.MS_RDONLY {
1119+
var checkflags int
1120+
if noMountFallback {
1121+
// Check for ro only
1122+
checkflags = unix.MS_RDONLY
1123+
} else {
1124+
// Check for ro, nodev, noexec, nosuid, noatime, relatime, strictatime,
1125+
// nodiratime
1126+
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
1127+
}
1128+
if int(s.Flags)&checkflags == 0 {
11181129
return err
11191130
}
1120-
// ... and retry the mount with ro flag set.
1121-
flags |= unix.MS_RDONLY
1131+
// ... and retry the mount with flags found above.
1132+
flags |= uintptr(int(s.Flags) & checkflags)
11221133
return mountViaFDs(m.Source, m.srcFD, m.Destination, dstFD, m.Device, flags, "")
11231134
})
11241135
}

libcontainer/specconv/spec_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ type CreateOpts struct {
312312
Spec *specs.Spec
313313
RootlessEUID bool
314314
RootlessCgroups bool
315+
NoMountFallback bool
315316
}
316317

317318
// getwd is a wrapper similar to os.Getwd, except it always gets
@@ -358,6 +359,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
358359
NoNewKeyring: opts.NoNewKeyring,
359360
RootlessEUID: opts.RootlessEUID,
360361
RootlessCgroups: opts.RootlessCgroups,
362+
NoMountFallback: opts.NoMountFallback,
361363
}
362364

363365
for _, m := range spec.Mounts {

restore.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ using the runc checkpoint command.`,
9898
Value: "",
9999
Usage: "Specify an LSM mount context to be used during restore.",
100100
},
101+
cli.BoolFlag{
102+
Name: "no-mount-fallback",
103+
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)",
104+
},
101105
},
102106
Action: func(context *cli.Context) error {
103107
if err := checkArgs(context, 1, exactArgs); err != nil {

run.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ command(s) that get executed on start, edit the args parameter of the spec. See
6464
Name: "preserve-fds",
6565
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
6666
},
67+
cli.BoolFlag{
68+
Name: "no-mount-fallback",
69+
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)",
70+
},
6771
},
6872
Action: func(context *cli.Context) error {
6973
if err := checkArgs(context, 1, exactArgs); err != nil {

tests/integration/mounts_sshfs.bats

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,20 @@
33
load helpers
44

55
function setup() {
6-
# Create a ro fuse-sshfs mount; skip the test if it's not working.
6+
setup_busybox
7+
update_config '.process.args = ["/bin/echo", "Hello World"]'
8+
}
9+
10+
function teardown() {
11+
# Some distros do not have fusermount installed
12+
# as a dependency of fuse-sshfs, and good ol' umount works.
13+
fusermount -u "$DIR" || umount "$DIR"
14+
15+
teardown_bundle
16+
}
17+
18+
function setup_sshfs() {
19+
# Create a fuse-sshfs mount; skip the test if it's not working.
720
local sshfs="sshfs
821
-o UserKnownHostsFile=/dev/null
922
-o StrictHostKeyChecking=no
@@ -12,30 +25,86 @@ function setup() {
1225
DIR="$BATS_RUN_TMPDIR/fuse-sshfs"
1326
mkdir -p "$DIR"
1427

15-
if ! $sshfs -o ro rootless@localhost: "$DIR"; then
28+
if ! $sshfs -o "$1" rootless@localhost: "$DIR"; then
1629
skip "test requires working sshfs mounts"
1730
fi
31+
}
1832

19-
setup_busybox
20-
update_config '.process.args = ["/bin/echo", "Hello World"]'
33+
@test "runc run [rw bind mount of a ro fuse sshfs mount]" {
34+
setup_sshfs "ro"
35+
update_config ' .mounts += [{
36+
type: "bind",
37+
source: "'"$DIR"'",
38+
destination: "/mnt",
39+
options: ["rw", "rprivate", "nosuid", "nodev", "rbind"]
40+
}]'
41+
42+
runc run --no-mount-fallback test_busybox
43+
[ "$status" -eq 0 ]
2144
}
2245

23-
function teardown() {
24-
# New distros (Fedora 35) do not have fusermount installed
25-
# as a dependency of fuse-sshfs, and good ol' umount works.
26-
fusermount -u "$DIR" || umount "$DIR"
46+
@test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" {
47+
setup_sshfs "nodev,nosuid,noexec,noatime"
48+
# The "sync" option is used to trigger a remount with the below options.
49+
# It serves no further purpose. Otherwise only a bind mount without
50+
# applying the below options will be done.
51+
update_config ' .mounts += [{
52+
type: "bind",
53+
source: "'"$DIR"'",
54+
destination: "/mnt",
55+
options: ["dev", "suid", "exec", "atime", "rprivate", "rbind", "sync"]
56+
}]'
2757

28-
teardown_bundle
58+
runc run test_busybox
59+
[ "$status" -eq 0 ]
2960
}
3061

31-
@test "runc run [rw bind mount of a ro fuse sshfs mount]" {
62+
@test "runc run [ro bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" {
63+
setup_sshfs "nodev,nosuid,noexec,noatime"
3264
update_config ' .mounts += [{
3365
type: "bind",
3466
source: "'"$DIR"'",
3567
destination: "/mnt",
36-
options: ["rw", "rprivate", "nosuid", "nodev", "rbind"]
68+
options: ["rbind", "ro"]
3769
}]'
3870

3971
runc run test_busybox
4072
[ "$status" -eq 0 ]
4173
}
74+
75+
@test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount without fallback]" {
76+
setup_sshfs "nodev,nosuid,noexec,noatime"
77+
# The "sync" option is used to trigger a remount with the below options.
78+
# It serves no further purpose. Otherwise only a bind mount without
79+
# applying the below options will be done.
80+
update_config ' .mounts += [{
81+
type: "bind",
82+
source: "'"$DIR"'",
83+
destination: "/mnt",
84+
options: ["dev", "suid", "exec", "atime", "rprivate", "rbind", "sync"]
85+
}]'
86+
87+
runc run --no-mount-fallback test_busybox
88+
# The above will fail as we added --no-mount-fallback which causes us not to
89+
# try to remount a bind mount again after the first attempt failed on source
90+
# filesystems that have nodev, noexec, nosuid, noatime set.
91+
[ "$status" -ne 0 ]
92+
[[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]]
93+
}
94+
95+
@test "runc run [ro bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount without fallback]" {
96+
setup_sshfs "nodev,nosuid,noexec,noatime"
97+
update_config ' .mounts += [{
98+
type: "bind",
99+
source: "'"$DIR"'",
100+
destination: "/mnt",
101+
options: ["rbind", "ro"]
102+
}]'
103+
104+
runc run --no-mount-fallback test_busybox
105+
# The above will fail as we added --no-mount-fallback which causes us not to
106+
# try to remount a bind mount again after the first attempt failed on source
107+
# filesystems that have nodev, noexec, nosuid, noatime set.
108+
[ "$status" -ne 0 ]
109+
[[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]]
110+
}

utils_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcon
175175
Spec: spec,
176176
RootlessEUID: os.Geteuid() != 0,
177177
RootlessCgroups: rootlessCg,
178+
NoMountFallback: context.Bool("no-mount-fallback"),
178179
})
179180
if err != nil {
180181
return nil, err

0 commit comments

Comments
 (0)