mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-12 03:45:46 +00:00
We cannot re-exec into a new user namespace to gain privileges and access an existing as the new namespace is not the owner of the existing container. "unshare" is used to join the user namespace of the target container. The current implementation assumes that the main process of the container didn't create a new user namespace. Since in the setup phase we are not running with euid=0, we must skip the setup for containers/storage. Closes: https://github.com/containers/libpod/issues/1329 Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com> Closes: #1331 Approved by: rhatdan
188 lines
4.6 KiB
Go
188 lines
4.6 KiB
Go
// +build linux
|
|
|
|
package rootless
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
gosignal "os/signal"
|
|
"os/user"
|
|
"runtime"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/containers/storage/pkg/idtools"
|
|
"github.com/docker/docker/pkg/signal"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
/*
|
|
extern int reexec_in_user_namespace(int ready);
|
|
extern int reexec_in_user_namespace_wait(int pid);
|
|
*/
|
|
import "C"
|
|
|
|
func runInUser() error {
|
|
os.Setenv("_LIBPOD_USERNS_CONFIGURED", "done")
|
|
return nil
|
|
}
|
|
|
|
// IsRootless tells us if we are running in rootless mode
|
|
func IsRootless() bool {
|
|
return os.Getuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != ""
|
|
}
|
|
|
|
var (
|
|
skipStorageSetup = false
|
|
)
|
|
|
|
// SetSkipStorageSetup tells the runtime to not setup containers/storage
|
|
func SetSkipStorageSetup(v bool) {
|
|
skipStorageSetup = v
|
|
}
|
|
|
|
// SkipStorageSetup tells if we should skip the containers/storage setup
|
|
func SkipStorageSetup() bool {
|
|
return skipStorageSetup
|
|
}
|
|
|
|
// GetRootlessUID returns the UID of the user in the parent userNS
|
|
func GetRootlessUID() int {
|
|
uidEnv := os.Getenv("_LIBPOD_ROOTLESS_UID")
|
|
if uidEnv != "" {
|
|
u, _ := strconv.Atoi(uidEnv)
|
|
return u
|
|
}
|
|
return os.Getuid()
|
|
}
|
|
|
|
func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error {
|
|
path, err := exec.LookPath(tool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
appendTriplet := func(l []string, a, b, c int) []string {
|
|
return append(l, fmt.Sprintf("%d", a), fmt.Sprintf("%d", b), fmt.Sprintf("%d", c))
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
cmd := exec.Cmd{
|
|
Path: path,
|
|
Args: args,
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
// 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.
|
|
func BecomeRootInUserNS() (bool, int, error) {
|
|
if os.Getuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" {
|
|
if os.Getenv("_LIBPOD_USERNS_CONFIGURED") == "init" {
|
|
return false, 0, runInUser()
|
|
}
|
|
return false, 0, nil
|
|
}
|
|
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return false, -1, err
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
pidC := C.reexec_in_user_namespace(C.int(r.Fd()))
|
|
pid := int(pidC)
|
|
if pid < 0 {
|
|
return false, -1, errors.Errorf("cannot re-exec process")
|
|
}
|
|
|
|
var uids, gids []idtools.IDMap
|
|
username := os.Getenv("USER")
|
|
if username == "" {
|
|
user, err := user.LookupId(fmt.Sprintf("%d", os.Geteuid()))
|
|
if err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" {
|
|
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 err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" {
|
|
return false, -1, err
|
|
}
|
|
if err == nil {
|
|
uids = mappings.UIDs()
|
|
gids = mappings.GIDs()
|
|
}
|
|
|
|
uidsMapped := false
|
|
if mappings != nil && uids != nil {
|
|
uidsMapped = tryMappingTool("newuidmap", pid, os.Getuid(), uids) == nil
|
|
}
|
|
if !uidsMapped {
|
|
setgroups := fmt.Sprintf("/proc/%d/setgroups", pid)
|
|
err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666)
|
|
if err != nil {
|
|
return false, -1, errors.Wrapf(err, "cannot write setgroups file")
|
|
}
|
|
|
|
uidMap := fmt.Sprintf("/proc/%d/uid_map", pid)
|
|
err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getuid())), 0666)
|
|
if err != nil {
|
|
return false, -1, errors.Wrapf(err, "cannot write uid_map")
|
|
}
|
|
}
|
|
|
|
gidsMapped := false
|
|
if mappings != nil && gids != nil {
|
|
gidsMapped = tryMappingTool("newgidmap", pid, os.Getgid(), gids) == nil
|
|
}
|
|
if !gidsMapped {
|
|
gidMap := fmt.Sprintf("/proc/%d/gid_map", pid)
|
|
err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getgid())), 0666)
|
|
if err != nil {
|
|
return false, -1, errors.Wrapf(err, "cannot write gid_map")
|
|
}
|
|
}
|
|
|
|
_, err = w.Write([]byte("1"))
|
|
if err != nil {
|
|
return false, -1, errors.Wrapf(err, "write to sync pipe")
|
|
}
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
gosignal.Notify(c)
|
|
defer gosignal.Reset()
|
|
go func() {
|
|
for s := range c {
|
|
if s == signal.SIGCHLD || s == signal.SIGPIPE {
|
|
continue
|
|
}
|
|
|
|
syscall.Kill(int(pidC), s.(syscall.Signal))
|
|
}
|
|
}()
|
|
|
|
ret := C.reexec_in_user_namespace_wait(pidC)
|
|
if ret < 0 {
|
|
return false, -1, errors.Wrapf(err, "error waiting for the re-exec process")
|
|
}
|
|
|
|
return true, int(ret), nil
|
|
}
|