65c23d
From 69daa67c436a8fdeb0149aa5cb0112f03fdb699f Mon Sep 17 00:00:00 2001
65c23d
From: Matthew Heon <mheon@redhat.com>
65c23d
Date: Mon, 25 Jan 2021 14:18:07 -0500
65c23d
Subject: [PATCH] Correct handling of capabilities
65c23d
65c23d
Ensure that capabilities are properly handled for non-root users
65c23d
in privileged containers. We do not want to give full caps, but
65c23d
instead only CapInh and CapEff (others should be all-zeroes).
65c23d
65c23d
Fixing `podman run` is easy - the same code as the Podman 1.6 fix
65c23d
works there. The `podman exec` command is far more challenging.
65c23d
Exec received a complete rewrite to use Conmon at some point
65c23d
before Podman 1.6, and gained many capabilities in the process.
65c23d
One of those was the ability to actually tweak the capabilities
65c23d
of the exec process - 1.0 did not have that. Since it was needed
65c23d
to resolve this CVE, I was forced to backport a large bit of the
65c23d
1.0 -> 1.6 exec changes (passing a Process block to the OCI
65c23d
runtime, and using `prepareProcessExec()` to prepare said block).
65c23d
I am honestly uncomfortable with the size and scope of this
65c23d
change but I don't see another way around this.
65c23d
65c23d
Fixes CVE-2021-20188
65c23d
65c23d
Signed-off-by: Matthew Heon <mheon@redhat.com>
65c23d
---
65c23d
 libpod/container_api.go |  24 +------
65c23d
 libpod/oci.go           | 148 ++++++++++++++++++++++++++++++++--------
65c23d
 pkg/spec/spec.go        |   8 +++
65c23d
 3 files changed, 132 insertions(+), 48 deletions(-)
65c23d
65c23d
diff -up libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/container_api.go.orig libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/container_api.go
65c23d
--- libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/container_api.go.orig	2019-02-11 16:26:46.000000000 +0100
65c23d
+++ libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/container_api.go	2021-02-12 10:38:48.767172399 +0100
65c23d
@@ -2,7 +2,6 @@ package libpod
65c23d
 
65c23d
 import (
65c23d
 	"context"
65c23d
-	"fmt"
65c23d
 	"io/ioutil"
65c23d
 	"os"
65c23d
 	"strconv"
65c23d
@@ -11,9 +10,7 @@ import (
65c23d
 
65c23d
 	"github.com/containers/libpod/libpod/driver"
65c23d
 	"github.com/containers/libpod/pkg/inspect"
65c23d
-	"github.com/containers/libpod/pkg/lookup"
65c23d
 	"github.com/containers/storage/pkg/stringid"
65c23d
-	"github.com/docker/docker/daemon/caps"
65c23d
 	"github.com/pkg/errors"
65c23d
 	"github.com/sirupsen/logrus"
65c23d
 	"k8s.io/apimachinery/pkg/util/wait"
65c23d
@@ -263,8 +260,6 @@ func (c *Container) Kill(signal uint) er
65c23d
 // TODO allow specifying streams to attach to
65c23d
 // TODO investigate allowing exec without attaching
65c23d
 func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string) error {
65c23d
-	var capList []string
65c23d
-
65c23d
 	locked := false
65c23d
 	if !c.batched {
65c23d
 		locked = true
65c23d
@@ -287,22 +282,8 @@ func (c *Container) Exec(tty, privileged
65c23d
 	if conState != ContainerStateRunning {
65c23d
 		return errors.Errorf("cannot exec into container that is not running")
65c23d
 	}
65c23d
-	if privileged || c.config.Privileged {
65c23d
-		capList = caps.GetAllCapabilities()
65c23d
-	}
65c23d
 
65c23d
-	// If user was set, look it up in the container to get a UID to use on
65c23d
-	// the host
65c23d
-	hostUser := ""
65c23d
-	if user != "" {
65c23d
-		execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil)
65c23d
-		if err != nil {
65c23d
-			return err
65c23d
-		}
65c23d
-
65c23d
-		// runc expects user formatted as uid:gid
65c23d
-		hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.Gid)
65c23d
-	}
65c23d
+	isPrivileged := privileged || c.config.Privileged
65c23d
 
65c23d
 	// Generate exec session ID
65c23d
 	// Ensure we don't conflict with an existing session ID
65c23d
@@ -324,10 +305,11 @@ func (c *Container) Exec(tty, privileged
65c23d
 
65c23d
 	logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID)
65c23d
 
65c23d
-	execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID)
65c23d
+	execCmd, processFile, err := c.runtime.ociRuntime.execContainer(c, cmd, env, tty, workDir, user, sessionID, isPrivileged)
65c23d
 	if err != nil {
65c23d
 		return errors.Wrapf(err, "error exec %s", c.ID())
65c23d
 	}
65c23d
+	defer os.Remove(processFile)
65c23d
 	chWait := make(chan error)
65c23d
 	go func() {
65c23d
 		chWait <- execCmd.Wait()
65c23d
diff -up libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/oci.go.orig libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/oci.go
65c23d
--- libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/oci.go.orig	2019-02-11 16:26:46.000000000 +0100
65c23d
+++ libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/libpod/oci.go	2021-02-12 10:38:48.768172416 +0100
65c23d
@@ -15,10 +15,12 @@ import (
65c23d
 	"syscall"
65c23d
 	"time"
65c23d
 
65c23d
+	"github.com/containers/libpod/pkg/lookup"
65c23d
 	"github.com/containers/libpod/pkg/rootless"
65c23d
 	"github.com/containers/libpod/pkg/util"
65c23d
 	"github.com/coreos/go-systemd/activation"
65c23d
 	"github.com/cri-o/ocicni/pkg/ocicni"
65c23d
+	"github.com/docker/docker/daemon/caps"
65c23d
 	spec "github.com/opencontainers/runtime-spec/specs-go"
65c23d
 	"github.com/opencontainers/selinux/go-selinux"
65c23d
 	"github.com/opencontainers/selinux/go-selinux/label"
65c23d
@@ -735,18 +737,23 @@ func (r *OCIRuntime) unpauseContainer(ct
65c23d
 // TODO: Add --detach support
65c23d
 // TODO: Convert to use conmon
65c23d
 // TODO: add --pid-file and use that to generate exec session tracking
65c23d
-func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string) (*exec.Cmd, error) {
65c23d
+func (r *OCIRuntime) execContainer(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string, privileged bool) (*exec.Cmd, string, error) {
65c23d
 	if len(cmd) == 0 {
65c23d
-		return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute")
65c23d
+		return nil, "", errors.Wrapf(ErrInvalidArg, "must provide a command to execute")
65c23d
 	}
65c23d
 
65c23d
 	if sessionID == "" {
65c23d
-		return nil, errors.Wrapf(ErrEmptyID, "must provide a session ID for exec")
65c23d
+		return nil, "", errors.Wrapf(ErrEmptyID, "must provide a session ID for exec")
65c23d
 	}
65c23d
 
65c23d
 	runtimeDir, err := util.GetRootlessRuntimeDir()
65c23d
 	if err != nil {
65c23d
-		return nil, err
65c23d
+		return nil, "", err
65c23d
+	}
65c23d
+
65c23d
+	processFile, err := prepareProcessExec(c, cmd, env, tty, cwd, user, sessionID, privileged)
65c23d
+	if err != nil {
65c23d
+		return nil, "", err
65c23d
 	}
65c23d
 
65c23d
 	args := []string{}
65c23d
@@ -756,34 +763,14 @@ func (r *OCIRuntime) execContainer(c *Co
65c23d
 
65c23d
 	args = append(args, "exec")
65c23d
 
65c23d
-	if cwd != "" {
65c23d
-		args = append(args, "--cwd", cwd)
65c23d
-	}
65c23d
+	args = append(args, "--process", processFile)
65c23d
 
65c23d
 	args = append(args, "--pid-file", c.execPidPath(sessionID))
65c23d
 
65c23d
-	if tty {
65c23d
-		args = append(args, "--tty")
65c23d
-	} else {
65c23d
-		args = append(args, "--tty=false")
65c23d
-	}
65c23d
-
65c23d
-	if user != "" {
65c23d
-		args = append(args, "--user", user)
65c23d
-	}
65c23d
-
65c23d
 	if c.config.Spec.Process.NoNewPrivileges {
65c23d
 		args = append(args, "--no-new-privs")
65c23d
 	}
65c23d
 
65c23d
-	for _, cap := range capAdd {
65c23d
-		args = append(args, "--cap", cap)
65c23d
-	}
65c23d
-
65c23d
-	for _, envVar := range env {
65c23d
-		args = append(args, "--env", envVar)
65c23d
-	}
65c23d
-
65c23d
 	// Append container ID and command
65c23d
 	args = append(args, c.ID())
65c23d
 	args = append(args, cmd...)
65c23d
@@ -797,10 +784,10 @@ func (r *OCIRuntime) execContainer(c *Co
65c23d
 	execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
65c23d
 
65c23d
 	if err := execCmd.Start(); err != nil {
65c23d
-		return nil, errors.Wrapf(err, "cannot start container %s", c.ID())
65c23d
+		return nil, "", errors.Wrapf(err, "cannot start container %s", c.ID())
65c23d
 	}
65c23d
 
65c23d
-	return execCmd, nil
65c23d
+	return execCmd, processFile, nil
65c23d
 }
65c23d
 
65c23d
 // execStopContainer stops all active exec sessions in a container
65c23d
@@ -892,3 +879,110 @@ func (r *OCIRuntime) checkpointContainer
65c23d
 	args = append(args, ctr.ID())
65c23d
 	return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...)
65c23d
 }
65c23d
+
65c23d
+// prepareProcessExec returns the path of the process.json used in runc exec -p.
65c23d
+// Returns path to the created exec process file. This will need to be removed
65c23d
+// by the caller when they're done, best effort.
65c23d
+func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string, privileged bool) (string, error) {
65c23d
+	filename := filepath.Join(c.bundlePath(), fmt.Sprintf("exec-process-%s", sessionID))
65c23d
+	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0600)
65c23d
+	if err != nil {
65c23d
+		return "", err
65c23d
+	}
65c23d
+	defer f.Close()
65c23d
+
65c23d
+	pspec := c.config.Spec.Process
65c23d
+	pspec.SelinuxLabel = c.config.ProcessLabel
65c23d
+	pspec.Args = cmd
65c23d
+	// We need to default this to false else it will inherit terminal as true
65c23d
+	// from the container.
65c23d
+	pspec.Terminal = false
65c23d
+	if tty {
65c23d
+		pspec.Terminal = true
65c23d
+	}
65c23d
+	if len(env) > 0 {
65c23d
+		pspec.Env = append(pspec.Env, env...)
65c23d
+	}
65c23d
+
65c23d
+	if cwd != "" {
65c23d
+		pspec.Cwd = cwd
65c23d
+
65c23d
+	}
65c23d
+
65c23d
+	var addGroups []string
65c23d
+	var sgids []uint32
65c23d
+
65c23d
+	// if the user is empty, we should inherit the user that the container is currently running with
65c23d
+	if user == "" {
65c23d
+		user = c.config.User
65c23d
+		addGroups = c.config.Groups
65c23d
+	}
65c23d
+
65c23d
+	execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil)
65c23d
+	if err != nil {
65c23d
+		return "", err
65c23d
+	}
65c23d
+
65c23d
+	if len(addGroups) > 0 {
65c23d
+		sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, nil)
65c23d
+		if err != nil {
65c23d
+			return "", errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID)
65c23d
+		}
65c23d
+	}
65c23d
+
65c23d
+	// If user was set, look it up in the container to get a UID to use on
65c23d
+	// the host
65c23d
+	if user != "" || len(sgids) > 0 {
65c23d
+		if user != "" {
65c23d
+			for _, sgid := range execUser.Sgids {
65c23d
+				sgids = append(sgids, uint32(sgid))
65c23d
+			}
65c23d
+		}
65c23d
+		processUser := spec.User{
65c23d
+			UID:            uint32(execUser.Uid),
65c23d
+			GID:            uint32(execUser.Gid),
65c23d
+			AdditionalGids: sgids,
65c23d
+		}
65c23d
+
65c23d
+		pspec.User = processUser
65c23d
+	}
65c23d
+
65c23d
+	allCaps := caps.GetAllCapabilities()
65c23d
+	pspec.Capabilities.Effective = []string{}
65c23d
+	if privileged {
65c23d
+		pspec.Capabilities.Bounding = allCaps
65c23d
+	} else {
65c23d
+		pspec.Capabilities.Bounding = []string{}
65c23d
+	}
65c23d
+	pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding
65c23d
+	if execUser.Uid == 0 {
65c23d
+		pspec.Capabilities.Effective = pspec.Capabilities.Bounding
65c23d
+		pspec.Capabilities.Permitted = pspec.Capabilities.Bounding
65c23d
+		pspec.Capabilities.Ambient = pspec.Capabilities.Bounding
65c23d
+	} else {
65c23d
+		pspec.Capabilities.Permitted = pspec.Capabilities.Effective
65c23d
+		pspec.Capabilities.Ambient = pspec.Capabilities.Effective
65c23d
+	}
65c23d
+
65c23d
+	hasHomeSet := false
65c23d
+	for _, s := range pspec.Env {
65c23d
+		if strings.HasPrefix(s, "HOME=") {
65c23d
+			hasHomeSet = true
65c23d
+			break
65c23d
+		}
65c23d
+	}
65c23d
+	if !hasHomeSet {
65c23d
+		pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home))
65c23d
+	}
65c23d
+
65c23d
+	processJSON, err := json.Marshal(pspec)
65c23d
+	if err != nil {
65c23d
+		return "", err
65c23d
+	}
65c23d
+
65c23d
+	if err := ioutil.WriteFile(filename, processJSON, 0644); err != nil {
65c23d
+		return "", err
65c23d
+	}
65c23d
+
65c23d
+	return filename, nil
65c23d
+}
65c23d
diff -up libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/pkg/spec/spec.go.orig libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/pkg/spec/spec.go
65c23d
--- libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/pkg/spec/spec.go.orig	2019-02-11 16:26:46.000000000 +0100
65c23d
+++ libpod-921f98f8795eb9fcb19ce581020cfdeff6dee09f/pkg/spec/spec.go	2021-02-12 10:38:48.768172416 +0100
65c23d
@@ -325,6 +325,14 @@ func CreateConfigToOCISpec(config *Creat
65c23d
 		}
65c23d
 	} else {
65c23d
 		g.SetupPrivileged(true)
65c23d
+		if config.User != "" {
65c23d
+			user := strings.SplitN(config.User, ":", 2)[0]
65c23d
+			if user != "root" && user != "0" {
65c23d
+				g.Spec().Process.Capabilities.Effective = []string{}
65c23d
+				g.Spec().Process.Capabilities.Permitted = []string{}
65c23d
+				g.Spec().Process.Capabilities.Ambient = []string{}
65c23d
+			}
65c23d
+		}
65c23d
 	}
65c23d
 
65c23d
 	// HANDLE SECCOMP