From c3abb8c00a5807a6843b8f55fb8f29a08eaabd2c Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Sat, 13 May 2023 15:36:52 -0700 Subject: [PATCH] Add directfs support for Docker/k8s/Podman rootless containers. These tools use rootless-containers/rootlesskit under the hood. rootlesskit configures a new userns with uid/gid mappings such that 0 ID is mapped to current user/group and IDs 1 onwards are mapped to host IDs configured in /etc/subuid and /etc/subgid. runsc is invoked by these tools in a new userns with UID=0 which is mapped to the caller's UID in the parent userns. When directfs tries modify the OCI spec by adding identity mappings, it should not assume that we are running as root in the initial userns. As described above, we could be running as root in a new userns. So the identity mappings should be created by looking at /proc/self/{uid/gid}_map. With this change, rootless containers work correctly with these tools. PiperOrigin-RevId: 531796427 --- .buildkite/pipeline.yaml | 4 +- runsc/container/container.go | 78 +++++++++++++++++++++++++++--------- test/podman/run.sh | 2 +- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 1528c5a55..3f541b0a3 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -401,7 +401,9 @@ steps: - <<: *common <<: *source_test label: ":podman: Podman" - command: sudo ./test/podman/run.sh + commands: + - sudo ./test/podman/run.sh + - sudo -E RUNTIME_ARGS=--directfs ./test/podman/run.sh agents: <<: *ubuntu_agents cgroup: "v2" diff --git a/runsc/container/container.go b/runsc/container/container.go index b509615a4..e49e5d68a 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -16,12 +16,14 @@ package container import ( + "bufio" "context" "errors" "fmt" "io/ioutil" "os" "os/exec" + "path" "regexp" "strconv" "strings" @@ -1648,28 +1650,64 @@ func modifySpecForDirectfs(conf *config.Config, spec *specs.Spec) error { return fmt.Errorf("spec defines UID/GID mappings without defining userns") } // Run the sandbox in a new user namespace with identity UID/GID mappings. + log.Debugf("Configuring container with a new userns with identity user mappings into current userns") spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.UserNamespace}) - // The maximum range of UID/GID mapping should be the largest uint32 integer. - // This is similar to what Linux does for identity mappings. Also note: - // "This leaves 4294967295 (the 32-bit signed -1 value) unmapped. This is - // deliberate: (uid_t) -1 is used in several interfaces (e.g., setreuid(2)) - // as a way to specify "no user ID". Leaving (uid_t) -1 unmapped and - // unusable guarantees that there will be no confusion when using these - // interfaces." -- user_namespaces(7). - maxRange := ^uint32(0) - spec.Linux.UIDMappings = []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: 0, - Size: maxRange, - }, + uidMappings, err := getIdentityMapping("uid_map") + if err != nil { + return err } - spec.Linux.GIDMappings = []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: 0, - Size: maxRange, - }, + spec.Linux.UIDMappings = uidMappings + logIDMappings(uidMappings, "UID") + gidMappings, err := getIdentityMapping("gid_map") + if err != nil { + return err } + spec.Linux.GIDMappings = gidMappings + logIDMappings(gidMappings, "GID") return nil } + +func getIdentityMapping(mapFileName string) ([]specs.LinuxIDMapping, error) { + // See user_namespaces(7) to understand how /proc/self/{uid/gid}_map files + // are organized. + mapFile := path.Join("/proc/self", mapFileName) + file, err := os.Open(mapFile) + if err != nil { + return nil, fmt.Errorf("failed to open %s: %v", mapFile, err) + } + defer file.Close() + + var mappings []specs.LinuxIDMapping + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + var myStart, parentStart, rangeLen uint32 + numParsed, err := fmt.Sscanf(line, "%d %d %d", &myStart, &parentStart, &rangeLen) + if err != nil { + return nil, fmt.Errorf("failed to parse line %q in file %s: %v", line, mapFile, err) + } + if numParsed != 3 { + return nil, fmt.Errorf("failed to parse 3 integers from line %q in file %s", line, mapFile) + } + // Create an identity mapping with the current userns. + mappings = append(mappings, specs.LinuxIDMapping{ + ContainerID: myStart, + HostID: myStart, + Size: rangeLen, + }) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file %s: %v", mapFile, err) + } + return mappings, nil +} + +func logIDMappings(mappings []specs.LinuxIDMapping, idType string) { + if !log.IsLogging(log.Debug) { + return + } + log.Debugf("%s Mappings:", idType) + for _, m := range mappings { + log.Debugf("\tContainer ID: %d, Host ID: %d, Range Length: %d", m.ContainerID, m.HostID, m.Size) + } +} diff --git a/test/podman/run.sh b/test/podman/run.sh index 3429444d2..1c96a2468 100755 --- a/test/podman/run.sh +++ b/test/podman/run.sh @@ -31,7 +31,7 @@ make copy TARGETS=runsc DESTINATION="${test_dir}" cat > "${podman_runtime}" <