mirror of
https://github.com/opencontainers/runc.git
synced 2025-09-26 19:41:35 +08:00
Support recursive mount attrs ("rro", "rnosuid", "rnodev", ...)
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>
This commit is contained in:
@@ -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"`
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
@@ -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
|
||||
@@ -99,6 +103,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
|
||||
@@ -133,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)
|
||||
}
|
||||
@@ -924,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 {
|
||||
@@ -940,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
|
||||
@@ -951,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
|
||||
}
|
||||
|
||||
|
78
tests/integration/mounts_recursive.bats
Normal file
78
tests/integration/mounts_recursive.bats
Normal file
@@ -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"* ]]
|
||||
}
|
Reference in New Issue
Block a user