...

Source file src/github.com/opencontainers/runc/libcontainer/userns/userns_maps_linux.go

Documentation: github.com/opencontainers/runc/libcontainer/userns

     1  //go:build linux
     2  
     3  package userns
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"unsafe"
    12  
    13  	"github.com/opencontainers/runc/libcontainer/configs"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  /*
    18  #include <stdlib.h>
    19  extern int spawn_userns_cat(char *userns_path, char *path, int outfd, int errfd);
    20  */
    21  import "C"
    22  
    23  func parseIdmapData(data []byte) (ms []configs.IDMap, err error) {
    24  	scanner := bufio.NewScanner(bytes.NewReader(data))
    25  	for scanner.Scan() {
    26  		var m configs.IDMap
    27  		line := scanner.Text()
    28  		if _, err := fmt.Sscanf(line, "%d %d %d", &m.ContainerID, &m.HostID, &m.Size); err != nil {
    29  			return nil, fmt.Errorf("parsing id map failed: invalid format in line %q: %w", line, err)
    30  		}
    31  		ms = append(ms, m)
    32  	}
    33  	if err := scanner.Err(); err != nil {
    34  		return nil, fmt.Errorf("parsing id map failed: %w", err)
    35  	}
    36  	return ms, nil
    37  }
    38  
    39  // Do something equivalent to nsenter --user=<nsPath> cat <path>, but more
    40  // efficiently. Returns the contents of the requested file from within the user
    41  // namespace.
    42  func spawnUserNamespaceCat(nsPath string, path string) ([]byte, error) {
    43  	rdr, wtr, err := os.Pipe()
    44  	if err != nil {
    45  		return nil, fmt.Errorf("create pipe for userns spawn failed: %w", err)
    46  	}
    47  	defer rdr.Close()
    48  	defer wtr.Close()
    49  
    50  	errRdr, errWtr, err := os.Pipe()
    51  	if err != nil {
    52  		return nil, fmt.Errorf("create error pipe for userns spawn failed: %w", err)
    53  	}
    54  	defer errRdr.Close()
    55  	defer errWtr.Close()
    56  
    57  	cNsPath := C.CString(nsPath)
    58  	defer C.free(unsafe.Pointer(cNsPath))
    59  	cPath := C.CString(path)
    60  	defer C.free(unsafe.Pointer(cPath))
    61  
    62  	childPid := C.spawn_userns_cat(cNsPath, cPath, C.int(wtr.Fd()), C.int(errWtr.Fd()))
    63  
    64  	if childPid < 0 {
    65  		return nil, fmt.Errorf("failed to spawn fork for userns")
    66  	} else if childPid == 0 {
    67  		// this should never happen
    68  		panic("runc executing inside fork child -- unsafe state!")
    69  	}
    70  
    71  	// We are in the parent -- close the write end of the pipe before reading.
    72  	wtr.Close()
    73  	output, err := io.ReadAll(rdr)
    74  	rdr.Close()
    75  	if err != nil {
    76  		return nil, fmt.Errorf("reading from userns spawn failed: %w", err)
    77  	}
    78  
    79  	// Ditto for the error pipe.
    80  	errWtr.Close()
    81  	errOutput, err := io.ReadAll(errRdr)
    82  	errRdr.Close()
    83  	if err != nil {
    84  		return nil, fmt.Errorf("reading from userns spawn error pipe failed: %w", err)
    85  	}
    86  	errOutput = bytes.TrimSpace(errOutput)
    87  
    88  	// Clean up the child.
    89  	child, err := os.FindProcess(int(childPid))
    90  	if err != nil {
    91  		return nil, fmt.Errorf("could not find userns spawn process: %w", err)
    92  	}
    93  	state, err := child.Wait()
    94  	if err != nil {
    95  		return nil, fmt.Errorf("failed to wait for userns spawn process: %w", err)
    96  	}
    97  	if !state.Success() {
    98  		errStr := string(errOutput)
    99  		if errStr == "" {
   100  			errStr = fmt.Sprintf("unknown error (status code %d)", state.ExitCode())
   101  		}
   102  		return nil, fmt.Errorf("userns spawn: %s", errStr)
   103  	} else if len(errOutput) > 0 {
   104  		// We can just ignore weird output in the error pipe if the process
   105  		// didn't bail(), but for completeness output for debugging.
   106  		logrus.Debugf("userns spawn succeeded but unexpected error message found: %s", string(errOutput))
   107  	}
   108  	// The subprocess succeeded, return whatever it wrote to the pipe.
   109  	return output, nil
   110  }
   111  
   112  func GetUserNamespaceMappings(nsPath string) (uidMap, gidMap []configs.IDMap, err error) {
   113  	var (
   114  		pid         int
   115  		extra       rune
   116  		tryFastPath bool
   117  	)
   118  
   119  	// nsPath is usually of the form /proc/<pid>/ns/user, which means that we
   120  	// already have a pid that is part of the user namespace and thus we can
   121  	// just use the pid to read from /proc/<pid>/*id_map.
   122  	//
   123  	// Note that Sscanf doesn't consume the whole input, so we check for any
   124  	// trailing data with %c. That way, we can be sure the pattern matched
   125  	// /proc/$pid/ns/user _exactly_ iff n === 1.
   126  	if n, _ := fmt.Sscanf(nsPath, "/proc/%d/ns/user%c", &pid, &extra); n == 1 {
   127  		tryFastPath = pid > 0
   128  	}
   129  
   130  	for _, mapType := range []struct {
   131  		name  string
   132  		idMap *[]configs.IDMap
   133  	}{
   134  		{"uid_map", &uidMap},
   135  		{"gid_map", &gidMap},
   136  	} {
   137  		var mapData []byte
   138  
   139  		if tryFastPath {
   140  			path := fmt.Sprintf("/proc/%d/%s", pid, mapType.name)
   141  			data, err := os.ReadFile(path)
   142  			if err != nil {
   143  				// Do not error out here -- we need to try the slow path if the
   144  				// fast path failed.
   145  				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)
   146  			} else {
   147  				mapData = data
   148  			}
   149  		} else {
   150  			logrus.Debugf("cannot use fast path to read %s from userns %s, falling back to slow userns-join path", mapType.name, nsPath)
   151  		}
   152  
   153  		if mapData == nil {
   154  			// We have to actually join the namespace if we cannot take the
   155  			// fast path. The path is resolved with respect to the child
   156  			// process, so just use /proc/self.
   157  			data, err := spawnUserNamespaceCat(nsPath, "/proc/self/"+mapType.name)
   158  			if err != nil {
   159  				return nil, nil, err
   160  			}
   161  			mapData = data
   162  		}
   163  		idMap, err := parseIdmapData(mapData)
   164  		if err != nil {
   165  			return nil, nil, fmt.Errorf("failed to parse %s of userns %s: %w", mapType.name, nsPath, err)
   166  		}
   167  		*mapType.idMap = idMap
   168  	}
   169  
   170  	return uidMap, gidMap, nil
   171  }
   172  
   173  // IsSameMapping returns whether or not the two id mappings are the same. Note
   174  // that if the order of the mappings is different, or a mapping has been split,
   175  // the mappings will be considered different.
   176  func IsSameMapping(a, b []configs.IDMap) bool {
   177  	if len(a) != len(b) {
   178  		return false
   179  	}
   180  	for idx := range a {
   181  		if a[idx] != b[idx] {
   182  			return false
   183  		}
   184  	}
   185  	return true
   186  }
   187  

View as plain text