diff options
Diffstat (limited to 'pkg/rootless/rootless_linux.go')
-rw-r--r-- | pkg/rootless/rootless_linux.go | 178 |
1 files changed, 141 insertions, 37 deletions
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index d302b1777..6e48988c5 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,cgo package rootless @@ -9,21 +9,26 @@ import ( "os/exec" gosignal "os/signal" "os/user" + "path/filepath" "runtime" "strconv" + "strings" "sync" "syscall" "unsafe" + "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" "github.com/docker/docker/pkg/signal" + "github.com/godbus/dbus" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) /* -#cgo remoteclient CFLAGS: -DDISABLE_JOIN_SHORTCUT +#cgo remoteclient CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT #include <stdlib.h> +#include <sys/types.h> extern uid_t rootless_uid(); extern uid_t rootless_gid(); extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd); @@ -37,8 +42,7 @@ const ( ) func runInUser() error { - os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") - return nil + return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") } var ( @@ -53,9 +57,15 @@ func IsRootless() bool { rootlessGIDInit := int(C.rootless_gid()) if rootlessUIDInit != 0 { // This happens if we joined the user+mount namespace as part of - os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") - os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)) - os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)) + if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil { + logrus.Errorf("failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done") + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)); err != nil { + logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit) + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)); err != nil { + logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit) + } } isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" }) @@ -101,10 +111,8 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) args := []string{path, fmt.Sprintf("%d", pid)} args = appendTriplet(args, 0, hostID, 1) - if mappings != nil { - for _, i := range mappings { - args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size) - } + for _, i := range mappings { + args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size) } cmd := exec.Cmd{ Path: path, @@ -169,6 +177,9 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { for { nextFd, err := getParentUserNs(fd) if err != nil { + if err == syscall.ENOTTY { + return os.NewFile(fd, "userns child"), nil + } return nil, errors.Wrapf(err, "cannot get parent user namespace") } @@ -178,40 +189,118 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { } if ns == currentNS { - syscall.Close(int(nextFd)) + if err := syscall.Close(int(nextFd)); err != nil { + return nil, err + } // Drop O_CLOEXEC for the fd. _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) if errno != 0 { - syscall.Close(int(fd)) + if err := syscall.Close(int(fd)); err != nil { + logrus.Errorf("failed to close file descriptor %d", fd) + } return nil, errno } return os.NewFile(fd, "userns child"), nil } - syscall.Close(int(fd)) + if err := syscall.Close(int(fd)); err != nil { + return nil, err + } fd = nextFd } } -func enableLinger(pausePid string) { - if pausePid == "" { - return +// EnableLinger configures the system to not kill the user processes once the session +// terminates +func EnableLinger() (string, error) { + uid := fmt.Sprintf("%d", GetRootlessUID()) + + conn, err := dbus.SystemBus() + if err == nil { + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("unable to close dbus connection: %q", err) + } + }() + } + + lingerEnabled := false + + // If we have a D-BUS connection, attempt to read the LINGER property from it. + if conn != nil { + path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) + ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.Linger") + if err == nil && ret.Value().(bool) { + lingerEnabled = true + } + } + + xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") + lingerFile := "" + if xdgRuntimeDir != "" && !lingerEnabled { + lingerFile = filepath.Join(xdgRuntimeDir, "libpod/linger") + _, err := os.Stat(lingerFile) + if err == nil { + lingerEnabled = true + } + } + + if !lingerEnabled { + // First attempt with D-BUS, if it fails, then attempt with "loginctl enable-linger" + if conn != nil { + o := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1") + ret := o.Call("org.freedesktop.login1.Manager.SetUserLinger", 0, uint32(GetRootlessUID()), true, true) + if ret.Err == nil { + lingerEnabled = true + } + } + if !lingerEnabled { + err := exec.Command("loginctl", "enable-linger", uid).Run() + if err == nil { + lingerEnabled = true + } else { + logrus.Debugf("cannot run `loginctl enable-linger` for the current user: %v", err) + } + } + if lingerEnabled && lingerFile != "" { + f, err := os.Create(lingerFile) + if err == nil { + if err := f.Close(); err != nil { + logrus.Errorf("failed to close %s", f.Name()) + } + } else { + logrus.Debugf("could not create linger file: %v", err) + } + } + } + + if !lingerEnabled { + return "", nil + } + + // If we have a D-BUS connection, attempt to read the RUNTIME PATH from it. + if conn != nil { + path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) + ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.RuntimePath") + if err == nil { + return strings.Trim(ret.String(), "\"\n"), nil + } } - // If we are trying to write a pause pid file, make sure we can leave processes - // running longer than the user session. - err := exec.Command("loginctl", "enable-linger", fmt.Sprintf("%d", GetRootlessUID())).Run() + + // If XDG_RUNTIME_DIR is not set and the D-BUS call didn't work, try to get the runtime path with "loginctl" + output, err := exec.Command("loginctl", "-pRuntimePath", "show-user", uid).Output() if err != nil { - logrus.Warnf("cannot run `loginctl enable-linger` for the current user: %v", err) + logrus.Debugf("could not get RuntimePath using loginctl: %v", err) + return "", nil } + return strings.Trim(strings.Replace(string(output), "RuntimePath=", "", -1), "\"\n"), nil } // joinUserAndMountNS 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 joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { - enableLinger(pausePid) - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -223,13 +312,21 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { if err != nil { return false, -1, err } - defer userNS.Close() + defer func() { + if err := userNS.Close(); err != nil { + logrus.Errorf("unable to close namespace: %q", err) + } + }() mountNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", pid)) if err != nil { return false, -1, err } - defer userNS.Close() + defer func() { + if err := mountNS.Close(); err != nil { + logrus.Errorf("unable to close namespace: %q", err) + } + }() fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { @@ -275,9 +372,13 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, } r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child") - defer r.Close() - defer w.Close() - defer w.Write([]byte("0")) + defer errorhandling.CloseQuiet(r) + defer errorhandling.CloseQuiet(w) + defer func() { + if _, err := w.Write([]byte("0")); err != nil { + logrus.Errorf("failed to write byte 0: %q", err) + } + }() pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD) pid := int(pidC) @@ -288,9 +389,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { - user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) + userID, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) if err == nil { - username = user.Username + username = userID.Username } } mappings, err := idtools.NewIDMappings(username, username) @@ -339,7 +440,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, return false, -1, errors.Wrapf(err, "write to sync pipe") } - b := make([]byte, 1, 1) + b := make([]byte, 1) _, err = w.Read(b) if err != nil { return false, -1, errors.Wrapf(err, "read from sync pipe") @@ -385,7 +486,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, continue } - syscall.Kill(int(pidC), s.(syscall.Signal)) + if err := syscall.Kill(int(pidC), s.(syscall.Signal)); err != nil { + logrus.Errorf("failed to kill %d", int(pidC)) + } } }() @@ -402,7 +505,6 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, // If podman was re-executed the caller needs to propagate the error code returned by the child // process. func BecomeRootInUserNS(pausePid string) (bool, int, error) { - enableLinger(pausePid) return becomeRootInUserNS(pausePid, "", nil) } @@ -447,17 +549,19 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file") - defer w.Close() - defer r.Close() + defer errorhandling.CloseQuiet(w) + defer errorhandling.CloseQuiet(r) if _, _, err := becomeRootInUserNS("", path, w); err != nil { lastErr = err continue } - w.Close() + if err := w.Close(); err != nil { + return false, 0, err + } defer func() { - r.Close() + errorhandling.CloseQuiet(r) C.reexec_in_user_namespace_wait(-1, 0) }() |