github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/rootless/rootless_linux.go (about)

     1  // +build linux,cgo
     2  
     3  package rootless
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	gosignal "os/signal"
    13  	"os/user"
    14  	"runtime"
    15  	"strconv"
    16  	"sync"
    17  	"unsafe"
    18  
    19  	"github.com/containers/libpod/pkg/errorhandling"
    20  	"github.com/containers/storage/pkg/idtools"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  	"golang.org/x/sys/unix"
    24  )
    25  
    26  /*
    27  #cgo remoteclient CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT
    28  #include <stdlib.h>
    29  #include <sys/types.h>
    30  extern uid_t rootless_uid();
    31  extern uid_t rootless_gid();
    32  extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd);
    33  extern int reexec_in_user_namespace_wait(int pid, int options);
    34  extern int reexec_userns_join(int pid, char *pause_pid_file_path);
    35  */
    36  import "C"
    37  
    38  const (
    39  	numSig = 65 // max number of signals
    40  )
    41  
    42  func runInUser() error {
    43  	return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done")
    44  }
    45  
    46  var (
    47  	isRootlessOnce sync.Once
    48  	isRootless     bool
    49  )
    50  
    51  // IsRootless tells us if we are running in rootless mode
    52  func IsRootless() bool {
    53  	isRootlessOnce.Do(func() {
    54  		rootlessUIDInit := int(C.rootless_uid())
    55  		rootlessGIDInit := int(C.rootless_gid())
    56  		if rootlessUIDInit != 0 {
    57  			// This happens if we joined the user+mount namespace as part of
    58  			if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil {
    59  				logrus.Errorf("failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done")
    60  			}
    61  			if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)); err != nil {
    62  				logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit)
    63  			}
    64  			if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)); err != nil {
    65  				logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit)
    66  			}
    67  		}
    68  		isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != ""
    69  	})
    70  	return isRootless
    71  }
    72  
    73  // GetRootlessUID returns the UID of the user in the parent userNS
    74  func GetRootlessUID() int {
    75  	uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID")
    76  	if uidEnv != "" {
    77  		u, _ := strconv.Atoi(uidEnv)
    78  		return u
    79  	}
    80  	return os.Geteuid()
    81  }
    82  
    83  // GetRootlessGID returns the GID of the user in the parent userNS
    84  func GetRootlessGID() int {
    85  	gidEnv := os.Getenv("_CONTAINERS_ROOTLESS_GID")
    86  	if gidEnv != "" {
    87  		u, _ := strconv.Atoi(gidEnv)
    88  		return u
    89  	}
    90  
    91  	/* If the _CONTAINERS_ROOTLESS_UID is set, assume the gid==uid.  */
    92  	uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID")
    93  	if uidEnv != "" {
    94  		u, _ := strconv.Atoi(uidEnv)
    95  		return u
    96  	}
    97  	return os.Getegid()
    98  }
    99  
   100  func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error {
   101  	path, err := exec.LookPath(tool)
   102  	if err != nil {
   103  		return errors.Wrapf(err, "cannot find %s", tool)
   104  	}
   105  
   106  	appendTriplet := func(l []string, a, b, c int) []string {
   107  		return append(l, strconv.Itoa(a), strconv.Itoa(b), strconv.Itoa(c))
   108  	}
   109  
   110  	args := []string{path, fmt.Sprintf("%d", pid)}
   111  	args = appendTriplet(args, 0, hostID, 1)
   112  	for _, i := range mappings {
   113  		args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size)
   114  	}
   115  	cmd := exec.Cmd{
   116  		Path: path,
   117  		Args: args,
   118  	}
   119  
   120  	if output, err := cmd.CombinedOutput(); err != nil {
   121  		logrus.Debugf("error from %s: %s", tool, output)
   122  		return errors.Wrapf(err, "cannot setup namespace using %s", tool)
   123  	}
   124  	return nil
   125  }
   126  
   127  // joinUserAndMountNS re-exec podman in a new userNS and join the user and mount
   128  // namespace of the specified PID without looking up its parent.  Useful to join directly
   129  // the conmon process.
   130  func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
   131  	if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
   132  		return false, -1, nil
   133  	}
   134  
   135  	cPausePid := C.CString(pausePid)
   136  	defer C.free(unsafe.Pointer(cPausePid))
   137  
   138  	pidC := C.reexec_userns_join(C.int(pid), cPausePid)
   139  	if int(pidC) < 0 {
   140  		return false, -1, errors.Errorf("cannot re-exec process")
   141  	}
   142  
   143  	ret := C.reexec_in_user_namespace_wait(pidC, 0)
   144  	if ret < 0 {
   145  		return false, -1, errors.New("error waiting for the re-exec process")
   146  	}
   147  
   148  	return true, int(ret), nil
   149  }
   150  
   151  // GetConfiguredMappings returns the additional IDs configured for the current user.
   152  func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) {
   153  	var uids, gids []idtools.IDMap
   154  	username := os.Getenv("USER")
   155  	if username == "" {
   156  		var id string
   157  		if os.Geteuid() == 0 {
   158  			id = strconv.Itoa(GetRootlessUID())
   159  		} else {
   160  			id = strconv.Itoa(os.Geteuid())
   161  		}
   162  		userID, err := user.LookupId(id)
   163  		if err == nil {
   164  			username = userID.Username
   165  		}
   166  	}
   167  	mappings, err := idtools.NewIDMappings(username, username)
   168  	if err != nil {
   169  		logrus.Errorf("cannot find mappings for user %s: %v", username, err)
   170  	} else {
   171  		uids = mappings.UIDs()
   172  		gids = mappings.GIDs()
   173  	}
   174  	return uids, gids, nil
   175  }
   176  
   177  func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, int, error) {
   178  	if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
   179  		if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" {
   180  			return false, 0, runInUser()
   181  		}
   182  		return false, 0, nil
   183  	}
   184  
   185  	cPausePid := C.CString(pausePid)
   186  	defer C.free(unsafe.Pointer(cPausePid))
   187  
   188  	cFileToRead := C.CString(fileToRead)
   189  	defer C.free(unsafe.Pointer(cFileToRead))
   190  	var fileOutputFD C.int
   191  	if fileOutput != nil {
   192  		fileOutputFD = C.int(fileOutput.Fd())
   193  	}
   194  
   195  	runtime.LockOSThread()
   196  	defer runtime.UnlockOSThread()
   197  
   198  	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0)
   199  	if err != nil {
   200  		return false, -1, err
   201  	}
   202  	r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child")
   203  
   204  	defer errorhandling.CloseQuiet(r)
   205  	defer errorhandling.CloseQuiet(w)
   206  	defer func() {
   207  		if _, err := w.Write([]byte("0")); err != nil {
   208  			logrus.Errorf("failed to write byte 0: %q", err)
   209  		}
   210  	}()
   211  
   212  	pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD)
   213  	pid := int(pidC)
   214  	if pid < 0 {
   215  		return false, -1, errors.Errorf("cannot re-exec process")
   216  	}
   217  
   218  	uids, gids, err := GetConfiguredMappings()
   219  	if err != nil {
   220  		return false, -1, err
   221  	}
   222  
   223  	uidsMapped := false
   224  	if uids != nil {
   225  		err := tryMappingTool("newuidmap", pid, os.Geteuid(), uids)
   226  		uidsMapped = err == nil
   227  	}
   228  	if !uidsMapped {
   229  		logrus.Warnf("using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding subids")
   230  		setgroups := fmt.Sprintf("/proc/%d/setgroups", pid)
   231  		err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666)
   232  		if err != nil {
   233  			return false, -1, errors.Wrapf(err, "cannot write setgroups file")
   234  		}
   235  		logrus.Debugf("write setgroups file exited with 0")
   236  
   237  		uidMap := fmt.Sprintf("/proc/%d/uid_map", pid)
   238  		err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Geteuid())), 0666)
   239  		if err != nil {
   240  			return false, -1, errors.Wrapf(err, "cannot write uid_map")
   241  		}
   242  		logrus.Debugf("write uid_map exited with 0")
   243  	}
   244  
   245  	gidsMapped := false
   246  	if gids != nil {
   247  		err := tryMappingTool("newgidmap", pid, os.Getegid(), gids)
   248  		gidsMapped = err == nil
   249  	}
   250  	if !gidsMapped {
   251  		gidMap := fmt.Sprintf("/proc/%d/gid_map", pid)
   252  		err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getegid())), 0666)
   253  		if err != nil {
   254  			return false, -1, errors.Wrapf(err, "cannot write gid_map")
   255  		}
   256  	}
   257  
   258  	_, err = w.Write([]byte("0"))
   259  	if err != nil {
   260  		return false, -1, errors.Wrapf(err, "write to sync pipe")
   261  	}
   262  
   263  	b := make([]byte, 1)
   264  	_, err = w.Read(b)
   265  	if err != nil {
   266  		return false, -1, errors.Wrapf(err, "read from sync pipe")
   267  	}
   268  
   269  	if fileOutput != nil {
   270  		return true, 0, nil
   271  	}
   272  
   273  	if b[0] == '2' {
   274  		// We have lost the race for writing the PID file, as probably another
   275  		// process created a namespace and wrote the PID.
   276  		// Try to join it.
   277  		data, err := ioutil.ReadFile(pausePid)
   278  		if err == nil {
   279  			pid, err := strconv.ParseUint(string(data), 10, 0)
   280  			if err == nil {
   281  				return joinUserAndMountNS(uint(pid), "")
   282  			}
   283  		}
   284  		return false, -1, errors.Wrapf(err, "error setting up the process")
   285  	}
   286  
   287  	if b[0] != '0' {
   288  		return false, -1, errors.Wrapf(err, "error setting up the process")
   289  	}
   290  
   291  	c := make(chan os.Signal, 1)
   292  
   293  	signals := []os.Signal{}
   294  	for sig := 0; sig < numSig; sig++ {
   295  		if sig == int(unix.SIGTSTP) {
   296  			continue
   297  		}
   298  		signals = append(signals, unix.Signal(sig))
   299  	}
   300  
   301  	gosignal.Notify(c, signals...)
   302  	defer gosignal.Reset()
   303  	go func() {
   304  		for s := range c {
   305  			if s == unix.SIGCHLD || s == unix.SIGPIPE {
   306  				continue
   307  			}
   308  
   309  			if err := unix.Kill(int(pidC), s.(unix.Signal)); err != nil {
   310  				logrus.Errorf("failed to kill %d", int(pidC))
   311  			}
   312  		}
   313  	}()
   314  
   315  	ret := C.reexec_in_user_namespace_wait(pidC, 0)
   316  	if ret < 0 {
   317  		return false, -1, errors.New("error waiting for the re-exec process")
   318  	}
   319  
   320  	return true, int(ret), nil
   321  }
   322  
   323  // BecomeRootInUserNS re-exec podman in a new userNS.  It returns whether podman was re-executed
   324  // into a new user namespace and the return code from the re-executed podman process.
   325  // If podman was re-executed the caller needs to propagate the error code returned by the child
   326  // process.
   327  func BecomeRootInUserNS(pausePid string) (bool, int, error) {
   328  	return becomeRootInUserNS(pausePid, "", nil)
   329  }
   330  
   331  // TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths.
   332  // This is useful when there are already running containers and we
   333  // don't have a pause process yet.  We can use the paths to the conmon
   334  // processes to attempt joining their namespaces.
   335  // If needNewNamespace is set, the file is read from a temporary user
   336  // namespace, this is useful for containers that are running with a
   337  // different uidmap and the unprivileged user has no way to read the
   338  // file owned by the root in the container.
   339  func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) {
   340  	if len(paths) == 0 {
   341  		return BecomeRootInUserNS(pausePidPath)
   342  	}
   343  
   344  	var lastErr error
   345  	var pausePid int
   346  	foundProcess := false
   347  
   348  	for _, path := range paths {
   349  		if !needNewNamespace {
   350  			data, err := ioutil.ReadFile(path)
   351  			if err != nil {
   352  				lastErr = err
   353  				continue
   354  			}
   355  
   356  			pausePid, err = strconv.Atoi(string(data))
   357  			if err != nil {
   358  				lastErr = errors.Wrapf(err, "cannot parse file %s", path)
   359  				continue
   360  			}
   361  
   362  			lastErr = nil
   363  			break
   364  		} else {
   365  			fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0)
   366  			if err != nil {
   367  				lastErr = err
   368  				continue
   369  			}
   370  
   371  			r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file")
   372  
   373  			defer errorhandling.CloseQuiet(r)
   374  
   375  			if _, _, err := becomeRootInUserNS("", path, w); err != nil {
   376  				w.Close()
   377  				lastErr = err
   378  				continue
   379  			}
   380  
   381  			if err := w.Close(); err != nil {
   382  				return false, 0, err
   383  			}
   384  			defer func() {
   385  				C.reexec_in_user_namespace_wait(-1, 0)
   386  			}()
   387  
   388  			b := make([]byte, 32)
   389  
   390  			n, err := r.Read(b)
   391  			if err != nil {
   392  				lastErr = errors.Wrapf(err, "cannot read %s\n", path)
   393  				continue
   394  			}
   395  
   396  			pausePid, err = strconv.Atoi(string(b[:n]))
   397  			if err == nil && unix.Kill(pausePid, 0) == nil {
   398  				foundProcess = true
   399  				lastErr = nil
   400  				break
   401  			}
   402  		}
   403  	}
   404  	if !foundProcess && pausePidPath != "" {
   405  		return BecomeRootInUserNS(pausePidPath)
   406  	}
   407  	if lastErr != nil {
   408  		return false, 0, lastErr
   409  	}
   410  
   411  	return joinUserAndMountNS(uint(pausePid), pausePidPath)
   412  }
   413  
   414  // ReadMappingsProc parses and returns the ID mappings at the specified path.
   415  func ReadMappingsProc(path string) ([]idtools.IDMap, error) {
   416  	file, err := os.Open(path)
   417  	if err != nil {
   418  		return nil, errors.Wrapf(err, "cannot open %s", path)
   419  	}
   420  	defer file.Close()
   421  
   422  	mappings := []idtools.IDMap{}
   423  
   424  	buf := bufio.NewReader(file)
   425  	for {
   426  		line, _, err := buf.ReadLine()
   427  		if err != nil {
   428  			if err == io.EOF {
   429  				return mappings, nil
   430  			}
   431  			return nil, errors.Wrapf(err, "cannot read line from %s", path)
   432  		}
   433  		if line == nil {
   434  			return mappings, nil
   435  		}
   436  
   437  		containerID, hostID, size := 0, 0, 0
   438  		if _, err := fmt.Sscanf(string(line), "%d %d %d", &containerID, &hostID, &size); err != nil {
   439  			return nil, errors.Wrapf(err, "cannot parse %s", string(line))
   440  		}
   441  		mappings = append(mappings, idtools.IDMap{ContainerID: containerID, HostID: hostID, Size: size})
   442  	}
   443  }
   444  
   445  func matches(id int, configuredIDs []idtools.IDMap, currentIDs []idtools.IDMap) bool {
   446  	// The first mapping is the host user, handle it separately.
   447  	if currentIDs[0].HostID != id || currentIDs[0].Size != 1 {
   448  		return false
   449  	}
   450  
   451  	currentIDs = currentIDs[1:]
   452  	if len(currentIDs) != len(configuredIDs) {
   453  		return false
   454  	}
   455  
   456  	// It is fine to iterate sequentially as both slices are sorted.
   457  	for i := range currentIDs {
   458  		if currentIDs[i].HostID != configuredIDs[i].HostID {
   459  			return false
   460  		}
   461  		if currentIDs[i].Size != configuredIDs[i].Size {
   462  			return false
   463  		}
   464  	}
   465  
   466  	return true
   467  }
   468  
   469  // ConfigurationMatches checks whether the additional uids/gids configured for the user
   470  // match the current user namespace.
   471  func ConfigurationMatches() (bool, error) {
   472  	if !IsRootless() || os.Geteuid() != 0 {
   473  		return true, nil
   474  	}
   475  
   476  	uids, gids, err := GetConfiguredMappings()
   477  	if err != nil {
   478  		return false, err
   479  	}
   480  
   481  	currentUIDs, err := ReadMappingsProc("/proc/self/uid_map")
   482  	if err != nil {
   483  		return false, err
   484  	}
   485  
   486  	if !matches(GetRootlessUID(), uids, currentUIDs) {
   487  		return false, err
   488  	}
   489  
   490  	currentGIDs, err := ReadMappingsProc("/proc/self/gid_map")
   491  	if err != nil {
   492  		return false, err
   493  	}
   494  
   495  	return matches(GetRootlessGID(), gids, currentGIDs), nil
   496  }