buildah: fix CVE-2024-9676

Backport patch to fix CVE-2024-9676.

Signed-off-by: Chen Qi <Qi.Chen@windriver.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
This commit is contained in:
Chen Qi 2025-01-22 00:28:45 -08:00 committed by Bruce Ashfield
parent 80dc21e4c1
commit fedb45ecea
2 changed files with 192 additions and 0 deletions

View File

@ -27,6 +27,7 @@ GOBUILDFLAGS += "-mod vendor"
SRC_URI = " \
git://github.com/containers/buildah;branch=release-1.26;name=buildah;protocol=https \
file://0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch;patchdir=src/github.com/containers/buildah/vendor/github.com/containers/storage \
"
DEPENDS = "libdevmapper btrfs-tools gpgme"

View File

@ -0,0 +1,191 @@
From b92233f24f22bc509e2f9fad2dc67f49e482f363 Mon Sep 17 00:00:00 2001
From: Matt Heon <mheon@redhat.com>
Date: Wed, 9 Oct 2024 09:54:22 -0400
Subject: [PATCH] Use securejoin.SecureJoin when forming userns paths
We need to read /etc/passwd and /etc/group in the container to
get an idea of how many UIDs and GIDs we need to allocate for a
user namespace when `--userns=auto` is specified. We were forming
paths for these using filepath.Join, which is not safe for paths
within a container, resulting in this CVE allowing crafted
symlinks in the container to access paths on the host instead.
Cherry-pick conflict fixed for v1.51 branch, and converted to use
the old securejoin API (securejoin.SecureJoin and then os.Open)
as this branch is too old to have the new API.
Addresses CVE-2024-9676
Signed-off-by: Matt Heon <mheon@redhat.com>
CVE: CVE-2024-9676
Upstream-Status: Backport [0dc4fc9bb826e08b6e25af0af6a296ac172b5e15]
Signed-off-by: Chen Qi <Qi.Chen@windriver.com>
---
userns.go | 93 ++++++++++++++++++++++++++++++-------------
userns_unsupported.go | 14 +++++++
2 files changed, 80 insertions(+), 27 deletions(-)
create mode 100644 userns_unsupported.go
diff --git a/userns.go b/userns.go
index e0e530275..bea2a9520 100644
--- a/userns.go
+++ b/userns.go
@@ -1,18 +1,21 @@
+//go:build linux
+
package storage
import (
"fmt"
"os"
"os/user"
- "path/filepath"
"strconv"
drivers "github.com/containers/storage/drivers"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/unshare"
"github.com/containers/storage/types"
+ securejoin "github.com/cyphar/filepath-securejoin"
libcontainerUser "github.com/opencontainers/runc/libcontainer/user"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
// getAdditionalSubIDs looks up the additional IDs configured for
@@ -78,43 +81,63 @@ func (s *store) getAvailableIDs() (*idSet, *idSet, error) {
return u, g, nil
}
+const nobodyUser = 65534
// parseMountedFiles returns the maximum UID and GID found in the /etc/passwd and
// /etc/group files.
func parseMountedFiles(containerMount, passwdFile, groupFile string) uint32 {
+ var (
+ passwd *os.File
+ group *os.File
+ size int
+ err error
+ )
if passwdFile == "" {
- passwdFile = filepath.Join(containerMount, "etc/passwd")
- }
- if groupFile == "" {
- groupFile = filepath.Join(groupFile, "etc/group")
+ passwd, err = secureOpen(containerMount, "/etc/passwd")
+ } else {
+ // User-specified override from a volume. Will not be in
+ // container root.
+ passwd, err = os.Open(passwdFile)
}
-
- size := 0
-
- users, err := libcontainerUser.ParsePasswdFile(passwdFile)
if err == nil {
- for _, u := range users {
- // Skip the "nobody" user otherwise we end up with 65536
- // ids with most images
- if u.Name == "nobody" {
- continue
- }
- if u.Uid > size {
- size = u.Uid
- }
- if u.Gid > size {
- size = u.Gid
+ defer passwd.Close()
+
+ users, err := libcontainerUser.ParsePasswd(passwd)
+ if err == nil {
+ for _, u := range users {
+ // Skip the "nobody" user otherwise we end up with 65536
+ // ids with most images
+ if u.Name == "nobody" || u.Name == "nogroup" {
+ continue
+ }
+ if u.Uid > size && u.Uid != nobodyUser {
+ size = u.Uid + 1
+ }
+ if u.Gid > size && u.Gid != nobodyUser {
+ size = u.Gid + 1
+ }
}
}
}
- groups, err := libcontainerUser.ParseGroupFile(groupFile)
+ if groupFile == "" {
+ group, err = secureOpen(containerMount, "/etc/group")
+ } else {
+ // User-specified override from a volume. Will not be in
+ // container root.
+ group, err = os.Open(groupFile)
+ }
if err == nil {
- for _, g := range groups {
- if g.Name == "nobody" {
- continue
- }
- if g.Gid > size {
- size = g.Gid
+ defer group.Close()
+
+ groups, err := libcontainerUser.ParseGroup(group)
+ if err == nil {
+ for _, g := range groups {
+ if g.Name == "nobody" || g.Name == "nogroup" {
+ continue
+ }
+ if g.Gid > size && g.Gid != nobodyUser {
+ size = g.Gid + 1
+ }
}
}
}
@@ -300,3 +323,19 @@ func getAutoUserNSIDMappings(
gidMap := append(availableGIDs.zip(requestedContainerGIDs), additionalGIDMappings...)
return uidMap, gidMap, nil
}
+
+// Securely open (read-only) a file in a container mount.
+func secureOpen(containerMount, file string) (*os.File, error) {
+ filePath, err := securejoin.SecureJoin(containerMount, file)
+ if err != nil {
+ return nil, err
+ }
+
+ flags := unix.O_PATH | unix.O_CLOEXEC | unix.O_RDONLY
+ fileHandle, err := os.OpenFile(filePath, flags, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ return fileHandle, nil
+}
diff --git a/userns_unsupported.go b/userns_unsupported.go
new file mode 100644
index 000000000..e37c18fe4
--- /dev/null
+++ b/userns_unsupported.go
@@ -0,0 +1,14 @@
+//go:build !linux
+
+package storage
+
+import (
+ "errors"
+
+ "github.com/containers/storage/pkg/idtools"
+ "github.com/containers/storage/types"
+)
+
+func (s *store) getAutoUserNS(_ *types.AutoUserNsOptions, _ *Image, _ rwLayerStore, _ []roLayerStore) ([]idtools.IDMap, []idtools.IDMap, error) {
+ return nil, nil, errors.New("user namespaces are not supported on this platform")
+}
--
2.25.1