mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
tests: remap rootfs for userns tests
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 <cyphar@cyphar.com>
This commit is contained in:
@@ -7,6 +7,7 @@ vendor/pkg
|
|||||||
/contrib/cmd/fs-idmap/fs-idmap
|
/contrib/cmd/fs-idmap/fs-idmap
|
||||||
/contrib/cmd/memfd-bind/memfd-bind
|
/contrib/cmd/memfd-bind/memfd-bind
|
||||||
/contrib/cmd/pidfd-kill/pidfd-kill
|
/contrib/cmd/pidfd-kill/pidfd-kill
|
||||||
|
/contrib/cmd/remap-rootfs/remap-rootfs
|
||||||
man/man8
|
man/man8
|
||||||
release
|
release
|
||||||
Vagrantfile
|
Vagrantfile
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ runc-bin: runc-dmz
|
|||||||
$(GO_BUILD) -o runc .
|
$(GO_BUILD) -o runc .
|
||||||
|
|
||||||
.PHONY: all
|
.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
|
.PHONY: recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill remap-rootfs
|
||||||
recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill:
|
recvtty sd-helper seccompagent fs-idmap memfd-bind pidfd-kill remap-rootfs:
|
||||||
$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@
|
$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@
|
||||||
|
|
||||||
.PHONY: static
|
.PHONY: static
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const usage = `contrib/cmd/remap-rootfs
|
||||||
|
|
||||||
|
remap-rootfs is a helper tool to remap the root filesystem of a Open Container
|
||||||
|
Initiative bundle using user namespaces such that the file owners are remapped
|
||||||
|
from "host" mappings to the user namespace's mappings.
|
||||||
|
|
||||||
|
Effectively, this is a slightly more complicated 'chown -R', and is primarily
|
||||||
|
used within runc's integration tests to remap the test filesystem to match the
|
||||||
|
test user namespace. Note that calling remap-rootfs multiple times, or changing
|
||||||
|
the mapping and then calling remap-rootfs will likely produce incorrect results
|
||||||
|
because we do not "un-map" any pre-applied mappings from previous remap-rootfs
|
||||||
|
calls.
|
||||||
|
|
||||||
|
Note that the bundle is assumed to be produced by a trusted source, and thus
|
||||||
|
malicious configuration files will likely not be handled safely.
|
||||||
|
|
||||||
|
To use remap-rootfs, simply pass it the path to an OCI bundle (a directory
|
||||||
|
containing a config.json):
|
||||||
|
|
||||||
|
$ sudo remap-rootfs ./bundle
|
||||||
|
`
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type inodeID struct {
|
||||||
|
Dev, Ino uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInodeID(st *syscall.Stat_t) inodeID {
|
||||||
|
return inodeID{Dev: st.Dev, Ino: st.Ino}
|
||||||
|
}
|
||||||
|
|
||||||
|
func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error {
|
||||||
|
seenInodes := make(map[inodeID]struct{})
|
||||||
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := info.Mode()
|
||||||
|
st := info.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
|
// Skip symlinks.
|
||||||
|
if mode.Type() == os.ModeSymlink {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Skip hard-links to files we've already remapped.
|
||||||
|
id := toInodeID(st)
|
||||||
|
if _, seen := seenInodes[id]; seen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seenInodes[id] = struct{}{}
|
||||||
|
|
||||||
|
// Calculate the new uid:gid.
|
||||||
|
uid := st.Uid
|
||||||
|
newUID, ok1 := toHostID(uidMap, uid)
|
||||||
|
gid := st.Gid
|
||||||
|
newGID, ok2 := toHostID(gidMap, gid)
|
||||||
|
|
||||||
|
// Skip files that cannot be mapped.
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
niceName := path
|
||||||
|
if relName, err := filepath.Rel(root, path); err == nil {
|
||||||
|
niceName = "/" + relName
|
||||||
|
}
|
||||||
|
fmt.Printf("skipping file %s: cannot remap user %d:%d -> %d:%d\n", niceName, uid, gid, newUID, newGID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.Lchown(path, newUID, newGID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Re-apply any setid bits that would be cleared due to chown(2).
|
||||||
|
return os.Chmod(path, mode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "remap-rootfs"
|
||||||
|
app.Usage = usage
|
||||||
|
|
||||||
|
app.Action = func(ctx *cli.Context) error {
|
||||||
|
args := ctx.Args()
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("exactly one bundle argument must be provided")
|
||||||
|
}
|
||||||
|
bundle := args[0]
|
||||||
|
|
||||||
|
configFile, err := os.Open(filepath.Join(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 errors.New("invalid config.json: root section is null")
|
||||||
|
}
|
||||||
|
rootfs := filepath.Join(bundle, spec.Root.Path)
|
||||||
|
|
||||||
|
if spec.Linux == nil {
|
||||||
|
return errors.New("invalid config.json: linux section is null")
|
||||||
|
}
|
||||||
|
uidMap := spec.Linux.UIDMappings
|
||||||
|
gidMap := spec.Linux.GIDMappings
|
||||||
|
if len(uidMap) == 0 && len(gidMap) == 0 {
|
||||||
|
fmt.Println("skipping remapping -- no userns mappings specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return remapRootfs(rootfs, uidMap, gidMap)
|
||||||
|
}
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
|
|||||||
SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
|
SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
|
||||||
FS_IDMAP="${INTEGRATION_ROOT}/../../contrib/cmd/fs-idmap/fs-idmap"
|
FS_IDMAP="${INTEGRATION_ROOT}/../../contrib/cmd/fs-idmap/fs-idmap"
|
||||||
PIDFD_KILL="${INTEGRATION_ROOT}/../../contrib/cmd/pidfd-kill/pidfd-kill"
|
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,
|
# Some variables may not always be set. Set those to empty value,
|
||||||
# if unset, to avoid "unbound variable" error.
|
# if unset, to avoid "unbound variable" error.
|
||||||
@@ -657,6 +658,12 @@ function teardown_bundle() {
|
|||||||
remove_parent
|
remove_parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remap_rootfs() {
|
||||||
|
[ ! -v ROOT ] && return 0 # nothing to remap
|
||||||
|
|
||||||
|
"$REMAP_ROOTFS" "$ROOT/bundle"
|
||||||
|
}
|
||||||
|
|
||||||
function is_kernel_gte() {
|
function is_kernel_gte() {
|
||||||
local major_required minor_required
|
local major_required minor_required
|
||||||
major_required=$(echo "$1" | cut -d. -f1)
|
major_required=$(echo "$1" | cut -d. -f1)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function setup() {
|
|||||||
# Use other owner for source-2
|
# Use other owner for source-2
|
||||||
chown 1:1 source-2/foo.txt
|
chown 1:1 source-2/foo.txt
|
||||||
|
|
||||||
mkdir -p rootfs/{proc,sys,tmp}
|
|
||||||
mkdir -p rootfs/tmp/mount-{1,2}
|
mkdir -p rootfs/tmp/mount-{1,2}
|
||||||
mkdir -p rootfs/mnt/bind-mount-{1,2}
|
mkdir -p rootfs/mnt/bind-mount-{1,2}
|
||||||
|
|
||||||
@@ -43,6 +42,8 @@ function setup() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
] '
|
] '
|
||||||
|
|
||||||
|
remap_rootfs
|
||||||
}
|
}
|
||||||
|
|
||||||
function teardown() {
|
function teardown() {
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ function teardown() {
|
|||||||
update_config '.linux.namespaces += [{"type": "user"}]
|
update_config '.linux.namespaces += [{"type": "user"}]
|
||||||
| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}]
|
| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}]
|
||||||
| .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]'
|
| .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]'
|
||||||
mkdir -p rootfs/{proc,sys,tmp}
|
remap_rootfs
|
||||||
fi
|
fi
|
||||||
update_config '.linux.namespaces += [{"type": "time"}]
|
update_config '.linux.namespaces += [{"type": "time"}]
|
||||||
| .linux.timeOffsets = {
|
| .linux.timeOffsets = {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ load helpers
|
|||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
setup_busybox
|
setup_busybox
|
||||||
|
|
||||||
mkdir -p rootfs/{proc,sys,tmp}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function teardown() {
|
function teardown() {
|
||||||
@@ -63,6 +61,7 @@ function teardown() {
|
|||||||
update_config ' .linux.namespaces += [{"type": "user"}]
|
update_config ' .linux.namespaces += [{"type": "user"}]
|
||||||
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
|
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
|
||||||
| .linux.gidMappings += [{"hostID": 200000, "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 '.process.args = ["cat", "/proc/self/timens_offsets"]'
|
||||||
update_config '.linux.namespaces += [{"type": "time"}]
|
update_config '.linux.namespaces += [{"type": "time"}]
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ function setup() {
|
|||||||
# Permissions only to the owner, it is inaccessible to group/others
|
# Permissions only to the owner, it is inaccessible to group/others
|
||||||
chmod 700 source-inaccessible-{1,2}
|
chmod 700 source-inaccessible-{1,2}
|
||||||
|
|
||||||
mkdir -p rootfs/{proc,sys,tmp}
|
|
||||||
mkdir -p rootfs/tmp/mount-{1,2}
|
mkdir -p rootfs/tmp/mount-{1,2}
|
||||||
|
|
||||||
to_umount_list="$(mktemp "$BATS_RUN_TMPDIR/userns-mounts.XXXXXX")"
|
to_umount_list="$(mktemp "$BATS_RUN_TMPDIR/userns-mounts.XXXXXX")"
|
||||||
@@ -20,6 +19,7 @@ function setup() {
|
|||||||
update_config ' .linux.namespaces += [{"type": "user"}]
|
update_config ' .linux.namespaces += [{"type": "user"}]
|
||||||
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
|
| .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}]
|
||||||
| .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] '
|
| .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] '
|
||||||
|
remap_rootfs
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user