From 037ff315e608fe6da80a570b2d7e11444c3d28b8 Mon Sep 17 00:00:00 2001
From: Matt Heon <mheon@redhat.com>
Date: Tue, 19 May 2026 18:00:36 +0800
Subject: [PATCH] Use securejoin.SecureJoin when forming userns paths

---
 .../github.com/containers/storage/userns.go   | 92 +++++++++++++------
 .../containers/storage/userns_unsupported.go  | 14 +++
 2 files changed, 79 insertions(+), 27 deletions(-)
 create mode 100644 vendor/github.com/containers/storage/userns_unsupported.go

diff --git a/vendor/github.com/containers/storage/userns.go b/vendor/github.com/containers/storage/userns.go
index 32ae830..447c455 100644
--- a/vendor/github.com/containers/storage/userns.go
+++ b/vendor/github.com/containers/storage/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
@@ -85,40 +88,59 @@ 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 && u.Uid != nobodyUser {
-				size = u.Uid
-			}
-			if u.Gid > size && u.Gid != nobodyUser {
-				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 && g.Gid != nobodyUser {
-				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
+				}
 			}
 		}
 	}
@@ -309,3 +331,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
+}
\ No newline at end of file
diff --git a/vendor/github.com/containers/storage/userns_unsupported.go b/vendor/github.com/containers/storage/userns_unsupported.go
new file mode 100644
index 0000000..7e18f34
--- /dev/null
+++ b/vendor/github.com/containers/storage/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")
+}
\ No newline at end of file
-- 
2.43.0