Skip to content

Commit

Permalink
Support recursive mount attrs ("rro", "rnosuid", "rnodev", ...)
Browse files Browse the repository at this point in the history
The new mount option "rro" makes the mount point recursively read-only,
by calling `mount_setattr(2)` with `MOUNT_ATTR_RDONLY` and `AT_RECURSIVE`.
https://man7.org/linux/man-pages/man2/mount_setattr.2.html

Requires kernel >= 5.12.

The "rro" option string conforms to the proposal in util-linux/util-linux Issue 1501.

Fix issue 2823

Similary, this commit also adds the following mount options:
- rrw
- r[no]{suid,dev,exec,relatime,atime,strictatime,diratime,symfollow}

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Nov 19, 2021
1 parent e683ab0 commit 9481cbe
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 5 deletions.
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 @@ -498,6 +498,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 @@ -1113,3 +1116,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)
})
}
58 changes: 53 additions & 5 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,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 @@ -98,6 +102,32 @@ func initMaps() {
"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 @@ -848,8 +878,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 @@ -864,6 +895,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 @@ -875,6 +917,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"* ]]
}

0 comments on commit 9481cbe

Please sign in to comment.