diff options
Diffstat (limited to 'pkg/rootless')
-rw-r--r-- | pkg/rootless/rootless.go | 9 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 6 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 108 | ||||
-rw-r--r-- | pkg/rootless/rootless_unsupported.go | 35 |
4 files changed, 92 insertions, 66 deletions
diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go new file mode 100644 index 000000000..a531e43ce --- /dev/null +++ b/pkg/rootless/rootless.go @@ -0,0 +1,9 @@ +package rootless + +// Opts allows to customize how re-execing to a rootless process is done +type Opts struct { + // Argument overrides the arguments on the command line + // for the re-execed process. The process in the namespace + // must use rootless.Argument() to read its value. + Argument string +} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index dfbc7fe33..ff39e9e77 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -32,7 +32,11 @@ syscall_setresgid (gid_t rgid, gid_t egid, gid_t sgid) static int syscall_clone (unsigned long flags, void *child_stack) { +#if defined(__s390__) || defined(__CRIS__) + return (int) syscall (__NR_clone, child_stack, flags); +#else return (int) syscall (__NR_clone, flags, child_stack); +#endif } static char ** @@ -273,6 +277,8 @@ reexec_in_user_namespace (int ready) _exit (EXIT_FAILURE); } close (ready); + if (b != '1') + _exit (EXIT_FAILURE); if (syscall_setresgid (0, 0, 0) < 0) { diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 98692707f..baceebee3 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -11,7 +11,6 @@ import ( "os/user" "runtime" "strconv" - "strings" "sync" "syscall" "unsafe" @@ -61,6 +60,11 @@ func SkipStorageSetup() bool { return skipStorageSetup } +// Argument returns the argument that was set for the rootless session. +func Argument() string { + return os.Getenv("_LIBPOD_ROOTLESS_ARG") +} + // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_LIBPOD_ROOTLESS_UID") @@ -68,7 +72,7 @@ func GetRootlessUID() int { u, _ := strconv.Atoi(uidEnv) return u } - return os.Getuid() + return os.Geteuid() } func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { @@ -102,7 +106,7 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) // JoinNS re-exec podman in a new userNS and join the user namespace of the specified // PID. -func JoinNS(pid uint) (bool, int, error) { +func JoinNS(pid uint, preserveFDs int) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -117,6 +121,13 @@ func JoinNS(pid uint) (bool, int, error) { if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } + if preserveFDs > 0 { + for fd := 3; fd < 3+preserveFDs; fd++ { + // These fds were passed down to the runtime. Close them + // and not interfere + os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() + } + } ret := C.reexec_in_user_namespace_wait(pidC) if ret < 0 { @@ -128,8 +139,16 @@ func JoinNS(pid uint) (bool, int, error) { // JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. +// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// with a default configuration. func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { + return JoinDirectUserAndMountNSWithOpts(pid, nil) +} + +// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and +// mount namespace of the specified PID without looking up its parent. Useful to join +// directly the conmon process. +func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -146,6 +165,12 @@ func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { } defer userNS.Close() + if opts != nil && opts.Argument != "" { + if err := os.Setenv("_LIBPOD_ROOTLESS_ARG", opts.Argument); err != nil { + return false, -1, err + } + } + pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd())) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") @@ -185,27 +210,19 @@ func JoinNSPath(path string) (bool, int, error) { return true, int(ret), nil } -const defaultMinimumMappings = 65536 - -func getMinimumIDs(p string) int { - content, err := ioutil.ReadFile(p) - if err != nil { - logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings) - return defaultMinimumMappings - } - ret, err := strconv.Atoi(strings.TrimSuffix(string(content), "\n")) - if err != nil { - logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings) - return defaultMinimumMappings - } - return ret + 1 -} - // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed // into a new user namespace and the return code from the re-executed podman process. // If podman was re-executed the caller needs to propagate the error code returned by the child -// process. +// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. func BecomeRootInUserNS() (bool, int, error) { + return BecomeRootInUserNSWithOpts(nil) +} + +// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was +// re-execute into a new user namespace and the return code from the re-executed podman process. +// If podman was re-executed the caller needs to propagate the error code returned by the child +// process. +func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { if os.Getenv("_LIBPOD_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -222,6 +239,13 @@ func BecomeRootInUserNS() (bool, int, error) { } defer r.Close() defer w.Close() + defer w.Write([]byte("0")) + + if opts != nil && opts.Argument != "" { + if err := os.Setenv("_LIBPOD_ROOTLESS_ARG", opts.Argument); err != nil { + return false, -1, err + } + } pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) @@ -229,47 +253,18 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.Errorf("cannot re-exec process") } - allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != "" - var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) - if err != nil && !allowSingleIDMapping { - if os.IsNotExist(err) { - return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files") - } - return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set") - } if err == nil { username = user.Username } } mappings, err := idtools.NewIDMappings(username, username) - if !allowSingleIDMapping { - if err != nil { - return false, -1, err - } - - availableGIDs, availableUIDs := 0, 0 - for _, i := range mappings.UIDs() { - availableUIDs += i.Size - } - - minUIDs := getMinimumIDs("/proc/sys/kernel/overflowuid") - if availableUIDs < minUIDs { - return false, 0, fmt.Errorf("not enough UIDs available for the user, at least %d are needed", minUIDs) - } - - for _, i := range mappings.GIDs() { - availableGIDs += i.Size - } - minGIDs := getMinimumIDs("/proc/sys/kernel/overflowgid") - if availableGIDs < minGIDs { - return false, 0, fmt.Errorf("not enough GIDs available for the user, at least %d are needed", minGIDs) - } - } - if err == nil { + if err != nil { + logrus.Warnf("cannot find mappings for user %s: %v", username, err) + } else { uids = mappings.UIDs() gids = mappings.GIDs() } @@ -277,12 +272,10 @@ func BecomeRootInUserNS() (bool, int, error) { uidsMapped := false if mappings != nil && uids != nil { err := tryMappingTool("newuidmap", pid, os.Getuid(), uids) - if !allowSingleIDMapping && err != nil { - return false, 0, err - } uidsMapped = err == nil } if !uidsMapped { + logrus.Warnf("using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding subids") setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666) if err != nil { @@ -299,9 +292,6 @@ func BecomeRootInUserNS() (bool, int, error) { gidsMapped := false if mappings != nil && gids != nil { err := tryMappingTool("newgidmap", pid, os.Getgid(), gids) - if !allowSingleIDMapping && err != nil { - return false, 0, err - } gidsMapped = err == nil } if !gidsMapped { diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index 1823c023e..e01d7855c 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -11,10 +11,18 @@ func IsRootless() bool { return false } +// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed +// into a new user namespace and the return code from the re-executed podman process. +// If podman was re-executed the caller needs to propagate the error code returned by the child +// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. +func BecomeRootInUserNS() (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") +} + // BecomeRootInUserNS is a stub function that always returns false and an // error on unsupported OS's -func BecomeRootInUserNS() (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os1") +func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") } // GetRootlessUID returns the UID of the user in the parent userNS @@ -33,19 +41,32 @@ func SkipStorageSetup() bool { // JoinNS re-exec podman in a new userNS and join the user namespace of the specified // PID. -func JoinNS(pid uint) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os2") +func JoinNS(pid uint, preserveFDs int) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") } // JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the // specified path. func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os3") + return false, -1, errors.New("this function is not supported on this os") +} + +// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and +// mount namespace of the specified PID without looking up its parent. Useful to join +// directly the conmon process. +func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") } // JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. +// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// with a default configuration. func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os4") + return false, -1, errors.New("this function is not supported on this os") +} + +// Argument returns the argument that was set for the rootless session. +func Argument() string { + return "" } |