mount: add support for ridmap and idmap

ridmap indicates that the id mapping should be applied recursively (only
really relevant for rbind mount entries), and idmap indicates that it
should not be applied recursively (the default). If no mappings are
specified for the mount, we use the userns configuration of the
container. This matches the behaviour in the currently-unreleased
runtime-spec.

This includes a minor change to the state.json serialisation format, but
because there has been no released version of runc with commit
fbf183c6f8 ("Add uid and gid mappings to mounts"), we can safely make
this change without affecting running containers. Doing it this way
makes it much easier to handle m.IsIDMapped() and indicating that a
mapping has been specified.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
Aleksa Sarai
2023-11-16 17:28:53 +11:00
parent 7795ca4668
commit 3b57e45cbf
7 changed files with 592 additions and 56 deletions

View File

@@ -3,5 +3,5 @@ package configs
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_COPYUP = 1 << iota //nolint:golint,revive // ignore "don't use ALL_CAPS" warning
)

View File

@@ -2,6 +2,24 @@ package configs
import "golang.org/x/sys/unix"
type MountIDMapping struct {
// Recursive indicates if the mapping needs to be recursive.
Recursive bool `json:"recursive"`
// UserNSPath is a path to a user namespace that indicates the necessary
// id-mappings for MOUNT_ATTR_IDMAP. If set to non-"", UIDMappings and
// GIDMappings must be set to nil.
UserNSPath string `json:"userns_path,omitempty"`
// UIDMappings is the uid mapping set for this mount, to be used with
// MOUNT_ATTR_IDMAP.
UIDMappings []IDMap `json:"uid_mappings,omitempty"`
// GIDMappings is the gid mapping set for this mount, to be used with
// MOUNT_ATTR_IDMAP.
GIDMappings []IDMap `json:"gid_mappings,omitempty"`
}
type Mount struct {
// Source path for the mount.
Source string `json:"source"`
@@ -34,17 +52,9 @@ type Mount struct {
// Extensions are additional flags that are specific to runc.
Extensions int `json:"extensions"`
// UIDMappings is used to changing file user owners w/o calling chown.
// Note that, the underlying filesystem should support this feature to be
// used.
// Every mount point could have its own mapping.
UIDMappings []IDMap `json:"uid_mappings,omitempty"`
// GIDMappings is used to changing file group owners w/o calling chown.
// Note that, the underlying filesystem should support this feature to be
// used.
// Every mount point could have its own mapping.
GIDMappings []IDMap `json:"gid_mappings,omitempty"`
// Mapping is the MOUNT_ATTR_IDMAP configuration for the mount. If non-nil,
// the mount is configured to use MOUNT_ATTR_IDMAP-style id mappings.
IDMapping *MountIDMapping `json:"id_mapping,omitempty"`
}
func (m *Mount) IsBind() bool {
@@ -52,5 +62,5 @@ func (m *Mount) IsBind() bool {
}
func (m *Mount) IsIDMapped() bool {
return len(m.UIDMappings) > 0 || len(m.GIDMappings) > 0
return m.IDMapping != nil
}

View File

@@ -309,6 +309,13 @@ func checkBindOptions(m *configs.Mount) error {
}
func checkIDMapMounts(config *configs.Config, m *configs.Mount) error {
// Make sure MOUNT_ATTR_IDMAP is not set on any of our mounts. This
// attribute is handled differently to all other attributes (through
// m.IDMapping), so make sure we never store it in the actual config. This
// really shouldn't ever happen.
if m.RecAttr != nil && (m.RecAttr.Attr_set|m.RecAttr.Attr_clr)&unix.MOUNT_ATTR_IDMAP != 0 {
return errors.New("mount configuration cannot contain recAttr for MOUNT_ATTR_IDMAP")
}
if !m.IsIDMapped() {
return nil
}
@@ -318,6 +325,16 @@ func checkIDMapMounts(config *configs.Config, m *configs.Mount) error {
if config.RootlessEUID {
return errors.New("id-mapped mounts are not supported for rootless containers")
}
if m.IDMapping.UserNSPath == "" {
if len(m.IDMapping.UIDMappings) == 0 || len(m.IDMapping.GIDMappings) == 0 {
return errors.New("id-mapped mounts must have both uid and gid mappings specified")
}
} else {
if m.IDMapping.UIDMappings != nil || m.IDMapping.GIDMappings != nil {
// should never happen
return errors.New("[internal error] id-mapped mounts cannot have both userns_path and uid and gid mappings specified")
}
}
return nil
}

View File

@@ -504,17 +504,82 @@ func TestValidateIDMapMounts(t *testing.T) {
config *configs.Config
}{
{
name: "idmap mount without bind opt specified",
name: "idmap non-bind mount",
isErr: true,
config: &configs.Config{
UIDMappings: mapping,
GIDMappings: mapping,
Mounts: []*configs.Mount{
{
Source: "/dev/sda1",
Destination: "/abs/path/",
Device: "ext4",
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
},
{
name: "idmap option non-bind mount",
isErr: true,
config: &configs.Config{
Mounts: []*configs.Mount{
{
Source: "/dev/sda1",
Destination: "/abs/path/",
Device: "ext4",
IDMapping: &configs.MountIDMapping{},
},
},
},
},
{
name: "ridmap option non-bind mount",
isErr: true,
config: &configs.Config{
Mounts: []*configs.Mount{
{
Source: "/dev/sda1",
Destination: "/abs/path/",
Device: "ext4",
IDMapping: &configs.MountIDMapping{
Recursive: true,
},
},
},
},
},
{
name: "idmap mount no uid mapping",
isErr: true,
config: &configs.Config{
Mounts: []*configs.Mount{
{
Source: "/abs/path/",
Destination: "/abs/path/",
UIDMappings: mapping,
GIDMappings: mapping,
Flags: unix.MS_BIND,
IDMapping: &configs.MountIDMapping{
GIDMappings: mapping,
},
},
},
},
},
{
name: "idmap mount no gid mapping",
isErr: true,
config: &configs.Config{
Mounts: []*configs.Mount{
{
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
},
},
},
},
@@ -531,8 +596,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -547,8 +614,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "./rel/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -563,8 +632,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/abs/path/",
Destination: "./rel/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -579,8 +650,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/another-abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -595,8 +668,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/another-abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND | unix.MS_RDONLY,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -609,8 +684,10 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: mapping,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: mapping,
},
},
},
},
@@ -625,35 +702,69 @@ func TestValidateIDMapMounts(t *testing.T) {
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: []configs.IDMap{
{
ContainerID: 10,
HostID: 10,
Size: 1,
IDMapping: &configs.MountIDMapping{
UIDMappings: []configs.IDMap{
{
ContainerID: 10,
HostID: 10,
Size: 1,
},
},
GIDMappings: mapping,
},
},
},
},
},
{
name: "idmap mounts with different userns and mount mappings",
config: &configs.Config{
UIDMappings: mapping,
GIDMappings: mapping,
Mounts: []*configs.Mount{
{
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
IDMapping: &configs.MountIDMapping{
UIDMappings: mapping,
GIDMappings: []configs.IDMap{
{
ContainerID: 10,
HostID: 10,
Size: 1,
},
},
},
GIDMappings: mapping,
},
},
},
},
{
name: "idmap mounts with different userns and mount mappings",
name: "mount with 'idmap' option but no mappings",
isErr: true,
config: &configs.Config{
UIDMappings: mapping,
GIDMappings: mapping,
Mounts: []*configs.Mount{
{
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
UIDMappings: mapping,
GIDMappings: []configs.IDMap{
{
ContainerID: 10,
HostID: 10,
Size: 1,
},
IDMapping: &configs.MountIDMapping{},
},
},
},
},
{
name: "mount with 'ridmap' option but no mappings",
isErr: true,
config: &configs.Config{
Mounts: []*configs.Mount{
{
Source: "/abs/path/",
Destination: "/abs/path/",
Flags: unix.MS_BIND,
IDMapping: &configs.MountIDMapping{
Recursive: true,
},
},
},

View File

@@ -229,16 +229,32 @@ func mountFd(nsHandles *userns.Handles, m *configs.Mount) (*mountSource, error)
sourceType = mountSourceOpenTree
// Configure the id mapping.
usernsFile, err := nsHandles.Get(userns.Mapping{
UIDMappings: m.UIDMappings,
GIDMappings: m.GIDMappings,
})
if err != nil {
return nil, fmt.Errorf("failed to create userns for %s id-mapping: %w", m.Source, err)
var usernsFile *os.File
if m.IDMapping.UserNSPath == "" {
usernsFile, err = nsHandles.Get(userns.Mapping{
UIDMappings: m.IDMapping.UIDMappings,
GIDMappings: m.IDMapping.GIDMappings,
})
if err != nil {
return nil, fmt.Errorf("failed to create userns for %s id-mapping: %w", m.Source, err)
}
} else {
usernsFile, err = os.Open(m.IDMapping.UserNSPath)
if err != nil {
return nil, fmt.Errorf("failed to open existing userns for %s id-mapping: %w", m.Source, err)
}
}
defer usernsFile.Close()
if err := unix.MountSetattr(int(mountFile.Fd()), "", unix.AT_EMPTY_PATH, &unix.MountAttr{
setAttrFlags := uint(unix.AT_EMPTY_PATH)
// If the mount has "ridmap" set, we apply the configuration
// recursively. This allows you to create "rbind" mounts where only
// the top-level mount has an idmapping. I'm not sure why you'd
// want that, but still...
if m.IDMapping.Recursive {
setAttrFlags |= unix.AT_RECURSIVE
}
if err := unix.MountSetattr(int(mountFile.Fd()), "", setAttrFlags, &unix.MountAttr{
Attr_set: unix.MOUNT_ATTR_IDMAP,
Userns_fd: uint64(usernsFile.Fd()),
}); err != nil {

View File

@@ -38,6 +38,7 @@ var (
clear bool
flag int
}
complexFlags map[string]func(*configs.Mount)
)
func initMaps() {
@@ -126,7 +127,6 @@ func initMaps() {
"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 {
@@ -135,6 +135,17 @@ func initMaps() {
}{
"tmpcopyup": {false, configs.EXT_COPYUP},
}
complexFlags = map[string]func(*configs.Mount){
"idmap": func(m *configs.Mount) {
m.IDMapping = new(configs.MountIDMapping)
m.IDMapping.Recursive = false // noop
},
"ridmap": func(m *configs.Mount) {
m.IDMapping = new(configs.MountIDMapping)
m.IDMapping.Recursive = true
},
}
})
}
@@ -415,6 +426,19 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
if err := setupUserNamespace(spec, config); err != nil {
return nil, err
}
// For idmap and ridmap mounts without explicit mappings, use the
// ones from the container's userns. If we are joining another
// userns, stash the path.
for _, m := range config.Mounts {
if m.IDMapping != nil && m.IDMapping.UIDMappings == nil && m.IDMapping.GIDMappings == nil {
if path := config.Namespaces.PathOf(configs.NEWUSER); path != "" {
m.IDMapping.UserNSPath = path
} else {
m.IDMapping.UIDMappings = config.UIDMappings
m.IDMapping.GIDMappings = config.GIDMappings
}
}
}
}
config.MaskPaths = spec.Linux.MaskedPaths
config.ReadonlyPaths = spec.Linux.ReadonlyPaths
@@ -447,6 +471,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
Domain: domain,
}
}
}
// Set the host UID that should own the container's cgroup.
@@ -552,8 +577,14 @@ func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error)
}
}
mnt.UIDMappings = toConfigIDMap(m.UIDMappings)
mnt.GIDMappings = toConfigIDMap(m.GIDMappings)
if m.UIDMappings != nil || m.GIDMappings != nil {
if mnt.IDMapping == nil {
// Neither "idmap" nor "ridmap" were specified.
mnt.IDMapping = new(configs.MountIDMapping)
}
mnt.IDMapping.UIDMappings = toConfigIDMap(m.UIDMappings)
mnt.IDMapping.GIDMappings = toConfigIDMap(m.GIDMappings)
}
// None of the mount arguments can contain a null byte. Normally such
// strings would either cause some other failure or would just be truncated
@@ -1046,12 +1077,14 @@ func parseMountOptions(options []string) *configs.Mount {
recAttrClr |= unix.MOUNT_ATTR__ATIME
}
}
} else if f, exists := extensionFlags[o]; exists && f.flag != 0 {
} else if f, exists := extensionFlags[o]; exists {
if f.clear {
m.Extensions &= ^f.flag
} else {
m.Extensions |= f.flag
}
} else if fn, exists := complexFlags[o]; exists {
fn(&m)
} else {
data = append(data, o)
}

View File

@@ -5,7 +5,6 @@ load helpers
function setup() {
OVERFLOW_UID="$(cat /proc/sys/kernel/overflowuid)"
OVERFLOW_GID="$(cat /proc/sys/kernel/overflowgid)"
requires root
requires_kernel 5.12
@@ -37,12 +36,34 @@ function setup() {
# Add a symlink-containing source.
ln -s source-multi1 source-multi1-symlink
# Add some top-level files in the mount tree.
mkdir -p mnt-subtree/multi{1,2}
touch mnt-subtree/{foo,bar,baz}.txt
chown 100:211 mnt-subtree/foo.txt
chown 200:222 mnt-subtree/bar.txt
chown 300:233 mnt-subtree/baz.txt
mounts_file="$PWD/.all-mounts"
echo -n >"$mounts_file"
}
function teardown() {
if [ -v mounts_file ]; then
xargs -n 1 -a "$mounts_file" -- umount -l
rm -f "$mounts_file"
fi
teardown_bundle
}
function setup_host_bind_mount() {
src="$1"
dst="$2"
mount --bind "$src" "$dst"
echo "$dst" >>"$mounts_file"
}
function setup_idmap_userns() {
update_config '.linux.namespaces += [{"type": "user"}]
| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}]
@@ -341,3 +362,331 @@ function setup_idmap_basic_mount() {
[[ "$output" == *"=/tmp/mount-multi1/bar.txt:2000=2202="* ]]
[[ "$output" == *"=/tmp/mount-multi1/baz.txt:3000=3303="* ]]
}
@test "idmap mount (non-recursive idmap) [userns]" {
setup_idmap_userns
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:1000=1101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:2000=2202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:3000=3303="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
}
@test "idmap mount (non-recursive idmap) [no userns]" {
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:101000=101101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:102000=102202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:103000=103303="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:101=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:102=233="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:200=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:201=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:202=233="* ]]
}
@test "idmap mount (idmap flag) [userns]" {
setup_idmap_userns
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "idmap"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:1000=1101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:2000=2202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:3000=3303="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
}
@test "idmap mount (idmap flag) [no userns]" {
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "idmap"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:101000=101101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:102000=102202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:103000=103303="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:101=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:102=233="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:200=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:201=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:202=233="* ]]
}
@test "idmap mount (ridmap flag) [userns]" {
setup_idmap_userns
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "ridmap"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:1000=1101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:2000=2202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:3000=3303="* ]]
# The child mounts have the same mapping applied.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:1000=1101="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:1001=2202="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:1002=3303="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:2000=1101="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:2001=2202="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:2002=3303="* ]]
}
@test "idmap mount (ridmap flag) [no userns]" {
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "ridmap"],
"uidMappings": [
{"containerID": 100, "hostID": 101000, "size": 3},
{"containerID": 200, "hostID": 102000, "size": 3},
{"containerID": 300, "hostID": 103000, "size": 3}
],
"gidMappings": [
{"containerID": 210, "hostID": 101100, "size": 10},
{"containerID": 220, "hostID": 102200, "size": 10},
{"containerID": 230, "hostID": 103300, "size": 10}
]
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:101000=101101="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:102000=102202="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:103000=103303="* ]]
# The child mounts have the same mapping applied.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:101000=101101="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:101001=102202="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:101002=103303="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:102000=101101="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:102001=102202="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:102002=103303="* ]]
}
@test "idmap mount (idmap flag, implied mapping) [userns]" {
setup_idmap_userns
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "idmap"],
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:200=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:300=233="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
}
@test "idmap mount (ridmap flag, implied mapping) [userns]" {
setup_idmap_userns
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "ridmap"],
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:200=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:300=233="* ]]
# The child mounts have the same mapping applied.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:101=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:102=233="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:200=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:201=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:202=233="* ]]
}
@test "idmap mount (idmap flag, implied mapping, userns join) [userns]" {
# Create a detached container with the id-mapping we want.
cp config.json config.json.bak
update_config '.linux.namespaces += [{"type": "user"}]
| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}]
| .linux.gidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}]'
update_config '.process.args = ["sleep", "infinity"]'
runc run -d --console-socket "$CONSOLE_SOCKET" target_userns
[ "$status" -eq 0 ]
# Configure our container to attach to the first container's userns.
target_pid="$(__runc state target_userns | jq .pid)"
update_config '.linux.namespaces |= map(if .type == "user" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end)'
update_config 'del(.linux.uidMappings) | del(.linux.gidMappings)'
setup_host_bind_mount "source-multi1/" "mnt-subtree/multi1"
setup_host_bind_mount "source-multi2/" "mnt-subtree/multi2"
update_config '.mounts += [
{
"source": "mnt-subtree/",
"destination": "/tmp/mount-tree",
"options": ["rbind", "idmap"],
}
]'
update_config '.process.args = ["bash", "-c", "stat -c =%n:%u=%g= /tmp/mount-tree{,/multi1,/multi2}/{foo,bar,baz}.txt"]'
runc run test_debian
[ "$status" -eq 0 ]
[[ "$output" == *"=/tmp/mount-tree/foo.txt:100=211="* ]]
[[ "$output" == *"=/tmp/mount-tree/bar.txt:200=222="* ]]
[[ "$output" == *"=/tmp/mount-tree/baz.txt:300=233="* ]]
# Because we used "idmap", the child mounts were not remapped recursively.
[[ "$output" == *"=/tmp/mount-tree/multi1/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi1/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/foo.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/bar.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
[[ "$output" == *"=/tmp/mount-tree/multi2/baz.txt:$OVERFLOW_UID=$OVERFLOW_GID="* ]]
}