diff options
-rw-r--r-- | recipes-containers/buildah/buildah_git.bb | 1 | ||||
-rw-r--r-- | recipes-containers/buildah/files/0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch | 191 |
2 files changed, 192 insertions, 0 deletions
diff --git a/recipes-containers/buildah/buildah_git.bb b/recipes-containers/buildah/buildah_git.bb index 1c33291a..45d7b31d 100644 --- a/recipes-containers/buildah/buildah_git.bb +++ b/recipes-containers/buildah/buildah_git.bb | |||
@@ -27,6 +27,7 @@ GOBUILDFLAGS += "-mod vendor" | |||
27 | 27 | ||
28 | SRC_URI = " \ | 28 | SRC_URI = " \ |
29 | git://github.com/containers/buildah;branch=release-1.26;name=buildah;protocol=https \ | 29 | git://github.com/containers/buildah;branch=release-1.26;name=buildah;protocol=https \ |
30 | file://0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch;patchdir=src/github.com/containers/buildah/vendor/github.com/containers/storage \ | ||
30 | " | 31 | " |
31 | 32 | ||
32 | DEPENDS = "libdevmapper btrfs-tools gpgme" | 33 | DEPENDS = "libdevmapper btrfs-tools gpgme" |
diff --git a/recipes-containers/buildah/files/0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch b/recipes-containers/buildah/files/0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch new file mode 100644 index 00000000..d2e3dbbd --- /dev/null +++ b/recipes-containers/buildah/files/0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch | |||
@@ -0,0 +1,191 @@ | |||
1 | From b92233f24f22bc509e2f9fad2dc67f49e482f363 Mon Sep 17 00:00:00 2001 | ||
2 | From: Matt Heon <mheon@redhat.com> | ||
3 | Date: Wed, 9 Oct 2024 09:54:22 -0400 | ||
4 | Subject: [PATCH] Use securejoin.SecureJoin when forming userns paths | ||
5 | |||
6 | We need to read /etc/passwd and /etc/group in the container to | ||
7 | get an idea of how many UIDs and GIDs we need to allocate for a | ||
8 | user namespace when `--userns=auto` is specified. We were forming | ||
9 | paths for these using filepath.Join, which is not safe for paths | ||
10 | within a container, resulting in this CVE allowing crafted | ||
11 | symlinks in the container to access paths on the host instead. | ||
12 | |||
13 | Cherry-pick conflict fixed for v1.51 branch, and converted to use | ||
14 | the old securejoin API (securejoin.SecureJoin and then os.Open) | ||
15 | as this branch is too old to have the new API. | ||
16 | |||
17 | Addresses CVE-2024-9676 | ||
18 | |||
19 | Signed-off-by: Matt Heon <mheon@redhat.com> | ||
20 | |||
21 | CVE: CVE-2024-9676 | ||
22 | |||
23 | Upstream-Status: Backport [0dc4fc9bb826e08b6e25af0af6a296ac172b5e15] | ||
24 | |||
25 | Signed-off-by: Chen Qi <Qi.Chen@windriver.com> | ||
26 | --- | ||
27 | userns.go | 93 ++++++++++++++++++++++++++++++------------- | ||
28 | userns_unsupported.go | 14 +++++++ | ||
29 | 2 files changed, 80 insertions(+), 27 deletions(-) | ||
30 | create mode 100644 userns_unsupported.go | ||
31 | |||
32 | diff --git a/userns.go b/userns.go | ||
33 | index e0e530275..bea2a9520 100644 | ||
34 | --- a/userns.go | ||
35 | +++ b/userns.go | ||
36 | @@ -1,18 +1,21 @@ | ||
37 | +//go:build linux | ||
38 | + | ||
39 | package storage | ||
40 | |||
41 | import ( | ||
42 | "fmt" | ||
43 | "os" | ||
44 | "os/user" | ||
45 | - "path/filepath" | ||
46 | "strconv" | ||
47 | |||
48 | drivers "github.com/containers/storage/drivers" | ||
49 | "github.com/containers/storage/pkg/idtools" | ||
50 | "github.com/containers/storage/pkg/unshare" | ||
51 | "github.com/containers/storage/types" | ||
52 | + securejoin "github.com/cyphar/filepath-securejoin" | ||
53 | libcontainerUser "github.com/opencontainers/runc/libcontainer/user" | ||
54 | "github.com/sirupsen/logrus" | ||
55 | + "golang.org/x/sys/unix" | ||
56 | ) | ||
57 | |||
58 | // getAdditionalSubIDs looks up the additional IDs configured for | ||
59 | @@ -78,43 +81,63 @@ func (s *store) getAvailableIDs() (*idSet, *idSet, error) { | ||
60 | return u, g, nil | ||
61 | } | ||
62 | |||
63 | +const nobodyUser = 65534 | ||
64 | // parseMountedFiles returns the maximum UID and GID found in the /etc/passwd and | ||
65 | // /etc/group files. | ||
66 | func parseMountedFiles(containerMount, passwdFile, groupFile string) uint32 { | ||
67 | + var ( | ||
68 | + passwd *os.File | ||
69 | + group *os.File | ||
70 | + size int | ||
71 | + err error | ||
72 | + ) | ||
73 | if passwdFile == "" { | ||
74 | - passwdFile = filepath.Join(containerMount, "etc/passwd") | ||
75 | - } | ||
76 | - if groupFile == "" { | ||
77 | - groupFile = filepath.Join(groupFile, "etc/group") | ||
78 | + passwd, err = secureOpen(containerMount, "/etc/passwd") | ||
79 | + } else { | ||
80 | + // User-specified override from a volume. Will not be in | ||
81 | + // container root. | ||
82 | + passwd, err = os.Open(passwdFile) | ||
83 | } | ||
84 | - | ||
85 | - size := 0 | ||
86 | - | ||
87 | - users, err := libcontainerUser.ParsePasswdFile(passwdFile) | ||
88 | if err == nil { | ||
89 | - for _, u := range users { | ||
90 | - // Skip the "nobody" user otherwise we end up with 65536 | ||
91 | - // ids with most images | ||
92 | - if u.Name == "nobody" { | ||
93 | - continue | ||
94 | - } | ||
95 | - if u.Uid > size { | ||
96 | - size = u.Uid | ||
97 | - } | ||
98 | - if u.Gid > size { | ||
99 | - size = u.Gid | ||
100 | + defer passwd.Close() | ||
101 | + | ||
102 | + users, err := libcontainerUser.ParsePasswd(passwd) | ||
103 | + if err == nil { | ||
104 | + for _, u := range users { | ||
105 | + // Skip the "nobody" user otherwise we end up with 65536 | ||
106 | + // ids with most images | ||
107 | + if u.Name == "nobody" || u.Name == "nogroup" { | ||
108 | + continue | ||
109 | + } | ||
110 | + if u.Uid > size && u.Uid != nobodyUser { | ||
111 | + size = u.Uid + 1 | ||
112 | + } | ||
113 | + if u.Gid > size && u.Gid != nobodyUser { | ||
114 | + size = u.Gid + 1 | ||
115 | + } | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | - groups, err := libcontainerUser.ParseGroupFile(groupFile) | ||
121 | + if groupFile == "" { | ||
122 | + group, err = secureOpen(containerMount, "/etc/group") | ||
123 | + } else { | ||
124 | + // User-specified override from a volume. Will not be in | ||
125 | + // container root. | ||
126 | + group, err = os.Open(groupFile) | ||
127 | + } | ||
128 | if err == nil { | ||
129 | - for _, g := range groups { | ||
130 | - if g.Name == "nobody" { | ||
131 | - continue | ||
132 | - } | ||
133 | - if g.Gid > size { | ||
134 | - size = g.Gid | ||
135 | + defer group.Close() | ||
136 | + | ||
137 | + groups, err := libcontainerUser.ParseGroup(group) | ||
138 | + if err == nil { | ||
139 | + for _, g := range groups { | ||
140 | + if g.Name == "nobody" || g.Name == "nogroup" { | ||
141 | + continue | ||
142 | + } | ||
143 | + if g.Gid > size && g.Gid != nobodyUser { | ||
144 | + size = g.Gid + 1 | ||
145 | + } | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | @@ -300,3 +323,19 @@ func getAutoUserNSIDMappings( | ||
150 | gidMap := append(availableGIDs.zip(requestedContainerGIDs), additionalGIDMappings...) | ||
151 | return uidMap, gidMap, nil | ||
152 | } | ||
153 | + | ||
154 | +// Securely open (read-only) a file in a container mount. | ||
155 | +func secureOpen(containerMount, file string) (*os.File, error) { | ||
156 | + filePath, err := securejoin.SecureJoin(containerMount, file) | ||
157 | + if err != nil { | ||
158 | + return nil, err | ||
159 | + } | ||
160 | + | ||
161 | + flags := unix.O_PATH | unix.O_CLOEXEC | unix.O_RDONLY | ||
162 | + fileHandle, err := os.OpenFile(filePath, flags, 0) | ||
163 | + if err != nil { | ||
164 | + return nil, err | ||
165 | + } | ||
166 | + | ||
167 | + return fileHandle, nil | ||
168 | +} | ||
169 | diff --git a/userns_unsupported.go b/userns_unsupported.go | ||
170 | new file mode 100644 | ||
171 | index 000000000..e37c18fe4 | ||
172 | --- /dev/null | ||
173 | +++ b/userns_unsupported.go | ||
174 | @@ -0,0 +1,14 @@ | ||
175 | +//go:build !linux | ||
176 | + | ||
177 | +package storage | ||
178 | + | ||
179 | +import ( | ||
180 | + "errors" | ||
181 | + | ||
182 | + "github.com/containers/storage/pkg/idtools" | ||
183 | + "github.com/containers/storage/types" | ||
184 | +) | ||
185 | + | ||
186 | +func (s *store) getAutoUserNS(_ *types.AutoUserNsOptions, _ *Image, _ rwLayerStore, _ []roLayerStore) ([]idtools.IDMap, []idtools.IDMap, error) { | ||
187 | + return nil, nil, errors.New("user namespaces are not supported on this platform") | ||
188 | +} | ||
189 | -- | ||
190 | 2.25.1 | ||
191 | |||