From 70b528e2aed9a9cc439f30dedf9ec84fec9a5994 Mon Sep 17 00:00:00 2001 From: wangxiao65 <287608437@qq.com> Date: Wed, 3 Mar 2021 15:28:18 +0800 Subject: [PATCH] fix CVE-2021-20188 --- CVE-2021-20188-pre1.patch | 181 ++++++++++++ CVE-2021-20188-pre2.patch | 181 ++++++++++++ CVE-2021-20188.patch | 321 +++++++++++++++++++++ builtin-remove-some-unused-functions.patch | 56 ++++ podman.spec | 15 +- v0.2.1.tar.gz | Bin 0 -> 10818 bytes 6 files changed, 753 insertions(+), 1 deletion(-) create mode 100644 CVE-2021-20188-pre1.patch create mode 100644 CVE-2021-20188-pre2.patch create mode 100644 CVE-2021-20188.patch create mode 100644 builtin-remove-some-unused-functions.patch create mode 100644 v0.2.1.tar.gz diff --git a/CVE-2021-20188-pre1.patch b/CVE-2021-20188-pre1.patch new file mode 100644 index 0000000..ed59904 --- /dev/null +++ b/CVE-2021-20188-pre1.patch @@ -0,0 +1,181 @@ +From 867669374c3fdd39f2629e53cbe7430f1bc3e085 Mon Sep 17 00:00:00 2001 +From: Debarshi Ray +Date: Tue, 8 Jan 2019 12:53:50 +0100 +Subject: [PATCH] Add a --workdir option to 'podman exec' + +Signed-off-by: Debarshi Ray +--- + cmd/podman/common.go | 9 +++++---- + cmd/podman/exec.go | 3 ++- + completions/bash/podman | 2 ++ + docs/podman-exec.1.md | 8 ++++++++ + libpod/container_api.go | 4 ++-- + libpod/oci.go | 6 ++++-- + test/e2e/exec_test.go | 32 ++++++++++++++++++++++++++++++++ + 7 files changed, 55 insertions(+), 9 deletions(-) + +diff --git a/cmd/podman/common.go b/cmd/podman/common.go +index 0fc9a6accfd..d934c869946 100644 +--- a/cmd/podman/common.go ++++ b/cmd/podman/common.go +@@ -28,6 +28,10 @@ var ( + Name: "latest, l", + Usage: "act on the latest pod podman is aware of", + } ++ WorkDirFlag = cli.StringFlag{ ++ Name: "workdir, w", ++ Usage: "Working directory inside the container", ++ } + ) + + const ( +@@ -522,10 +526,7 @@ var createFlags = []cli.Flag{ + Name: "volumes-from", + Usage: "Mount volumes from the specified container(s) (default [])", + }, +- cli.StringFlag{ +- Name: "workdir, w", +- Usage: "Working `directory inside the container", +- }, ++ WorkDirFlag, + } + + func getFormat(c *cli.Context) (string, error) { +diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go +index c03834dea23..073e72e6404 100644 +--- a/cmd/podman/exec.go ++++ b/cmd/podman/exec.go +@@ -34,6 +34,7 @@ var ( + Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", + }, + LatestFlag, ++ WorkDirFlag, + } + execDescription = ` + podman exec +@@ -108,5 +109,5 @@ func execCmd(c *cli.Context) error { + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) + } + +- return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user")) ++ return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"), c.String("workdir")) + } +diff --git a/completions/bash/podman b/completions/bash/podman +index d65f54690e3..e23615d5256 100644 +--- a/completions/bash/podman ++++ b/completions/bash/podman +@@ -1111,6 +1111,8 @@ _podman_exec() { + --env + --user + -u ++ --workdir ++ -w + " + local boolean_options=" + --latest +diff --git a/docs/podman-exec.1.md b/docs/podman-exec.1.md +index 284fa5a4a29..77317b0cabd 100644 +--- a/docs/podman-exec.1.md ++++ b/docs/podman-exec.1.md +@@ -38,6 +38,14 @@ Sets the username or UID used and optionally the groupname or GID for the specif + The following examples are all valid: + --user [user | user:group | uid | uid:gid | user:gid | uid:group ] + ++**--workdir**, **-w**="" ++ ++Working directory inside the container ++ ++The default working directory for running binaries within a container is the root directory (/). ++The image developer can set a different default with the WORKDIR instruction, which can be overridden ++when creating the container. ++ + ## SEE ALSO + podman(1), podman-run(1) + +diff --git a/libpod/container_api.go b/libpod/container_api.go +index 09bc46905ae..4eaf737b09a 100644 +--- a/libpod/container_api.go ++++ b/libpod/container_api.go +@@ -262,7 +262,7 @@ func (c *Container) Kill(signal uint) error { + // Exec starts a new process inside the container + // TODO allow specifying streams to attach to + // TODO investigate allowing exec without attaching +-func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) error { ++func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string) error { + var capList []string + + locked := false +@@ -324,7 +324,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e + + logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID) + +- execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, hostUser, sessionID) ++ execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID) + if err != nil { + return errors.Wrapf(err, "error exec %s", c.ID()) + } +diff --git a/libpod/oci.go b/libpod/oci.go +index 093bfdd3573..31c1a7e8514 100644 +--- a/libpod/oci.go ++++ b/libpod/oci.go +@@ -728,7 +728,7 @@ func (r *OCIRuntime) unpauseContainer(ctr *Container) error { + // TODO: Add --detach support + // TODO: Convert to use conmon + // TODO: add --pid-file and use that to generate exec session tracking +-func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, user, sessionID string) (*exec.Cmd, error) { ++func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string) (*exec.Cmd, error) { + if len(cmd) == 0 { + return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute") + } +@@ -749,7 +749,9 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty + + args = append(args, "exec") + +- args = append(args, "--cwd", c.config.Spec.Process.Cwd) ++ if cwd != "" { ++ args = append(args, "--cwd", cwd) ++ } + + args = append(args, "--pid-file", c.execPidPath(sessionID)) + +diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go +index fec80717fa2..a181501a5ff 100644 +--- a/test/e2e/exec_test.go ++++ b/test/e2e/exec_test.go +@@ -127,4 +127,36 @@ var _ = Describe("Podman exec", func() { + Expect(session2.ExitCode()).To(Equal(0)) + Expect(session2.OutputToString()).To(Equal(testUser)) + }) ++ ++ It("podman exec simple working directory test", func() { ++ setup := podmanTest.RunTopContainer("test1") ++ setup.WaitWithDefaultTimeout() ++ Expect(setup.ExitCode()).To(Equal(0)) ++ ++ session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/tmp", "pwd"}) ++ session.WaitWithDefaultTimeout() ++ Expect(session.ExitCode()).To(Equal(0)) ++ match, _ := session.GrepString("/tmp") ++ Expect(match).Should(BeTrue()) ++ ++ session = podmanTest.Podman([]string{"exec", "-l", "-w", "/tmp", "pwd"}) ++ session.WaitWithDefaultTimeout() ++ Expect(session.ExitCode()).To(Equal(0)) ++ match, _ = session.GrepString("/tmp") ++ Expect(match).Should(BeTrue()) ++ }) ++ ++ It("podman exec missing working directory test", func() { ++ setup := podmanTest.RunTopContainer("test1") ++ setup.WaitWithDefaultTimeout() ++ Expect(setup.ExitCode()).To(Equal(0)) ++ ++ session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/missing", "pwd"}) ++ session.WaitWithDefaultTimeout() ++ Expect(session.ExitCode()).To(Equal(1)) ++ ++ session = podmanTest.Podman([]string{"exec", "-l", "-w", "/missing", "pwd"}) ++ session.WaitWithDefaultTimeout() ++ Expect(session.ExitCode()).To(Equal(1)) ++ }) + }) diff --git a/CVE-2021-20188-pre2.patch b/CVE-2021-20188-pre2.patch new file mode 100644 index 0000000..492e7d2 --- /dev/null +++ b/CVE-2021-20188-pre2.patch @@ -0,0 +1,181 @@ +From 1dd7f13dfbc1dd377eabace0239b1c05cd60b144 Mon Sep 17 00:00:00 2001 +From: baude +Date: Thu, 25 Oct 2018 13:39:25 -0500 +Subject: [PATCH] get user and group information using securejoin and runc's + user library + +for the purposes of performance and security, we use securejoin to contstruct +the root fs's path so that symlinks are what they appear to be and no pointing +to something naughty. + +then instead of chrooting to parse /etc/passwd|/etc/group, we now use the runc user/group +methods which saves us quite a bit of performance. + +Signed-off-by: baude +--- + pkg/lookup/lookup.go | 156 +++++++++++++++++++++++++++++ + 1 files changed, 156 insertions(+) + create mode 100644 pkg/lookup/lookup.go + +diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go +new file mode 100644 +index 00000000000..b27e2a724bc +--- /dev/null ++++ b/pkg/lookup/lookup.go +@@ -0,0 +1,156 @@ ++package lookup ++ ++import ( ++ "github.com/cyphar/filepath-securejoin" ++ "github.com/opencontainers/runc/libcontainer/user" ++ "github.com/sirupsen/logrus" ++ "strconv" ++) ++ ++const ( ++ etcpasswd = "/etc/passwd" ++ etcgroup = "/etc/group" ++) ++ ++// Overrides allows you to override defaults in GetUserGroupInfo ++type Overrides struct { ++ DefaultUser *user.ExecUser ++ ContainerEtcPasswdPath string ++ ContainerEtcGroupPath string ++} ++ ++// GetUserGroupInfo takes string forms of the the container's mount path and the container user and ++// returns a ExecUser with uid, gid, sgids, and home. And override can be provided for defaults. ++func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) { ++ var ( ++ passwdDest, groupDest string ++ defaultExecUser *user.ExecUser ++ err error ++ ) ++ passwdPath := etcpasswd ++ groupPath := etcgroup ++ ++ if override != nil { ++ // Check for an override /etc/passwd path ++ if override.ContainerEtcPasswdPath != "" { ++ passwdPath = override.ContainerEtcPasswdPath ++ } ++ // Check for an override for /etc/group path ++ if override.ContainerEtcGroupPath != "" { ++ groupPath = override.ContainerEtcGroupPath ++ } ++ } ++ ++ // Check for an override default user ++ if override != nil && override.DefaultUser != nil { ++ defaultExecUser = override.DefaultUser ++ } else { ++ // Define a default container user ++ //defaultExecUser = &user.ExecUser{ ++ // Uid: 0, ++ // Gid: 0, ++ // Home: "/", ++ defaultExecUser = nil ++ ++ } ++ ++ // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty ++ if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { ++ logrus.Debug(err) ++ return nil, err ++ } ++ if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { ++ logrus.Debug(err) ++ return nil, err ++ } ++ return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) ++} ++ ++// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc ++// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found, ++// or the given group data is nil, the id will be returned as-is provided it is in the legal range. ++func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) { ++ var ( ++ groupDest string ++ err error ++ uintgids []uint32 ++ ) ++ ++ groupPath := etcgroup ++ if override != nil && override.ContainerEtcGroupPath != "" { ++ groupPath = override.ContainerEtcGroupPath ++ } ++ ++ if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { ++ logrus.Debug(err) ++ return nil, err ++ } ++ ++ gids, err := user.GetAdditionalGroupsPath(groups, groupDest) ++ if err != nil { ++ return nil, err ++ } ++ // For libpod, we want []uint32s ++ for _, gid := range gids { ++ uintgids = append(uintgids, uint32(gid)) ++ } ++ return uintgids, nil ++} ++ ++// GetUser takes a containermount path and user name or id and returns ++// a matching User structure from /etc/passwd. If it cannot locate a user ++// with the provided information, an ErrNoPasswdEntries is returned. ++func GetUser(containerMount, userIDorName string) (*user.User, error) { ++ var inputIsName bool ++ uid, err := strconv.Atoi(userIDorName) ++ if err != nil { ++ inputIsName = true ++ } ++ passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd) ++ if err != nil { ++ return nil, err ++ } ++ users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool { ++ if inputIsName { ++ return u.Name == userIDorName ++ } ++ return u.Uid == uid ++ }) ++ if err != nil { ++ return nil, err ++ } ++ if len(users) > 0 { ++ return &users[0], nil ++ } ++ return nil, user.ErrNoPasswdEntries ++} ++ ++// GetGroup takes ac ontainermount path and a group name or id and returns ++// a match Group struct from /etc/group. if it cannot locate a group, ++// an ErrNoGroupEntries error is returned. ++func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { ++ var inputIsName bool ++ gid, err := strconv.Atoi(groupIDorName) ++ if err != nil { ++ inputIsName = true ++ } ++ ++ groupDest, err := securejoin.SecureJoin(containerMount, etcgroup) ++ if err != nil { ++ return nil, err ++ } ++ ++ groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool { ++ if inputIsName { ++ return g.Name == groupIDorName ++ } ++ return g.Gid == gid ++ }) ++ if err != nil { ++ return nil, err ++ } ++ if len(groups) > 0 { ++ return &groups[0], nil ++ } ++ return nil, user.ErrNoGroupEntries ++} diff --git a/CVE-2021-20188.patch b/CVE-2021-20188.patch new file mode 100644 index 0000000..f7a780a --- /dev/null +++ b/CVE-2021-20188.patch @@ -0,0 +1,321 @@ +From 69daa67c436a8fdeb0149aa5cb0112f03fdb699f Mon Sep 17 00:00:00 2001 +From: Matthew Heon +Date: Mon, 25 Jan 2021 14:18:07 -0500 +Subject: [PATCH] Correct handling of capabilities + +Ensure that capabilities are properly handled for non-root users +in privileged containers. We do not want to give full caps, but +instead only CapInh and CapEff (others should be all-zeroes). + +Fixing `podman run` is easy - the same code as the Podman 1.6 fix +works there. The `podman exec` command is far more challenging. +Exec received a complete rewrite to use Conmon at some point +before Podman 1.6, and gained many capabilities in the process. +One of those was the ability to actually tweak the capabilities +of the exec process - 1.0 did not have that. Since it was needed +to resolve this CVE, I was forced to backport a large bit of the +1.0 -> 1.6 exec changes (passing a Process block to the OCI +runtime, and using `prepareProcessExec()` to prepare said block). +I am honestly uncomfortable with the size and scope of this +change but I don't see another way around this. + +Fixes CVE-2021-20188 + +Signed-off-by: Matthew Heon +--- + libpod/container_api.go | 24 +------ + libpod/oci.go | 148 ++++++++++++++++++++++++++++++++-------- + pkg/spec/spec.go | 8 +++ + 3 files changed, 132 insertions(+), 48 deletions(-) + +diff --git a/libpod/container_api.go b/libpod/container_api.go +index fe66abf7a8f..b10596f6228 100644 +--- a/libpod/container_api.go ++++ b/libpod/container_api.go +@@ -2,7 +2,6 @@ package libpod + + import ( + "context" +- "fmt" + "io/ioutil" + "os" + "strconv" +@@ -10,10 +9,8 @@ import ( + "time" + + "github.com/containers/libpod/libpod/driver" +- "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/inspect" + "github.com/containers/storage/pkg/stringid" +- "github.com/docker/docker/daemon/caps" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" +@@ -260,8 +258,6 @@ func (c *Container) Kill(signal uint) er + // TODO allow specifying streams to attach to + // TODO investigate allowing exec without attaching + func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string) error { +- var capList []string +- + locked := false + if !c.batched { + locked = true +@@ -284,22 +280,8 @@ func (c *Container) Exec(tty, privileged + if conState != ContainerStateRunning { + return errors.Errorf("cannot exec into container that is not running") + } +- if privileged || c.config.Privileged { +- capList = caps.GetAllCapabilities() +- } +- +- // If user was set, look it up in the container to get a UID to use on +- // the host +- hostUser := "" +- if user != "" { +- uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, user) +- if err != nil { +- return errors.Wrapf(err, "error getting user to launch exec session as") +- } + +- // runc expects user formatted as uid:gid +- hostUser = fmt.Sprintf("%d:%d", uid, gid) +- } ++ isPrivileged := privileged || c.config.Privileged + + // Generate exec session ID + // Ensure we don't conflict with an existing session ID +@@ -321,10 +303,11 @@ func (c *Container) Exec(tty, privileged + + logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID) + +- execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID) ++ execCmd, processFile, err := c.runtime.ociRuntime.execContainer(c, cmd, env, tty, workDir, user, sessionID, isPrivileged) + if err != nil { + return errors.Wrapf(err, "error exec %s", c.ID()) + } ++ defer os.Remove(processFile) + + pidFile := c.execPidPath(sessionID) + // 1 second seems a reasonable time to wait +diff --git a/libpod/oci.go b/libpod/oci.go +index a1894b52fbe..79217dced5d 100644 +--- a/libpod/oci.go ++++ b/libpod/oci.go +@@ -15,10 +15,12 @@ import ( + "syscall" + "time" + ++ "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/ctime" + "github.com/containers/libpod/pkg/rootless" + "github.com/coreos/go-systemd/activation" + "github.com/cri-o/ocicni/pkg/ocicni" ++ "github.com/docker/docker/daemon/caps" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux/label" +@@ -689,18 +691,23 @@ func (r *OCIRuntime) unpauseContainer(ct + // TODO: Add --detach support + // TODO: Convert to use conmon + // TODO: add --pid-file and use that to generate exec session tracking +-func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string) (*exec.Cmd, error) { ++func (r *OCIRuntime) execContainer(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string, privileged bool) (*exec.Cmd, string, error) { + if len(cmd) == 0 { +- return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute") ++ return nil, "", errors.Wrapf(ErrInvalidArg, "must provide a command to execute") + } + + if sessionID == "" { +- return nil, errors.Wrapf(ErrEmptyID, "must provide a session ID for exec") ++ return nil, "", errors.Wrapf(ErrEmptyID, "must provide a session ID for exec") + } + + runtimeDir, err := GetRootlessRuntimeDir() + if err != nil { +- return nil, err ++ return nil, "", err ++ } ++ ++ processFile, err := prepareProcessExec(c, cmd, env, tty, cwd, user, sessionID, privileged) ++ if err != nil { ++ return nil, "", err + } + + args := []string{} +@@ -710,32 +717,14 @@ func (r *OCIRuntime) execContainer(c *Co + + args = append(args, "exec") + +- if cwd != "" { +- args = append(args, "--cwd", cwd) +- } ++ args = append(args, "--process", processFile) + + args = append(args, "--pid-file", c.execPidPath(sessionID)) + +- if tty { +- args = append(args, "--tty") +- } +- +- if user != "" { +- args = append(args, "--user", user) +- } +- + if c.config.Spec.Process.NoNewPrivileges { + args = append(args, "--no-new-privs") + } + +- for _, cap := range capAdd { +- args = append(args, "--cap", cap) +- } +- +- for _, envVar := range env { +- args = append(args, "--env", envVar) +- } +- + // Append container ID and command + args = append(args, c.ID()) + args = append(args, cmd...) +@@ -749,10 +738,10 @@ func (r *OCIRuntime) execContainer(c *Co + execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + + if err := execCmd.Start(); err != nil { +- return nil, errors.Wrapf(err, "cannot start container %s", c.ID()) ++ return nil, "", errors.Wrapf(err, "cannot start container %s", c.ID()) + } + +- return execCmd, nil ++ return execCmd, processFile, nil + } + + // execStopContainer stops all active exec sessions in a container +@@ -831,3 +820,110 @@ func (r *OCIRuntime) checkpointContainer + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "checkpoint", + "--image-path", imagePath, "--work-path", workPath, ctr.ID()) + } ++ ++// prepareProcessExec returns the path of the process.json used in runc exec -p. ++// Returns path to the created exec process file. This will need to be removed ++// by the caller when they're done, best effort. ++func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string, privileged bool) (string, error) { ++ filename := filepath.Join(c.bundlePath(), fmt.Sprintf("exec-process-%s", sessionID)) ++ f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0600) ++ if err != nil { ++ return "", err ++ } ++ defer f.Close() ++ ++ pspec := c.config.Spec.Process ++ pspec.SelinuxLabel = c.config.ProcessLabel ++ pspec.Args = cmd ++ // We need to default this to false else it will inherit terminal as true ++ // from the container. ++ pspec.Terminal = false ++ if tty { ++ pspec.Terminal = true ++ } ++ if len(env) > 0 { ++ pspec.Env = append(pspec.Env, env...) ++ } ++ ++ if cwd != "" { ++ pspec.Cwd = cwd ++ ++ } ++ ++ var addGroups []string ++ var sgids []uint32 ++ ++ // if the user is empty, we should inherit the user that the container is currently running with ++ if user == "" { ++ user = c.config.User ++ addGroups = c.config.Groups ++ } ++ ++ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) ++ if err != nil { ++ return "", err ++ } ++ ++ if len(addGroups) > 0 { ++ sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, nil) ++ if err != nil { ++ return "", errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID) ++ } ++ } ++ ++ // If user was set, look it up in the container to get a UID to use on ++ // the host ++ if user != "" || len(sgids) > 0 { ++ if user != "" { ++ for _, sgid := range execUser.Sgids { ++ sgids = append(sgids, uint32(sgid)) ++ } ++ } ++ processUser := spec.User{ ++ UID: uint32(execUser.Uid), ++ GID: uint32(execUser.Gid), ++ AdditionalGids: sgids, ++ } ++ ++ pspec.User = processUser ++ } ++ ++ allCaps := caps.GetAllCapabilities() ++ pspec.Capabilities.Effective = []string{} ++ if privileged { ++ pspec.Capabilities.Bounding = allCaps ++ } else { ++ pspec.Capabilities.Bounding = []string{} ++ } ++ pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding ++ if execUser.Uid == 0 { ++ pspec.Capabilities.Effective = pspec.Capabilities.Bounding ++ pspec.Capabilities.Permitted = pspec.Capabilities.Bounding ++ pspec.Capabilities.Ambient = pspec.Capabilities.Bounding ++ } else { ++ pspec.Capabilities.Permitted = pspec.Capabilities.Effective ++ pspec.Capabilities.Ambient = pspec.Capabilities.Effective ++ } ++ ++ hasHomeSet := false ++ for _, s := range pspec.Env { ++ if strings.HasPrefix(s, "HOME=") { ++ hasHomeSet = true ++ break ++ } ++ } ++ if !hasHomeSet { ++ pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) ++ } ++ ++ processJSON, err := json.Marshal(pspec) ++ if err != nil { ++ return "", err ++ } ++ ++ if err := ioutil.WriteFile(filename, processJSON, 0644); err != nil { ++ return "", err ++ } ++ ++ return filename, nil ++} +diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go +index 46105af4aef..d4f13711859 100644 +--- a/pkg/spec/spec.go ++++ b/pkg/spec/spec.go +@@ -325,6 +325,14 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint + } + } else { + g.SetupPrivileged(true) ++ if config.User != "" { ++ user := strings.SplitN(config.User, ":", 2)[0] ++ if user != "root" && user != "0" { ++ g.Spec().Process.Capabilities.Effective = []string{} ++ g.Spec().Process.Capabilities.Permitted = []string{} ++ g.Spec().Process.Capabilities.Ambient = []string{} ++ } ++ } + } + + // HANDLE SECCOMP diff --git a/builtin-remove-some-unused-functions.patch b/builtin-remove-some-unused-functions.patch new file mode 100644 index 0000000..422a137 --- /dev/null +++ b/builtin-remove-some-unused-functions.patch @@ -0,0 +1,56 @@ +From 7af23efc78cbcbf462ae58e30fc4b94e99f09436 Mon Sep 17 00:00:00 2001 +From: Giuseppe Scrivano +Date: Mon, 12 Nov 2018 18:07:21 +0100 +Subject: [PATCH] builtin.go.h: remove some unused functions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +it helps to solve a warning with cgo: + +In file included from /usr/include/glib-2.0/glib/glist.h:32, + from /usr/include/glib-2.0/glib/ghash.h:33, + from /usr/include/glib-2.0/glib.h:50, + from vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/commit.go:16: +vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h: In function ‘_g_clear_object’: +/usr/include/glib-2.0/glib/gmem.h:121:18: warning: passing argument 1 of ‘g_object_unref’ discards ‘volatile’ qualifier from pointer target type [-Wdiscarded-qualifiers] + (destroy) (_ptr); \ + ^~~~ +/usr/include/glib-2.0/gobject/gobject.h:6 + +Closes: https://github.com/ostreedev/ostree-go/issues/23 + +Signed-off-by: Giuseppe Scrivano +--- + vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h | 12 ------------ + 1 file changed, 12 deletions(-) + +diff --git a/pkg/otbuiltin/builtin.go.h b/pkg/otbuiltin/builtin.go.h +index 734de98..7617155 100644 +--- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h ++++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h +@@ -33,24 +33,12 @@ _ostree_repo_file(GFile *file) + return OSTREE_REPO_FILE (file); + } + +-static guint +-_gpointer_to_uint (gpointer ptr) +-{ +- return GPOINTER_TO_UINT (ptr); +-} +- + static gpointer + _guint_to_pointer (guint u) + { + return GUINT_TO_POINTER (u); + } + +-static void +-_g_clear_object (volatile GObject **object_ptr) +-{ +- g_clear_object(object_ptr); +-} +- + static const GVariantType* + _g_variant_type (char *type) + { diff --git a/podman.spec b/podman.spec index e5cb6d8..0f911bf 100644 --- a/podman.spec +++ b/podman.spec @@ -1,12 +1,13 @@ Name: podman Version: 0.10.1 -Release: 7 +Release: 8 Summary: A daemonless container engine for managing Containers Epoch: 1 License: ASL 2.0 URL: https://podman.io/ Source0: https://github.com/containers/libpod/archive/e4a155328fb88590fafd3d4e845f9bca49133f62/libpod-e4a1553.tar.gz Source1: https://github.com/cpuguy83/go-md2man/archive/v1.0.10.tar.gz +Source2: https://github.com/cyphar/filepath-securejoin/archive/v0.2.1.tar.gz BuildRequires: golang btrfs-progs-devel glib2-devel glibc-devel glibc-static BuildRequires: git gpgme-devel libassuan-devel libgpg-error-devel libseccomp-devel BuildRequires: libselinux-devel ostree-devel pkgconfig make @@ -110,6 +111,10 @@ Provides: bundled(golang(k8s.io/kube-openapi)) = 275e2ce91dec4c05a4094a7b1daee55 Provides: bundled(golang(k8s.io/utils)) = 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e Patch1: 0001-podman-patch-for-local-search.patch +Patch2: builtin-remove-some-unused-functions.patch +Patch3: CVE-2021-20188-pre1.patch +Patch4: CVE-2021-20188-pre2.patch +Patch5: CVE-2021-20188.patch %description Podman manages the entire container ecosystem which includes pods, @@ -157,8 +162,12 @@ sed -i 's/0.0.0/%{version}/' contrib/python/%{name}/setup.py sed -i 's/0.0.0/%{version}/' contrib/python/py%{name}/setup.py mv pkg/hooks/README.md pkg/hooks/README-hooks.md tar -xf %SOURCE1 +tar -xf %SOURCE2 +rm -rf filepath-securejoin-0.2.1/vendor %build +mkdir -p vendor/github.com/cyphar/filepath-securejoin +cp filepath-securejoin-0.2.1/* vendor/github.com/cyphar/filepath-securejoin mkdir -p _build/bin _output/bin cd go-md2man-* go build -mod=vendor -o ../_build/bin/go-md2man . @@ -218,6 +227,10 @@ install -Dp -m644 libpod.conf %{buildroot}%{_datadir}/containers/libpod.conf %{_mandir}/man5/*.5* %changelog +* Wed Mar 3 2021 wangxiao - 1:0.10.1-8 +- Add missing bundled(golang(github.com/cyphar/filepath-securejoin) +- Fix CVE-2021-20188 + * Thu Feb 18 2021 lingsheng - 1:0.10.1-7 - Resolve go-md2man dependency diff --git a/v0.2.1.tar.gz b/v0.2.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b5d847ed64022ce6c20a7cecbc18585eeacde38f GIT binary patch literal 10818 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