Skip to content

Commit

Permalink
Support mount option "rro" (recursive read-only) using mount_setattr
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

Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Nov 11, 2021
1 parent cbd725e commit aa0b494
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
4 changes: 4 additions & 0 deletions libcontainer/configs/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const (
// EXT_COPYUP is a directive to copy up the contents of a directory when
// a tmpfs is mounted over it.
EXT_COPYUP = 1 << iota //nolint:golint // ignore "don't use ALL_CAPS" warning

// EXT_RRO enables recursive read-only (RRO) mount, with MOUNT_ATTR_RDONLY , AT_RECURSIVE .
// Requires kernel >= 5.12.
EXT_RRO //nolint:golint
)

type Mount struct {
Expand Down
17 changes: 16 additions & 1 deletion libcontainer/rootfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/opencontainers/runc/libcontainer/devices"
"github.com/opencontainers/runc/libcontainer/userns"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runc/libcontainer/utils/syscallutil"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -474,7 +475,11 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
return err
}
}

if m.Extensions&configs.EXT_RRO == configs.EXT_RRO {
if err := setRRO(m, rootfs); err != nil {
return err
}
}
if m.Relabel != "" {
if err := label.Validate(m.Relabel); err != nil {
return err
Expand Down Expand Up @@ -1113,3 +1118,13 @@ func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd
}
return nil
}

// setRRO sets mount attribute MOUNT_ATTR_RDONLY, with AT_RECURSIVE
func setRRO(m *configs.Mount, rootfs string) error {
return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
attr := syscallutil.MountAttr{
AttrSet: syscallutil.MOUNT_ATTR_RDONLY,
}
return syscallutil.MountSetattr(-1, procfd, syscallutil.AT_RECURSIVE, &attr)
})
}
3 changes: 3 additions & 0 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,9 @@ func parseMountOptions(options []string) (int, []int, string, int) {
flag int
}{
"tmpcopyup": {false, configs.EXT_COPYUP},
// rro = recursive read-only.
// "rro" option string is being proposed to `mount(8)` too: https://github.com/util-linux/util-linux/issues/1501
"rro": {false, configs.EXT_RRO},
}
for _, o := range options {
// If the option does not exist in the flags table or the flag
Expand Down
56 changes: 56 additions & 0 deletions libcontainer/utils/syscallutil/syscallutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Package syscallutil provdes addenda to golang.org/x/sys/unix
package syscallutil

import (
"unsafe"

"golang.org/x/sys/unix"
)

// nolint
const (
AT_EMPTY_PATH = unix.AT_EMPTY_PATH
AT_RECURSIVE = 0x8000 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/fcntl.h#L112
AT_SYMLINK_NOFOLLOW = unix.AT_SYMLINK_NOFOLLOW
AT_NO_AUTOMOUNT = unix.AT_NO_AUTOMOUNT
MOUNT_ATTR_RDONLY = 0x00000001 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L113
MOUNT_ATTR_NOSUID = 0x00000002
MOUNT_ATTR_NODEV = 0x00000004
MOUNT_ATTR_NOEXEC = 0x00000008
MOUNT_ATTR__ATIME = 0x00000070
MOUNT_ATTR_RELATIME = 0x00000000
MOUNT_ATTR_NOATIME = 0x00000010
MOUNT_ATTR_STRICTATIME = 0x00000020
MOUNT_ATTR_NODIRATIME = 0x00000080
MOUNT_ATTR_IDMAP = 0x00100000
MOUNT_ATTR_SIZE_VER0 = 32 // https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L135
)

// MountAttr corresponds to struct mount_attr, version 0, appeared in kernel 5.12.
// https://github.com/torvalds/linux/blob/v5.12/include/uapi/linux/mount.h#L124-L132
type MountAttr struct {
AttrSet uint64 // __u64 attr_set
AttrClr uint64 // __u64 attr_clr
Propagation uint64 // __u64 propagation
UsernsFd uint64 // __u64 userns_fd
}

// MountSetattr is a wrapper for mount_setattr(2).
//
// int syscall(SYS_mount_setattr, int dirfd, const char *pathname, unsigned int flags, struct mount_attr *attr, size_t size);
//
// Requires kernel >= 5.12.
// https://man7.org/linux/man-pages/man2/mount_setattr.2.html
func MountSetattr(dirfd int, pathname string, flags uint, attr *MountAttr) error {
pathnamePtr, err := unix.BytePtrFromString(pathname)
if err != nil {
return err
}
_, _, errno := unix.Syscall6(unix.SYS_MOUNT_SETATTR,
uintptr(dirfd), uintptr(unsafe.Pointer(pathnamePtr)), uintptr(flags),
uintptr(unsafe.Pointer(attr)), unsafe.Sizeof(*attr), 0)
if errno != 0 {
return errno
}
return nil
}
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 aa0b494

Please sign in to comment.