Skip to content

Commit 2f6d86f

Browse files
committed
Support mount option "rro" (recursive read-only) using MOUNT_ATTR_RDONLY + AT_RECURSIVE
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 Signed-off-by: Akihiro Suda <[email protected]>
1 parent cbd725e commit 2f6d86f

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

libcontainer/configs/mount.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ const (
66
// EXT_COPYUP is a directive to copy up the contents of a directory when
77
// a tmpfs is mounted over it.
88
EXT_COPYUP = 1 << iota //nolint:golint // ignore "don't use ALL_CAPS" warning
9+
10+
// EXT_RRO enables recursive read-only (RRO) mount, with MOUNT_ATTR_RDONLY , AT_RECURSIVE .
11+
// Requires kernel >= 5.12.
12+
EXT_RRO //nolint:golint
913
)
1014

1115
type Mount struct {

libcontainer/rootfs_linux.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/opencontainers/runc/libcontainer/devices"
2222
"github.com/opencontainers/runc/libcontainer/userns"
2323
"github.com/opencontainers/runc/libcontainer/utils"
24+
"github.com/opencontainers/runc/libcontainer/utils/syscallutil"
2425
"github.com/opencontainers/runtime-spec/specs-go"
2526
"github.com/opencontainers/selinux/go-selinux/label"
2627
"github.com/sirupsen/logrus"
@@ -474,7 +475,11 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
474475
return err
475476
}
476477
}
477-
478+
if m.Extensions&configs.EXT_RRO == configs.EXT_RRO {
479+
if err := setRRO(m, rootfs); err != nil {
480+
return err
481+
}
482+
}
478483
if m.Relabel != "" {
479484
if err := label.Validate(m.Relabel); err != nil {
480485
return err
@@ -1113,3 +1118,13 @@ func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd
11131118
}
11141119
return nil
11151120
}
1121+
1122+
// setRRO sets mount attribute MOUNT_ATTR_RDONLY, with AT_RECURSIVE
1123+
func setRRO(m *configs.Mount, rootfs string) error {
1124+
return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
1125+
attr := syscallutil.MountAttr{
1126+
AttrSet: syscallutil.MOUNT_ATTR_RDONLY,
1127+
}
1128+
return syscallutil.MountSetattr(-1, procfd, syscallutil.AT_RECURSIVE, &attr)
1129+
})
1130+
}

libcontainer/specconv/spec_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,9 @@ func parseMountOptions(options []string) (int, []int, string, int) {
821821
flag int
822822
}{
823823
"tmpcopyup": {false, configs.EXT_COPYUP},
824+
// rro = recursive read-only.
825+
// "rro" option string is being proposed to `mount(8)` too: https://github.com/util-linux/util-linux/issues/1501
826+
"rro": {false, configs.EXT_RRO},
824827
}
825828
for _, o := range options {
826829
// If the option does not exist in the flags table or the flag
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Package syscallutil provdes addenda to golang.org/x/sys/unix
2+
package syscallutil
3+
4+
import (
5+
"unsafe"
6+
7+
"golang.org/x/sys/unix"
8+
)
9+
10+
// nolint
11+
const (
12+
AT_EMPTY_PATH = unix.AT_EMPTY_PATH
13+
AT_RECURSIVE = 0x8000 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/fcntl.h#L112
14+
AT_SYMLINK_NOFOLLOW = unix.AT_SYMLINK_NOFOLLOW
15+
AT_NO_AUTOMOUNT = unix.AT_NO_AUTOMOUNT
16+
MOUNT_ATTR_RDONLY = 0x00000001 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L113
17+
MOUNT_ATTR_NOSUID = 0x00000002
18+
MOUNT_ATTR_NODEV = 0x00000004
19+
MOUNT_ATTR_NOEXEC = 0x00000008
20+
MOUNT_ATTR__ATIME = 0x00000070
21+
MOUNT_ATTR_RELATIME = 0x00000000
22+
MOUNT_ATTR_NOATIME = 0x00000010
23+
MOUNT_ATTR_STRICTATIME = 0x00000020
24+
MOUNT_ATTR_NODIRATIME = 0x00000080
25+
MOUNT_ATTR_IDMAP = 0x00100000
26+
MOUNT_ATTR_SIZE_VER0 = 32 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L135
27+
)
28+
29+
// MountAttr corresponds to struct mount_attr, version 0, appeared in kernel 5.12.
30+
// https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L124-L132
31+
type MountAttr struct {
32+
AttrSet uint64 // __u64 attr_set
33+
AttrClr uint64 // __u64 attr_clr
34+
Propagation uint64 // __u64 propagation
35+
UsernsFd uint64 // __u64 userns_fd
36+
}
37+
38+
// MountSetattr is a wrapper for mount_setattr(2).
39+
//
40+
// int syscall(SYS_mount_setattr, int dirfd, const char *pathname, unsigned int flags, struct mount_attr *attr, size_t size);
41+
//
42+
// Requires kernel >= 5.12.
43+
// https://man7.org/linux/man-pages/man2/mount_setattr.2.html
44+
func MountSetattr(dirfd int, pathname string, flags uint, attr *MountAttr) error {
45+
pathnamePtr, err := unix.BytePtrFromString(pathname)
46+
if err != nil {
47+
return err
48+
}
49+
_, _, errno := unix.Syscall6(unix.SYS_MOUNT_SETATTR,
50+
uintptr(dirfd), uintptr(unsafe.Pointer(pathnamePtr)), uintptr(flags),
51+
uintptr(unsafe.Pointer(attr)), unsafe.Sizeof(*attr), 0)
52+
if errno != 0 {
53+
return errno
54+
}
55+
return nil
56+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
TESTVOLUME="${BATS_RUN_TMPDIR}/mounts_recursive"
6+
7+
function setup_volume() {
8+
# requires root (in the current user namespace) to mount tmpfs outside runc
9+
requires root
10+
11+
mkdir -p "${TESTVOLUME}"
12+
mount -t tmpfs none "${TESTVOLUME}"
13+
echo "foo" >"${TESTVOLUME}/foo"
14+
15+
mkdir "${TESTVOLUME}/subvol"
16+
mount -t tmpfs none "${TESTVOLUME}/subvol"
17+
echo "bar" >"${TESTVOLUME}/subvol/bar"
18+
}
19+
20+
function teardown_volume() {
21+
umount -R "${TESTVOLUME}"
22+
}
23+
24+
function setup() {
25+
setup_volume
26+
setup_busybox
27+
}
28+
29+
function teardown() {
30+
teardown_volume
31+
teardown_bundle
32+
}
33+
34+
@test "runc run [rbind,ro mount is read-only but not recursively]" {
35+
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"ro\"]}]"
36+
37+
runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_ro
38+
[ "$status" -eq 0 ]
39+
40+
runc exec test_rbind_ro touch /mnt/foo
41+
[ "$status" -eq 1 ]
42+
[[ "${output}" == *"Read-only file system"* ]]
43+
44+
runc exec test_rbind_ro touch /mnt/subvol/bar
45+
[ "$status" -eq 0 ]
46+
}
47+
48+
@test "runc run [rbind,rro mount is recursively read-only]" {
49+
requires_kernel 5.12
50+
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"rro\"]}]"
51+
52+
runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_rro
53+
[ "$status" -eq 0 ]
54+
55+
runc exec test_rbind_rro touch /mnt/foo
56+
[ "$status" -eq 1 ]
57+
[[ "${output}" == *"Read-only file system"* ]]
58+
59+
runc exec test_rbind_rro touch /mnt/subvol/bar
60+
[ "$status" -eq 1 ]
61+
[[ "${output}" == *"Read-only file system"* ]]
62+
}
63+
64+
@test "runc run [rbind,ro,rro mount is recursively read-only too]" {
65+
requires_kernel 5.12
66+
update_config ".mounts += [{source: \"${TESTVOLUME}\" , destination: \"/mnt\", options: [\"rbind\",\"ro\",\"rro\"]}]"
67+
68+
runc run -d --console-socket "$CONSOLE_SOCKET" test_rbind_ro_rro
69+
[ "$status" -eq 0 ]
70+
71+
runc exec test_rbind_ro_rro touch /mnt/foo
72+
[ "$status" -eq 1 ]
73+
[[ "${output}" == *"Read-only file system"* ]]
74+
75+
runc exec test_rbind_ro_rro touch /mnt/subvol/bar
76+
[ "$status" -eq 1 ]
77+
[[ "${output}" == *"Read-only file system"* ]]
78+
}

0 commit comments

Comments
 (0)