From ef4d5b0ae34d3306d30f37b6bb4f4f68aab7e98f Mon Sep 17 00:00:00 2001 From: wangxiao65 <287608437@qq.com> Date: Thu, 4 Mar 2021 09:31:37 +0800 Subject: [PATCH] fix CVE-2021-20188 (cherry picked from commit 2be833b9f77a8497c18c78391a82f3f3acfeab0c) --- CVE-2021-20188-PRE1.patch | 371 ++++++++++++++++++++++++++++++++++++ CVE-2021-20188-PRE2.patch | 388 ++++++++++++++++++++++++++++++++++++++ CVE-2021-20188-PRE3.patch | 181 ++++++++++++++++++ CVE-2021-20188.patch | 320 +++++++++++++++++++++++++++++++ podman.spec | 24 ++- 5 files changed, 1280 insertions(+), 4 deletions(-) create mode 100644 CVE-2021-20188-PRE1.patch create mode 100644 CVE-2021-20188-PRE2.patch create mode 100644 CVE-2021-20188-PRE3.patch create mode 100644 CVE-2021-20188.patch diff --git a/CVE-2021-20188-PRE1.patch b/CVE-2021-20188-PRE1.patch new file mode 100644 index 0000000..04aa685 --- /dev/null +++ b/CVE-2021-20188-PRE1.patch @@ -0,0 +1,371 @@ +From 6246942d377bd9ed665a4ac448120352454dd83d Mon Sep 17 00:00:00 2001 +From: baude +Date: Wed, 24 Oct 2018 10:39:12 -0500 +Subject: [PATCH] Increase security and performance when looking up groups + +We implement the securejoin method to make sure the paths to /etc/passwd and +/etc/group are not symlinks to something naughty or outside the container +image. And then instead of actually chrooting, we use the runc functions to +get information about a user. The net result is increased security and +a a performance gain from 41ms to 100us. + +Signed-off-by: baude +--- + libpod/container_internal_linux.go | 26 +++- + .../cyphar/filepath-securejoin/LICENSE | 28 ++++ + .../cyphar/filepath-securejoin/README.md | 65 +++++++++ + .../cyphar/filepath-securejoin/join.go | 134 ++++++++++++++++++ + .../cyphar/filepath-securejoin/vendor.conf | 1 + + .../cyphar/filepath-securejoin/vfs.go | 41 ++++++ + 6 files changed, 291 insertions(+), 4 deletions(-) + create mode 100644 vendor/github.com/cyphar/filepath-securejoin/LICENSE + create mode 100644 vendor/github.com/cyphar/filepath-securejoin/README.md + create mode 100644 vendor/github.com/cyphar/filepath-securejoin/join.go + create mode 100644 vendor/github.com/cyphar/filepath-securejoin/vendor.conf + create mode 100644 vendor/github.com/cyphar/filepath-securejoin/vfs.go + +diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go +index b25645e5cc0..5a6b72580d5 100644 +--- a/libpod/container_internal_linux.go ++++ b/libpod/container_internal_linux.go +@@ -21,6 +21,8 @@ import ( + "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/idtools" ++ "github.com/cyphar/filepath-securejoin" ++ "github.com/opencontainers/runc/libcontainer/user" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" +@@ -197,12 +199,28 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + + // Look up and add groups the user belongs to, if a group wasn't directly specified + if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") { +- groups, err := chrootuser.GetAdditionalGroupsForUser(c.state.Mountpoint, uint64(g.Config.Process.User.UID)) +- if err != nil && errors.Cause(err) != chrootuser.ErrNoSuchUser { ++ var groupDest, passwdDest string ++ defaultExecUser := user.ExecUser{ ++ Uid: 0, ++ Gid: 0, ++ Home: "/", ++ } ++ ++ // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty ++ if groupDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/group"); err != nil { ++ logrus.Debug(err) + return nil, err + } +- for _, gid := range groups { +- g.AddProcessAdditionalGid(gid) ++ if passwdDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd"); err != nil { ++ logrus.Debug(err) ++ return nil, err ++ } ++ execUser, err := user.GetExecUserPath(c.config.User, &defaultExecUser, passwdDest, groupDest) ++ if err != nil { ++ return nil, err ++ } ++ for _, gid := range execUser.Sgids { ++ g.AddProcessAdditionalGid(uint32(gid)) + } + } + +diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE +new file mode 100644 +index 00000000000..bec842f294f +--- /dev/null ++++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE +@@ -0,0 +1,28 @@ ++Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. ++Copyright (C) 2017 SUSE LLC. All rights reserved. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions are ++met: ++ ++ * Redistributions of source code must retain the above copyright ++notice, this list of conditions and the following disclaimer. ++ * Redistributions in binary form must reproduce the above ++copyright notice, this list of conditions and the following disclaimer ++in the documentation and/or other materials provided with the ++distribution. ++ * Neither the name of Google Inc. nor the names of its ++contributors may be used to endorse or promote products derived from ++this software without specific prior written permission. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ++A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ++OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md +new file mode 100644 +index 00000000000..49b2baa9f35 +--- /dev/null ++++ b/vendor/github.com/cyphar/filepath-securejoin/README.md +@@ -0,0 +1,65 @@ ++## `filepath-securejoin` ## ++ ++[![Build Status](https://travis-ci.org/cyphar/filepath-securejoin.svg?branch=master)](https://travis-ci.org/cyphar/filepath-securejoin) ++ ++An implementation of `SecureJoin`, a [candidate for inclusion in the Go ++standard library][go#20126]. The purpose of this function is to be a "secure" ++alternative to `filepath.Join`, and in particular it provides certain ++guarantees that are not provided by `filepath.Join`. ++ ++This is the function prototype: ++ ++```go ++func SecureJoin(root, unsafePath string) (string, error) ++``` ++ ++This library **guarantees** the following: ++ ++* If no error is set, the resulting string **must** be a child path of ++ `SecureJoin` and will not contain any symlink path components (they will all ++ be expanded). ++ ++* When expanding symlinks, all symlink path components **must** be resolved ++ relative to the provided root. In particular, this can be considered a ++ userspace implementation of how `chroot(2)` operates on file paths. Note that ++ these symlinks will **not** be expanded lexically (`filepath.Clean` is not ++ called on the input before processing). ++ ++* Non-existant path components are unaffected by `SecureJoin` (similar to ++ `filepath.EvalSymlinks`'s semantics). ++ ++* The returned path will always be `filepath.Clean`ed and thus not contain any ++ `..` components. ++ ++A (trivial) implementation of this function on GNU/Linux systems could be done ++with the following (note that this requires root privileges and is far more ++opaque than the implementation in this library, and also requires that ++`readlink` is inside the `root` path): ++ ++```go ++package securejoin ++ ++import ( ++ "os/exec" ++ "path/filepath" ++) ++ ++func SecureJoin(root, unsafePath string) (string, error) { ++ unsafePath = string(filepath.Separator) + unsafePath ++ cmd := exec.Command("chroot", root, ++ "readlink", "--canonicalize-missing", "--no-newline", unsafePath) ++ output, err := cmd.CombinedOutput() ++ if err != nil { ++ return "", err ++ } ++ expanded := string(output) ++ return filepath.Join(root, expanded), nil ++} ++``` ++ ++[go#20126]: https://github.com/golang/go/issues/20126 ++ ++### License ### ++ ++The license of this project is the same as Go, which is a BSD 3-clause license ++available in the `LICENSE` file. +diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go +new file mode 100644 +index 00000000000..c4ca3d71300 +--- /dev/null ++++ b/vendor/github.com/cyphar/filepath-securejoin/join.go +@@ -0,0 +1,134 @@ ++// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. ++// Copyright (C) 2017 SUSE LLC. All rights reserved. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// Package securejoin is an implementation of the hopefully-soon-to-be-included ++// SecureJoin helper that is meant to be part of the "path/filepath" package. ++// The purpose of this project is to provide a PoC implementation to make the ++// SecureJoin proposal (https://github.com/golang/go/issues/20126) more ++// tangible. ++package securejoin ++ ++import ( ++ "bytes" ++ "os" ++ "path/filepath" ++ "strings" ++ "syscall" ++ ++ "github.com/pkg/errors" ++) ++ ++// ErrSymlinkLoop is returned by SecureJoinVFS when too many symlinks have been ++// evaluated in attempting to securely join the two given paths. ++var ErrSymlinkLoop = errors.Wrap(syscall.ELOOP, "secure join") ++ ++// IsNotExist tells you if err is an error that implies that either the path ++// accessed does not exist (or path components don't exist). This is ++// effectively a more broad version of os.IsNotExist. ++func IsNotExist(err error) bool { ++ // If it's a bone-fide ENOENT just bail. ++ if os.IsNotExist(errors.Cause(err)) { ++ return true ++ } ++ ++ // Check that it's not actually an ENOTDIR, which in some cases is a more ++ // convoluted case of ENOENT (usually involving weird paths). ++ var errno error ++ switch err := errors.Cause(err).(type) { ++ case *os.PathError: ++ errno = err.Err ++ case *os.LinkError: ++ errno = err.Err ++ case *os.SyscallError: ++ errno = err.Err ++ } ++ return errno == syscall.ENOTDIR || errno == syscall.ENOENT ++} ++ ++// SecureJoinVFS joins the two given path components (similar to Join) except ++// that the returned path is guaranteed to be scoped inside the provided root ++// path (when evaluated). Any symbolic links in the path are evaluated with the ++// given root treated as the root of the filesystem, similar to a chroot. The ++// filesystem state is evaluated through the given VFS interface (if nil, the ++// standard os.* family of functions are used). ++// ++// Note that the guarantees provided by this function only apply if the path ++// components in the returned string are not modified (in other words are not ++// replaced with symlinks on the filesystem) after this function has returned. ++// Such a symlink race is necessarily out-of-scope of SecureJoin. ++func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { ++ // Use the os.* VFS implementation if none was specified. ++ if vfs == nil { ++ vfs = osVFS{} ++ } ++ ++ var path bytes.Buffer ++ n := 0 ++ for unsafePath != "" { ++ if n > 255 { ++ return "", ErrSymlinkLoop ++ } ++ ++ // Next path component, p. ++ i := strings.IndexRune(unsafePath, filepath.Separator) ++ var p string ++ if i == -1 { ++ p, unsafePath = unsafePath, "" ++ } else { ++ p, unsafePath = unsafePath[:i], unsafePath[i+1:] ++ } ++ ++ // Create a cleaned path, using the lexical semantics of /../a, to ++ // create a "scoped" path component which can safely be joined to fullP ++ // for evaluation. At this point, path.String() doesn't contain any ++ // symlink components. ++ cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p) ++ if cleanP == string(filepath.Separator) { ++ path.Reset() ++ continue ++ } ++ fullP := filepath.Clean(root + cleanP) ++ ++ // Figure out whether the path is a symlink. ++ fi, err := vfs.Lstat(fullP) ++ if err != nil && !IsNotExist(err) { ++ return "", err ++ } ++ // Treat non-existent path components the same as non-symlinks (we ++ // can't do any better here). ++ if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 { ++ path.WriteString(p) ++ path.WriteRune(filepath.Separator) ++ continue ++ } ++ ++ // Only increment when we actually dereference a link. ++ n++ ++ ++ // It's a symlink, expand it by prepending it to the yet-unparsed path. ++ dest, err := vfs.Readlink(fullP) ++ if err != nil { ++ return "", err ++ } ++ // Absolute symlinks reset any work we've already done. ++ if filepath.IsAbs(dest) { ++ path.Reset() ++ } ++ unsafePath = dest + string(filepath.Separator) + unsafePath ++ } ++ ++ // We have to clean path.String() here because it may contain '..' ++ // components that are entirely lexical, but would be misleading otherwise. ++ // And finally do a final clean to ensure that root is also lexically ++ // clean. ++ fullP := filepath.Clean(string(filepath.Separator) + path.String()) ++ return filepath.Clean(root + fullP), nil ++} ++ ++// SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library ++// of functions as the VFS. If in doubt, use this function over SecureJoinVFS. ++func SecureJoin(root, unsafePath string) (string, error) { ++ return SecureJoinVFS(root, unsafePath, nil) ++} +diff --git a/vendor/github.com/cyphar/filepath-securejoin/vendor.conf b/vendor/github.com/cyphar/filepath-securejoin/vendor.conf +new file mode 100644 +index 00000000000..66bb574b955 +--- /dev/null ++++ b/vendor/github.com/cyphar/filepath-securejoin/vendor.conf +@@ -0,0 +1 @@ ++github.com/pkg/errors v0.8.0 +diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go +new file mode 100644 +index 00000000000..a82a5eae11e +--- /dev/null ++++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go +@@ -0,0 +1,41 @@ ++// Copyright (C) 2017 SUSE LLC. All rights reserved. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++package securejoin ++ ++import "os" ++ ++// In future this should be moved into a separate package, because now there ++// are several projects (umoci and go-mtree) that are using this sort of ++// interface. ++ ++// VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is ++// equivalent to using the standard os.* family of functions. This is mainly ++// used for the purposes of mock testing, but also can be used to otherwise use ++// SecureJoin with VFS-like system. ++type VFS interface { ++ // Lstat returns a FileInfo describing the named file. If the file is a ++ // symbolic link, the returned FileInfo describes the symbolic link. Lstat ++ // makes no attempt to follow the link. These semantics are identical to ++ // os.Lstat. ++ Lstat(name string) (os.FileInfo, error) ++ ++ // Readlink returns the destination of the named symbolic link. These ++ // semantics are identical to os.Readlink. ++ Readlink(name string) (string, error) ++} ++ ++// osVFS is the "nil" VFS, in that it just passes everything through to the os ++// module. ++type osVFS struct{} ++ ++// Lstat returns a FileInfo describing the named file. If the file is a ++// symbolic link, the returned FileInfo describes the symbolic link. Lstat ++// makes no attempt to follow the link. These semantics are identical to ++// os.Lstat. ++func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } ++ ++// Readlink returns the destination of the named symbolic link. These ++// semantics are identical to os.Readlink. ++func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) } diff --git a/CVE-2021-20188-PRE2.patch b/CVE-2021-20188-PRE2.patch new file mode 100644 index 0000000..bdddeed --- /dev/null +++ b/CVE-2021-20188-PRE2.patch @@ -0,0 +1,388 @@ +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 +--- + libpod/container_api.go | 8 +- + libpod/container_internal.go | 56 ++++++----- + libpod/container_internal_linux.go | 44 ++------ + pkg/lookup/lookup.go | 156 +++++++++++++++++++++++++++++ + 4 files changed, 201 insertions(+), 63 deletions(-) + create mode 100644 pkg/lookup/lookup.go + +diff --git a/libpod/container_api.go b/libpod/container_api.go +index 41a131ea212..83f93cf9eb6 100644 +--- a/libpod/container_api.go ++++ b/libpod/container_api.go +@@ -10,8 +10,8 @@ import ( + "time" + + "github.com/containers/libpod/libpod/driver" +- "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/inspect" ++ "github.com/containers/libpod/pkg/lookup" + "github.com/containers/storage/pkg/stringid" + "github.com/docker/docker/daemon/caps" + "github.com/pkg/errors" +@@ -292,13 +292,13 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e + // the host + hostUser := "" + if user != "" { +- uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, user) ++ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) + if err != nil { +- return errors.Wrapf(err, "error getting user to launch exec session as") ++ return err + } + + // runc expects user formatted as uid:gid +- hostUser = fmt.Sprintf("%d:%d", uid, gid) ++ hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.Gid) + } + + // Generate exec session ID +diff --git a/libpod/container_internal.go b/libpod/container_internal.go +index 2af216358b4..d928c4aedd5 100644 +--- a/libpod/container_internal.go ++++ b/libpod/container_internal.go +@@ -12,9 +13,9 @@ import ( + "strings" + "syscall" + +- "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/hooks" + "github.com/containers/libpod/pkg/hooks/exec" ++ "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/resolvconf" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/secrets" +@@ -1094,6 +1095,7 @@ func (c *Container) generateHosts() (str + } + + func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error { ++ var uid, gid int + mountPoint := c.state.Mountpoint + if !c.state.Mounted { + return errors.Wrapf(ErrInternal, "container is not mounted") +@@ -1117,6 +1119,18 @@ func (c *Container) addLocalVolumes(ctx + } + } + ++ if c.config.User != "" { ++ if !c.state.Mounted { ++ return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) ++ } ++ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) ++ if err != nil { ++ return err ++ } ++ uid = execUser.Uid ++ gid = execUser.Gid ++ } ++ + for k := range imageData.ContainerConfig.Volumes { + mount := spec.Mount{ + Destination: k, +@@ -1129,27 +1143,13 @@ func (c *Container) addLocalVolumes(ctx + volumePath := filepath.Join(c.config.StaticDir, "volumes", k) + srcPath := filepath.Join(mountPoint, k) + +- var ( +- uid uint32 +- gid uint32 +- ) +- if c.config.User != "" { +- if !c.state.Mounted { +- return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) +- } +- uid, gid, err = chrootuser.GetUser(c.state.Mountpoint, c.config.User) +- if err != nil { +- return err +- } +- } +- + if _, err := os.Stat(srcPath); os.IsNotExist(err) { + logrus.Infof("Volume image mount point %s does not exist in root FS, need to create it", k) + if err = os.MkdirAll(srcPath, 0755); err != nil { + return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID) + } + +- if err = os.Chown(srcPath, int(uid), int(gid)); err != nil { ++ if err = os.Chown(srcPath, uid, gid); err != nil { + return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID) + } + } +@@ -1159,7 +1159,7 @@ func (c *Container) addLocalVolumes(ctx + return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID) + } + +- if err = os.Chown(volumePath, int(uid), int(gid)); err != nil { ++ if err = os.Chown(volumePath, uid, gid); err != nil { + return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID) + } + +diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go +index 0a1784ba740..7bf2c71cac6 100644 +--- a/libpod/container_internal_linux.go ++++ b/libpod/container_internal_linux.go +@@ -17,11 +17,9 @@ import ( + + cnitypes "github.com/containernetworking/cni/pkg/types/current" + crioAnnotations "github.com/containers/libpod/pkg/annotations" +- "github.com/containers/libpod/pkg/chrootuser" ++ "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/idtools" +- "github.com/cyphar/filepath-securejoin" +- "github.com/opencontainers/runc/libcontainer/user" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" +@@ -135,6 +133,10 @@ func (c *Container) cleanupNetwork() error { + // Generate spec for a container + // Accepts a map of the container's dependencies + func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { ++ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) ++ if err != nil { ++ return nil, err ++ } + g := generate.NewFromSpec(c.config.Spec) + + // If network namespace was requested, add it now +@@ -188,7 +190,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + } + } + +- var err error + if !rootless.IsRootless() { + if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { + return nil, errors.Wrapf(err, "error setting up OCI Hooks") +@@ -206,13 +207,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + if !c.state.Mounted { + return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) + } +- uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User) +- if err != nil { +- return nil, err +- } + // User and Group must go together +- g.SetProcessUID(uid) +- g.SetProcessGID(gid) ++ g.SetProcessUID(uint32(execUser.Uid)) ++ g.SetProcessGID(uint32(execUser.Gid)) + } + + // Add addition groups if c.config.GroupAdd is not empty +@@ -220,11 +217,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + if !c.state.Mounted { + return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to add additional groups", c.ID()) + } +- for _, group := range c.config.Groups { +- gid, err := chrootuser.GetGroup(c.state.Mountpoint, group) +- if err != nil { +- return nil, err +- } ++ gids, _ := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, nil) ++ for _, gid := range gids { + g.AddProcessAdditionalGid(gid) + } + } +@@ -237,26 +231,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + + // Look up and add groups the user belongs to, if a group wasn't directly specified + if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") { +- var groupDest, passwdDest string +- defaultExecUser := user.ExecUser{ +- Uid: 0, +- Gid: 0, +- Home: "/", +- } +- +- // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty +- if groupDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/group"); err != nil { +- logrus.Debug(err) +- return nil, err +- } +- if passwdDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd"); err != nil { +- logrus.Debug(err) +- return nil, err +- } +- execUser, err := user.GetExecUserPath(c.config.User, &defaultExecUser, passwdDest, groupDest) +- if err != nil { +- return nil, err +- } + for _, gid := range execUser.Sgids { + g.AddProcessAdditionalGid(uint32(gid)) + } +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-PRE3.patch b/CVE-2021-20188-PRE3.patch new file mode 100644 index 0000000..ed59904 --- /dev/null +++ b/CVE-2021-20188-PRE3.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.patch b/CVE-2021-20188.patch new file mode 100644 index 0000000..021fa59 --- /dev/null +++ b/CVE-2021-20188.patch @@ -0,0 +1,320 @@ +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" +@@ -11,9 +10,7 @@ import ( + + "github.com/containers/libpod/libpod/driver" + "github.com/containers/libpod/pkg/inspect" +- "github.com/containers/libpod/pkg/lookup" + "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 +257,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 +279,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 != "" { +- execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) +- if err != nil { +- return err +- } +- +- // runc expects user formatted as uid:gid +- hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.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/podman.spec b/podman.spec index 5ade502..21bd612 100644 --- a/podman.spec +++ b/podman.spec @@ -1,6 +1,6 @@ Name: podman Version: 0.10.1 -Release: 4 +Release: 8 Summary: A daemonless container engine for managing Containers Epoch: 1 License: ASL 2.0 @@ -108,7 +108,11 @@ Provides: bundled(golang(k8s.io/client-go)) = 7cd1d3291b7d9b1e2d54d4b69eb65995ea Provides: bundled(golang(k8s.io/kube-openapi)) = 275e2ce91dec4c05a4094a7b1daee5560b555ac9 Provides: bundled(golang(k8s.io/utils)) = 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e -Patch1: 0001-podman-patch-for-local-search.patch +Patch1: 0001-podman-patch-for-local-search.patch +Patch2: CVE-2021-20188-PRE1.patch +Patch3: CVE-2021-20188-PRE2.patch +Patch4: CVE-2021-20188-PRE3.patch +Patch5: CVE-2021-20188.patch %description Podman manages the entire container ecosystem which includes pods, @@ -211,8 +215,20 @@ install -Dp -m644 libpod.conf %{buildroot}%{_datadir}/containers/libpod.conf %{_mandir}/man5/*.5* %changelog -* Wed Jan 13 2021 Shengjing Wei - 1:0.10.1-4 -- Fix podman pull failed with issue I2BF99 +* Wed Mar 3 2021 wangxiao - 1:0.10.1-8 +- Fix CVE-2021-20188 + +* Thu Feb 18 2021 lingsheng - 1:0.10.1-7 +- Resolve go-md2man dependency + +* Mon Feb 8 2021 lingsheng - 1:0.10.1-6 +- Change BuildRequires to golang + +* Sat Jan 9 2021 Shengjing Wei - 1:0.10.1-5 +- Fixed podman pull failed with issue I2BF99 + +* Wed Sep 9 2020 Guoshuai Sun - 1:0.10.1-4 +- Add conflicts with docker-engine for help package * Thu Mar 12 2020 Ling Yang - 1:0.10.1-3 - Fixed install fail -- Gitee