mirror of
https://github.com/opencontainers/runc.git
synced 2026-04-24 08:35:53 +08:00
67840cce4b
Commit b2f8a74d "clothed" the naked return as inflicted by gofumpt
v0.9.0. Since gofumpt v0.9.2 this rule was moved to "extra" category,
not enabled by default. The only other "extra" rule is to group adjacent
parameters with the same type, which also makes sense.
Enable gofumpt "extra" rules, and reformat the code accordingly.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
187 lines
5.3 KiB
Go
187 lines
5.3 KiB
Go
//go:build linux
|
|
|
|
package userns
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
extern int spawn_userns_cat(char *userns_path, char *path, int outfd, int errfd);
|
|
*/
|
|
import "C"
|
|
|
|
func parseIdmapData(data []byte) (ms []configs.IDMap, err error) {
|
|
scanner := bufio.NewScanner(bytes.NewReader(data))
|
|
for scanner.Scan() {
|
|
var m configs.IDMap
|
|
line := scanner.Text()
|
|
if _, err := fmt.Sscanf(line, "%d %d %d", &m.ContainerID, &m.HostID, &m.Size); err != nil {
|
|
return nil, fmt.Errorf("parsing id map failed: invalid format in line %q: %w", line, err)
|
|
}
|
|
ms = append(ms, m)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("parsing id map failed: %w", err)
|
|
}
|
|
return ms, nil
|
|
}
|
|
|
|
// Do something equivalent to nsenter --user=<nsPath> cat <path>, but more
|
|
// efficiently. Returns the contents of the requested file from within the user
|
|
// namespace.
|
|
func spawnUserNamespaceCat(nsPath, path string) ([]byte, error) {
|
|
rdr, wtr, err := os.Pipe()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create pipe for userns spawn failed: %w", err)
|
|
}
|
|
defer rdr.Close()
|
|
defer wtr.Close()
|
|
|
|
errRdr, errWtr, err := os.Pipe()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create error pipe for userns spawn failed: %w", err)
|
|
}
|
|
defer errRdr.Close()
|
|
defer errWtr.Close()
|
|
|
|
cNsPath := C.CString(nsPath)
|
|
defer C.free(unsafe.Pointer(cNsPath))
|
|
cPath := C.CString(path)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
|
|
childPid := C.spawn_userns_cat(cNsPath, cPath, C.int(wtr.Fd()), C.int(errWtr.Fd()))
|
|
|
|
if childPid < 0 {
|
|
return nil, fmt.Errorf("failed to spawn fork for userns")
|
|
} else if childPid == 0 {
|
|
// this should never happen
|
|
panic("runc executing inside fork child -- unsafe state!")
|
|
}
|
|
|
|
// We are in the parent -- close the write end of the pipe before reading.
|
|
wtr.Close()
|
|
output, err := io.ReadAll(rdr)
|
|
rdr.Close()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading from userns spawn failed: %w", err)
|
|
}
|
|
|
|
// Ditto for the error pipe.
|
|
errWtr.Close()
|
|
errOutput, err := io.ReadAll(errRdr)
|
|
errRdr.Close()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading from userns spawn error pipe failed: %w", err)
|
|
}
|
|
errOutput = bytes.TrimSpace(errOutput)
|
|
|
|
// Clean up the child.
|
|
child, err := os.FindProcess(int(childPid))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not find userns spawn process: %w", err)
|
|
}
|
|
state, err := child.Wait()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to wait for userns spawn process: %w", err)
|
|
}
|
|
if !state.Success() {
|
|
errStr := string(errOutput)
|
|
if errStr == "" {
|
|
errStr = fmt.Sprintf("unknown error (status code %d)", state.ExitCode())
|
|
}
|
|
return nil, fmt.Errorf("userns spawn: %s", errStr)
|
|
} else if len(errOutput) > 0 {
|
|
// We can just ignore weird output in the error pipe if the process
|
|
// didn't bail(), but for completeness output for debugging.
|
|
logrus.Debugf("userns spawn succeeded but unexpected error message found: %s", string(errOutput))
|
|
}
|
|
// The subprocess succeeded, return whatever it wrote to the pipe.
|
|
return output, nil
|
|
}
|
|
|
|
func GetUserNamespaceMappings(nsPath string) (uidMap, gidMap []configs.IDMap, err error) {
|
|
var (
|
|
pid int
|
|
extra rune
|
|
tryFastPath bool
|
|
)
|
|
|
|
// nsPath is usually of the form /proc/<pid>/ns/user, which means that we
|
|
// already have a pid that is part of the user namespace and thus we can
|
|
// just use the pid to read from /proc/<pid>/*id_map.
|
|
//
|
|
// Note that Sscanf doesn't consume the whole input, so we check for any
|
|
// trailing data with %c. That way, we can be sure the pattern matched
|
|
// /proc/$pid/ns/user _exactly_ iff n === 1.
|
|
if n, _ := fmt.Sscanf(nsPath, "/proc/%d/ns/user%c", &pid, &extra); n == 1 {
|
|
tryFastPath = pid > 0
|
|
}
|
|
|
|
for _, mapType := range []struct {
|
|
name string
|
|
idMap *[]configs.IDMap
|
|
}{
|
|
{"uid_map", &uidMap},
|
|
{"gid_map", &gidMap},
|
|
} {
|
|
var mapData []byte
|
|
|
|
if tryFastPath {
|
|
path := fmt.Sprintf("/proc/%d/%s", pid, mapType.name)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
// Do not error out here -- we need to try the slow path if the
|
|
// fast path failed.
|
|
logrus.Debugf("failed to use fast path to read %s from userns %s (error: %s), falling back to slow userns-join path", mapType.name, nsPath, err)
|
|
} else {
|
|
mapData = data
|
|
}
|
|
} else {
|
|
logrus.Debugf("cannot use fast path to read %s from userns %s, falling back to slow userns-join path", mapType.name, nsPath)
|
|
}
|
|
|
|
if mapData == nil {
|
|
// We have to actually join the namespace if we cannot take the
|
|
// fast path. The path is resolved with respect to the child
|
|
// process, so just use /proc/self.
|
|
data, err := spawnUserNamespaceCat(nsPath, "/proc/self/"+mapType.name)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
mapData = data
|
|
}
|
|
idMap, err := parseIdmapData(mapData)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to parse %s of userns %s: %w", mapType.name, nsPath, err)
|
|
}
|
|
*mapType.idMap = idMap
|
|
}
|
|
|
|
return uidMap, gidMap, nil
|
|
}
|
|
|
|
// IsSameMapping returns whether or not the two id mappings are the same. Note
|
|
// that if the order of the mappings is different, or a mapping has been split,
|
|
// the mappings will be considered different.
|
|
func IsSameMapping(a, b []configs.IDMap) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for idx := range a {
|
|
if a[idx] != b[idx] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|