Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support recursive mount attrs ("rro", "rnosuid", "rnodev", ...) #3272

Merged
merged 3 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ require (
github.com/urfave/cli v1.22.1
github.com/vishvananda/netlink v1.1.0
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c
google.golang.org/protobuf v1.27.1
)
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/configs/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type Mount struct {
// Relabel source if set, "z" indicates shared, "Z" indicates unshared.
Relabel string `json:"relabel"`

// RecAttr represents mount properties to be applied recursively (AT_RECURSIVE), see mount_setattr(2).
RecAttr *unix.MountAttr `json:"rec_attr"`

// Extensions are additional flags that are specific to runc.
Extensions int `json:"extensions"`

Expand Down
12 changes: 12 additions & 0 deletions libcontainer/rootfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
}
return mountPropagate(m, rootfs, mountLabel, mountFd)
}
if err := setRecAttr(m, rootfs); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -1123,3 +1126,12 @@ func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd
}
return nil
}

func setRecAttr(m *configs.Mount, rootfs string) error {
if m.RecAttr == nil {
return nil
}
return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
return unix.MountSetattr(-1, procfd, unix.AT_RECURSIVE, m.RecAttr)
})
}
63 changes: 58 additions & 5 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ import (
)

var (
initMapsOnce sync.Once
namespaceMapping map[specs.LinuxNamespaceType]configs.NamespaceType
mountPropagationMapping map[string]int
initMapsOnce sync.Once
namespaceMapping map[specs.LinuxNamespaceType]configs.NamespaceType
mountPropagationMapping map[string]int
recAttrFlags map[string]struct {
clear bool
flag uint64
}
mountFlags, extensionFlags map[string]struct {
clear bool
flag int
Expand Down Expand Up @@ -87,6 +91,7 @@ func initMaps() {
"norelatime": {true, unix.MS_RELATIME},
"nostrictatime": {true, unix.MS_STRICTATIME},
"nosuid": {false, unix.MS_NOSUID},
"nosymfollow": {false, unix.MS_NOSYMFOLLOW}, // since kernel 5.10
"rbind": {false, unix.MS_BIND | unix.MS_REC},
"relatime": {false, unix.MS_RELATIME},
"remount": {false, unix.MS_REMOUNT},
Expand All @@ -96,7 +101,34 @@ func initMaps() {
"strictatime": {false, unix.MS_STRICTATIME},
"suid": {true, unix.MS_NOSUID},
"sync": {false, unix.MS_SYNCHRONOUS},
"symfollow": {true, unix.MS_NOSYMFOLLOW}, // since kernel 5.10
}

recAttrFlags = map[string]struct {
clear bool
flag uint64
}{
"rro": {false, unix.MOUNT_ATTR_RDONLY},
"rrw": {true, unix.MOUNT_ATTR_RDONLY},
"rnosuid": {false, unix.MOUNT_ATTR_NOSUID},
"rsuid": {true, unix.MOUNT_ATTR_NOSUID},
"rnodev": {false, unix.MOUNT_ATTR_NODEV},
"rdev": {true, unix.MOUNT_ATTR_NODEV},
"rnoexec": {false, unix.MOUNT_ATTR_NOEXEC},
"rexec": {true, unix.MOUNT_ATTR_NOEXEC},
"rnodiratime": {false, unix.MOUNT_ATTR_NODIRATIME},
"rdiratime": {true, unix.MOUNT_ATTR_NODIRATIME},
"rrelatime": {false, unix.MOUNT_ATTR_RELATIME},
"rnorelatime": {true, unix.MOUNT_ATTR_RELATIME},
"rnoatime": {false, unix.MOUNT_ATTR_NOATIME},
"ratime": {true, unix.MOUNT_ATTR_NOATIME},
"rstrictatime": {false, unix.MOUNT_ATTR_STRICTATIME},
"rnostrictatime": {true, unix.MOUNT_ATTR_STRICTATIME},
"rnosymfollow": {false, unix.MOUNT_ATTR_NOSYMFOLLOW}, // since kernel 5.14
"rsymfollow": {true, unix.MOUNT_ATTR_NOSYMFOLLOW}, // since kernel 5.14
// No support for MOUNT_ATTR_IDMAP yet (needs UserNS FD)
}

extensionFlags = map[string]struct {
clear bool
flag int
Expand Down Expand Up @@ -131,6 +163,9 @@ func KnownMountOptions() []string {
res = append(res, k)
}
}
for k := range recAttrFlags {
res = append(res, k)
}
for k := range extensionFlags {
res = append(res, k)
}
Expand Down Expand Up @@ -922,8 +957,9 @@ func setupUserNamespace(spec *specs.Spec, config *configs.Config) error {
// structure with fields that depends on options set accordingly.
func parseMountOptions(options []string) *configs.Mount {
var (
data []string
m configs.Mount
data []string
m configs.Mount
recAttrSet, recAttrClr uint64
)
initMaps()
for _, o := range options {
Expand All @@ -938,6 +974,17 @@ func parseMountOptions(options []string) *configs.Mount {
}
} else if f, exists := mountPropagationMapping[o]; exists && f != 0 {
m.PropagationFlags = append(m.PropagationFlags, f)
} else if f, exists := recAttrFlags[o]; exists {
if f.clear {
recAttrClr |= f.flag
} else {
recAttrSet |= f.flag
if f.flag&unix.MOUNT_ATTR__ATIME == f.flag {
// https://man7.org/linux/man-pages/man2/mount_setattr.2.html
// "cannot simply specify the access-time setting in attr_set, but must also include MOUNT_ATTR__ATIME in the attr_clr field."
recAttrClr |= unix.MOUNT_ATTR__ATIME
}
}
} else if f, exists := extensionFlags[o]; exists && f.flag != 0 {
if f.clear {
m.Extensions &= ^f.flag
Expand All @@ -949,6 +996,12 @@ func parseMountOptions(options []string) *configs.Mount {
}
}
m.Data = strings.Join(data, ",")
if recAttrSet != 0 || recAttrClr != 0 {
m.RecAttr = &unix.MountAttr{
Attr_set: recAttrSet,
Attr_clr: recAttrClr,
}
}
return &m
}

Expand Down
78 changes: 78 additions & 0 deletions tests/integration/mounts_recursive.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bats

load helpers

TESTVOLUME="${BATS_RUN_TMPDIR}/mounts_recursive"

function setup_volume() {
# requires root (in the current user namespace) to mount tmpfs outside runc
requires root

mkdir -p "${TESTVOLUME}"
mount -t tmpfs none "${TESTVOLUME}"
echo "foo" >"${TESTVOLUME}/foo"

mkdir "${TESTVOLUME}/subvol"
mount -t tmpfs none "${TESTVOLUME}/subvol"
echo "bar" >"${TESTVOLUME}/subvol/bar"
}

function teardown_volume() {
umount -R "${TESTVOLUME}"
}

function setup() {
setup_volume
setup_busybox
}

function teardown() {
teardown_volume
teardown_bundle
}

@test "runc run [rbind,ro mount is read-only but not recursively]" {
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"ro\"]}]"

runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_ro
[ "$status" -eq 0 ]

runc exec test_rbind_ro touch /mnt/foo
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]

runc exec test_rbind_ro touch /mnt/subvol/bar
[ "$status" -eq 0 ]
}

@test "runc run [rbind,rro mount is recursively read-only]" {
requires_kernel 5.12
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"rro\"]}]"

runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_rro
[ "$status" -eq 0 ]

runc exec test_rbind_rro touch /mnt/foo
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]

runc exec test_rbind_rro touch /mnt/subvol/bar
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]
}

@test "runc run [rbind,ro,rro mount is recursively read-only too]" {
requires_kernel 5.12
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"ro\",\"rro\"]}]"

runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_ro_rro
[ "$status" -eq 0 ]

runc exec test_rbind_ro_rro touch /mnt/foo
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]

runc exec test_rbind_ro_rro touch /mnt/subvol/bar
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]
}
2 changes: 1 addition & 1 deletion vendor/golang.org/x/sys/unix/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/golang.org/x/sys/unix/mkall.sh

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion vendor/golang.org/x/sys/unix/mkerrors.sh

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions vendor/golang.org/x/sys/unix/sockcmsg_linux.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 9 additions & 13 deletions vendor/golang.org/x/sys/unix/syscall_aix.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading