cri-o: 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:46 -08:00 committed by Bruce Ashfield
parent fedb45ecea
commit f0c9dc00f2
2 changed files with 192 additions and 0 deletions

View File

@ -19,6 +19,7 @@ SRC_URI = "\
git://github.com/kubernetes-sigs/cri-o.git;branch=release-1.23;name=cri-o;protocol=https \
file://0001-Makefile-force-symlinks.patch \
file://crio.conf \
file://0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch;patchdir=src/import/vendor/github.com/containers/storage \
"
# Apache-2.0 for docker

View File

@ -0,0 +1,191 @@
From 03aff6270b389f27bc1edc4985dab753a38e7c7b 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>
Upstream-Status: Backport [0dc4fc9bb826e08b6e25af0af6a296ac172b5e15]
CVE: CVE-2024-9676
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 523c92dc8..c234414ef 100644
--- a/userns.go
+++ b/userns.go
@@ -1,18 +1,21 @@
+//go:build linux
+
package storage
import (
"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/pkg/errors"
"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