summaryrefslogtreecommitdiff
path: root/pkg/rootless/rootless_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/rootless/rootless_linux.go')
-rw-r--r--pkg/rootless/rootless_linux.go178
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)
}()