Skip to content

Commit

Permalink
tests: remap rootfs for userns tests
Browse files Browse the repository at this point in the history
Previously, all of our userns tests worked around the remapping issue by
creating the paths that runc would attempt to create (like /proc).
However, this isn't really accurate to how real userns containers are
created, so it's much better to actually remap the rootfs.

Signed-off-by: Aleksa Sarai <[email protected]>
  • Loading branch information
cyphar committed Dec 4, 2023
1 parent a8b67a6 commit f6aecb3
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ vendor/pkg
/contrib/cmd/fs-idmap/fs-idmap
/contrib/cmd/memfd-bind/memfd-bind
/contrib/cmd/pidfd-kill/pidfd-kill
/contrib/cmd/remap-rootfs/remap-rootfs
man/man8
release
Vagrantfile
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ runc-bin: runc-dmz
$(GO_BUILD) -o runc .

.PHONY: all
all: runc recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill
all: runc recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill remap-rootfs

.PHONY: recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill
recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill:
.PHONY: recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill remap-rootfs
recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill remap-rootfs:
$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@

.PHONY: static
Expand Down
100 changes: 100 additions & 0 deletions contrib/cmd/remap-rootfs/remap-rootfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"

"github.com/opencontainers/runtime-spec/specs-go"
)

func usage() {
fmt.Println("usage: remap-rootfs <bundle>")
os.Exit(1)
}

func toHostID(mappings []specs.LinuxIDMapping, id uint32) (int, bool) {
for _, m := range mappings {
if m.ContainerID <= id && id < m.ContainerID+m.Size {
return int(m.HostID + id), true
}
}
return -1, false
}

func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip symlinks.
if info.Mode().Type() == os.ModeSymlink {
return nil
}
// Chown the file.
mode := info.Mode()
uid := info.Sys().(*syscall.Stat_t).Uid
gid := info.Sys().(*syscall.Stat_t).Gid
newUID, ok1 := toHostID(uidMap, uid)
newGID, ok2 := toHostID(gidMap, gid)
if !ok1 || !ok2 {
niceName := path
if relName, err := filepath.Rel(root, path); err == nil {
niceName = "/" + relName
}
// Skip files that cannot be mapped.
fmt.Printf("skipping file %s: cannot remap user %d:%d -> %d:%d\n", niceName, uid, gid, newUID, newGID)
return nil
}
if err := os.Chown(path, newUID, newGID); err != nil {
return err
}
// Make sure to re-apply any setid bits.
return os.Chmod(path, mode)
})
}

func Main(args []string) error {
if len(args) != 1 {
usage()
}
bundle := args[0]

configFile, err := os.Open(bundle + "/config.json")
if err != nil {
return err
}
defer configFile.Close()

var spec specs.Spec
if err := json.NewDecoder(configFile).Decode(&spec); err != nil {
return fmt.Errorf("parsing config.json: %w", err)
}

if spec.Root == nil {
return fmt.Errorf("invalid config.json: root section is null")
}
rootfs := bundle + "/" + spec.Root.Path

if spec.Linux == nil {
return fmt.Errorf("invalid config.json: linux section is null")
}
uidMap := spec.Linux.UIDMappings
gidMap := spec.Linux.GIDMappings
if uidMap == nil && gidMap == nil {
fmt.Println("skipping remapping -- no userns mappings specified")
return nil
}

return remapRootfs(rootfs, uidMap, gidMap)
}

func main() {
err := Main(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "remap-rootfs failed: %v\n", err)
os.Exit(1)
}
}
7 changes: 7 additions & 0 deletions tests/integration/helpers.bash
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
FS_IDMAP="${INTEGRATION_ROOT}/../../contrib/cmd/fs-idmap/fs-idmap"
PIDFD_KILL="${INTEGRATION_ROOT}/../../contrib/cmd/pidfd-kill/pidfd-kill"
REMAP_ROOTFS="${INTEGRATION_ROOT}/../../contrib/cmd/remap-rootfs/remap-rootfs"

# Some variables may not always be set. Set those to empty value,
# if unset, to avoid "unbound variable" error.
Expand Down Expand Up @@ -657,6 +658,12 @@ function teardown_bundle() {
remove_parent
}

function remap_rootfs() {
[ ! -v ROOT ] && return 0 # nothing to remap

"$REMAP_ROOTFS" "$ROOT/bundle"
}

function is_kernel_gte() {
local major_required minor_required
major_required=$(echo "$1" | cut -d. -f1)
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/idmap.bats
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function setup() {
# Use other owner for source-2
chown 1:1 source-2/foo.txt

mkdir -p rootfs/{proc,sys,tmp}
mkdir -p rootfs/tmp/mount-{1,2}
mkdir -p rootfs/mnt/bind-mount-{1,2}

Expand All @@ -43,6 +42,8 @@ function setup() {
]
}
] '

remap_rootfs
}

function teardown() {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ function teardown() {
update_config '.linux.namespaces += [{"type": "user"}]
| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}]
| .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]'
mkdir -p rootfs/{proc,sys,tmp}
remap_rootfs
fi
update_config '.linux.namespaces += [{"type": "time"}]
| .linux.timeOffsets = {
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/timens.bats
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ load helpers

function setup() {
setup_busybox

mkdir -p rootfs/{proc,sys,tmp}
}

function teardown() {
Expand Down Expand Up @@ -63,6 +61,7 @@ function teardown() {
update_config ' .linux.namespaces += [{"type": "user"}]
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
| .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] '
remap_rootfs

update_config '.process.args = ["cat", "/proc/self/timens_offsets"]'
update_config '.linux.namespaces += [{"type": "time"}]
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/userns.bats
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ function setup() {
# Permissions only to the owner, it is inaccessible to group/others
chmod 700 source-inaccessible-{1,2}

mkdir -p rootfs/{proc,sys,tmp}
mkdir -p rootfs/tmp/mount-{1,2}

to_umount_list="$(mktemp "$BATS_RUN_TMPDIR/userns-mounts.XXXXXX")"
if [ $EUID -eq 0 ]; then
update_config ' .linux.namespaces += [{"type": "user"}]
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
| .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] '
remap_rootfs
fi
}

Expand Down

0 comments on commit f6aecb3

Please sign in to comment.