mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-28 18:02:00 +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 source if set, "z" indicates shared, "Z" indicates unshared.
|
||||||
Relabel string `json:"relabel"`
|
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 are additional flags that are specific to runc.
|
||||||
Extensions int `json:"extensions"`
|
Extensions int `json:"extensions"`
|
||||||
|
|
||||||
|
|||||||
@@ -493,6 +493,9 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
|
|||||||
}
|
}
|
||||||
return mountPropagate(m, rootfs, mountLabel, mountFd)
|
return mountPropagate(m, rootfs, mountLabel, mountFd)
|
||||||
}
|
}
|
||||||
|
if err := setRecAttr(m, rootfs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1123,3 +1126,12 @@ func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd
|
|||||||
}
|
}
|
||||||
return nil
|
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 (
|
var (
|
||||||
initMapsOnce sync.Once
|
initMapsOnce sync.Once
|
||||||
namespaceMapping map[specs.LinuxNamespaceType]configs.NamespaceType
|
namespaceMapping map[specs.LinuxNamespaceType]configs.NamespaceType
|
||||||
mountPropagationMapping map[string]int
|
mountPropagationMapping map[string]int
|
||||||
|
recAttrFlags map[string]struct {
|
||||||
|
clear bool
|
||||||
|
flag uint64
|
||||||
|
}
|
||||||
mountFlags, extensionFlags map[string]struct {
|
mountFlags, extensionFlags map[string]struct {
|
||||||
clear bool
|
clear bool
|
||||||
flag int
|
flag int
|
||||||
@@ -99,6 +103,32 @@ func initMaps() {
|
|||||||
"sync": {false, unix.MS_SYNCHRONOUS},
|
"sync": {false, unix.MS_SYNCHRONOUS},
|
||||||
"symfollow": {true, unix.MS_NOSYMFOLLOW}, // since kernel 5.10
|
"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 {
|
extensionFlags = map[string]struct {
|
||||||
clear bool
|
clear bool
|
||||||
flag int
|
flag int
|
||||||
@@ -133,6 +163,9 @@ func KnownMountOptions() []string {
|
|||||||
res = append(res, k)
|
res = append(res, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for k := range recAttrFlags {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
for k := range extensionFlags {
|
for k := range extensionFlags {
|
||||||
res = append(res, k)
|
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.
|
// structure with fields that depends on options set accordingly.
|
||||||
func parseMountOptions(options []string) *configs.Mount {
|
func parseMountOptions(options []string) *configs.Mount {
|
||||||
var (
|
var (
|
||||||
data []string
|
data []string
|
||||||
m configs.Mount
|
m configs.Mount
|
||||||
|
recAttrSet, recAttrClr uint64
|
||||||
)
|
)
|
||||||
initMaps()
|
initMaps()
|
||||||
for _, o := range options {
|
for _, o := range options {
|
||||||
@@ -940,6 +974,17 @@ func parseMountOptions(options []string) *configs.Mount {
|
|||||||
}
|
}
|
||||||
} else if f, exists := mountPropagationMapping[o]; exists && f != 0 {
|
} else if f, exists := mountPropagationMapping[o]; exists && f != 0 {
|
||||||
m.PropagationFlags = append(m.PropagationFlags, f)
|
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 {
|
} else if f, exists := extensionFlags[o]; exists && f.flag != 0 {
|
||||||
if f.clear {
|
if f.clear {
|
||||||
m.Extensions &= ^f.flag
|
m.Extensions &= ^f.flag
|
||||||
@@ -951,6 +996,12 @@ func parseMountOptions(options []string) *configs.Mount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.Data = strings.Join(data, ",")
|
m.Data = strings.Join(data, ",")
|
||||||
|
if recAttrSet != 0 || recAttrClr != 0 {
|
||||||
|
m.RecAttr = &unix.MountAttr{
|
||||||
|
Attr_set: recAttrSet,
|
||||||
|
Attr_clr: recAttrClr,
|
||||||
|
}
|
||||||
|
}
|
||||||
return &m
|
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