diff options
author | Hitendra Prajapati <hprajapati@mvista.com> | 2025-07-24 10:02:52 +0530 |
---|---|---|
committer | Steve Sakoman <steve@sakoman.com> | 2025-07-30 07:47:48 -0700 |
commit | a485d82c25b97f4b7b9f656d60849136fbbde40a (patch) | |
tree | da8fd17427835b957a9053729d23afba9d708dbd | |
parent | 875170d8f8e33fd19abd6f492d6449a608a6aea4 (diff) | |
download | poky-a485d82c25b97f4b7b9f656d60849136fbbde40a.tar.gz |
libpam: fix CVE-2025-6020
Upstream-Status: Backport from https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e && https://github.com/linux-pam/linux-pam/commit/592d84e1265d04c3104acee815a503856db503a1 && https://github.com/linux-pam/linux-pam/commit/976c20079358d133514568fc7fd95c02df8b5773
(From OE-Core rev: 4ff5111d2a758bacb803de981177799a8ac7fd0b)
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
6 files changed, 1959 insertions, 0 deletions
diff --git a/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch b/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch new file mode 100644 index 0000000000..48e8b255f2 --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch | |||
@@ -0,0 +1,102 @@ | |||
1 | From 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc Mon Sep 17 00:00:00 2001 | ||
2 | From: "Dmitry V. Levin" <ldv@strace.io> | ||
3 | Date: Tue, 18 Feb 2025 08:00:00 +0000 | ||
4 | Subject: [PATCH] pam_inline: introduce pam_asprintf(), pam_snprintf(), and | ||
5 | pam_sprintf() | ||
6 | |||
7 | pam_asprintf() is essentially asprintf() with the following semantic | ||
8 | difference: it returns the string itself instead of its length. | ||
9 | |||
10 | pam_snprintf() is essentially snprintf() with the following semantic | ||
11 | difference: it returns -1 in case of truncation. | ||
12 | |||
13 | pam_sprintf() is essentially snprintf() but with a check that the buffer | ||
14 | is an array, and with an automatically calculated buffer size. | ||
15 | |||
16 | Use of these helpers would make error checking simpler. | ||
17 | |||
18 | (cherry picked from commit 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc) | ||
19 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
20 | |||
21 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc] | ||
22 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
23 | --- | ||
24 | libpam/include/pam_cc_compat.h | 6 ++++++ | ||
25 | libpam/include/pam_inline.h | 37 ++++++++++++++++++++++++++++++++++ | ||
26 | 2 files changed, 43 insertions(+) | ||
27 | |||
28 | diff --git a/libpam/include/pam_cc_compat.h b/libpam/include/pam_cc_compat.h | ||
29 | index 6919036..45c74b5 100644 | ||
30 | --- a/libpam/include/pam_cc_compat.h | ||
31 | +++ b/libpam/include/pam_cc_compat.h | ||
32 | @@ -21,6 +21,12 @@ | ||
33 | # define PAM_ATTRIBUTE_ALIGNED(arg) /* empty */ | ||
34 | #endif | ||
35 | |||
36 | +#if PAM_GNUC_PREREQ(3, 0) | ||
37 | +# define PAM_ATTRIBUTE_MALLOC __attribute__((__malloc__)) | ||
38 | +#else | ||
39 | +# define PAM_ATTRIBUTE_MALLOC /* empty */ | ||
40 | +#endif | ||
41 | + | ||
42 | #if PAM_GNUC_PREREQ(4, 6) | ||
43 | # define DIAG_PUSH_IGNORE_CAST_QUAL \ | ||
44 | _Pragma("GCC diagnostic push"); \ | ||
45 | diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h | ||
46 | index ec2f3bf..666a028 100644 | ||
47 | --- a/libpam/include/pam_inline.h | ||
48 | +++ b/libpam/include/pam_inline.h | ||
49 | @@ -9,6 +9,9 @@ | ||
50 | #define PAM_INLINE_H | ||
51 | |||
52 | #include "pam_cc_compat.h" | ||
53 | +#include <stdarg.h> | ||
54 | +#include <stdio.h> | ||
55 | +#include <stdlib.h> | ||
56 | #include <string.h> | ||
57 | #include <unistd.h> | ||
58 | #include <errno.h> | ||
59 | @@ -66,6 +69,40 @@ pam_str_skip_icase_prefix_len(const char *str, const char *prefix, size_t prefix | ||
60 | #define pam_str_skip_icase_prefix(str_, prefix_) \ | ||
61 | pam_str_skip_icase_prefix_len((str_), (prefix_), sizeof(prefix_) - 1 + PAM_MUST_BE_ARRAY(prefix_)) | ||
62 | |||
63 | +static inline char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC | ||
64 | +pam_asprintf(const char *fmt, ...) | ||
65 | +{ | ||
66 | + int rc; | ||
67 | + char *res; | ||
68 | + va_list ap; | ||
69 | + | ||
70 | + va_start(ap, fmt); | ||
71 | + rc = vasprintf(&res, fmt, ap); | ||
72 | + va_end(ap); | ||
73 | + | ||
74 | + return rc < 0 ? NULL : res; | ||
75 | +} | ||
76 | + | ||
77 | +static inline int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3)) | ||
78 | +pam_snprintf(char *str, size_t size, const char *fmt, ...) | ||
79 | +{ | ||
80 | + int rc; | ||
81 | + va_list ap; | ||
82 | + | ||
83 | + va_start(ap, fmt); | ||
84 | + rc = vsnprintf(str, size, fmt, ap); | ||
85 | + va_end(ap); | ||
86 | + | ||
87 | + if (rc < 0 || (unsigned int) rc >= size) | ||
88 | + return -1; | ||
89 | + return rc; | ||
90 | +} | ||
91 | + | ||
92 | +#define pam_sprintf(str_, fmt_, ...) \ | ||
93 | + pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \ | ||
94 | + ##__VA_ARGS__) | ||
95 | + | ||
96 | + | ||
97 | static inline int | ||
98 | pam_read_passwords(int fd, int npass, char **passwords) | ||
99 | { | ||
100 | -- | ||
101 | 2.50.1 | ||
102 | |||
diff --git a/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch b/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch new file mode 100644 index 0000000000..124e5f1c3c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch | |||
@@ -0,0 +1,42 @@ | |||
1 | From cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13 Mon Sep 17 00:00:00 2001 | ||
2 | From: Jacob Heider <jacob@pkgx.dev> | ||
3 | Date: Wed, 17 Jan 2024 11:49:26 -0500 | ||
4 | Subject: [PATCH] pam_namespace: include stdint.h | ||
5 | |||
6 | pam_namespace.c makes use of SIZE_MAX but doesn't include stdint.h, | ||
7 | resulting in the following build failures on 1.6.0: | ||
8 | |||
9 | pam_namespace.c: In function 'process_line': | ||
10 | pam_namespace.c:649:41: error: 'SIZE_MAX' undeclared (first use in this function) | ||
11 | 649 | if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
12 | | ^~~~~~~~ | ||
13 | pam_namespace.c:41:1: note: 'SIZE_MAX' is defined in header '<stdint.h>'; did you forget to '#include <stdint.h>'? | ||
14 | 40 | #include "argv_parse.h" | ||
15 | +++ |+#include <stdint.h> | ||
16 | 41 | | ||
17 | pam_namespace.c:649:41: note: each undeclared identifier is reported only once for each function it appears in | ||
18 | 649 | if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
19 | | ^~~~~~~~ | ||
20 | |||
21 | Fixes: v1.6.0~100 ("pam_namespace: validate amount of uids in config") | ||
22 | Resolves: https://github.com/linux-pam/linux-pam/issues/733 | ||
23 | |||
24 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13] | ||
25 | Signed-off-by: Khem Raj <raj.khem@gmail.com> | ||
26 | --- | ||
27 | modules/pam_namespace/pam_namespace.c | 2 ++ | ||
28 | 1 file changed, 2 insertions(+) | ||
29 | |||
30 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
31 | index f72d67189..b16731c22 100644 | ||
32 | --- a/modules/pam_namespace/pam_namespace.c | ||
33 | +++ b/modules/pam_namespace/pam_namespace.c | ||
34 | @@ -34,6 +34,8 @@ | ||
35 | |||
36 | #define _ATFILE_SOURCE | ||
37 | |||
38 | +#include "config.h" | ||
39 | +#include <stdint.h> | ||
40 | #include "pam_cc_compat.h" | ||
41 | #include "pam_inline.h" | ||
42 | #include "pam_namespace.h" | ||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch new file mode 100644 index 0000000000..4f5f780f9c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch | |||
@@ -0,0 +1,1588 @@ | |||
1 | From 475bd60c552b98c7eddb3270b0b4196847c0072e Mon Sep 17 00:00:00 2001 | ||
2 | From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
3 | Date: Tue, 4 Mar 2025 14:37:02 +0100 | ||
4 | Subject: [PATCH] pam_namespace: fix potential privilege escalation | ||
5 | |||
6 | Existing protection provided by protect_dir() and protect_mount() were | ||
7 | bind mounting on themselves all directories part of the to-be-secured | ||
8 | paths. However, this works *only* against attacks executed by processes | ||
9 | in the same mount namespace as the one the mountpoint was created in. | ||
10 | Therefore, a user with an out-of-mount-namespace access, or multiple | ||
11 | users colluding, could exploit multiple race conditions, and, for | ||
12 | instance, elevate their privileges to root. | ||
13 | |||
14 | This commit keeps the existing protection as a defense in depth | ||
15 | measure, and to keep the existing behavior of the module. However, | ||
16 | it converts all the needed function calls to operate on file | ||
17 | descriptors instead of absolute paths to protect against race | ||
18 | conditions globally. | ||
19 | |||
20 | Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
21 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
22 | |||
23 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e] | ||
24 | CVE: CVE-2025-6020 | ||
25 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
26 | --- | ||
27 | modules/pam_namespace/pam_namespace.c | 999 ++++++++++++++++---------- | ||
28 | modules/pam_namespace/pam_namespace.h | 17 +- | ||
29 | 2 files changed, 647 insertions(+), 369 deletions(-) | ||
30 | |||
31 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
32 | index 2a5082b..22d8445 100644 | ||
33 | --- a/modules/pam_namespace/pam_namespace.c | ||
34 | +++ b/modules/pam_namespace/pam_namespace.c | ||
35 | @@ -41,6 +41,300 @@ | ||
36 | #include "pam_namespace.h" | ||
37 | #include "argv_parse.h" | ||
38 | |||
39 | +#define MAGIC_LNK_FD_SIZE 64 | ||
40 | + | ||
41 | +/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */ | ||
42 | +static const char *base_name(const char *path) | ||
43 | +{ | ||
44 | + const char *base = strrchr(path, '/'); | ||
45 | + return base ? base+1 : path; | ||
46 | +} | ||
47 | + | ||
48 | +static int | ||
49 | +compare_filename(const void *a, const void *b) | ||
50 | +{ | ||
51 | + return strcmp(base_name(* (char * const *) a), | ||
52 | + base_name(* (char * const *) b)); | ||
53 | +} | ||
54 | + | ||
55 | +static void close_fds_pre_exec(struct instance_data *idata) | ||
56 | +{ | ||
57 | + if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD, | ||
58 | + PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) { | ||
59 | + _exit(1); | ||
60 | + } | ||
61 | +} | ||
62 | + | ||
63 | +static void | ||
64 | +strip_trailing_slashes(char *str) | ||
65 | +{ | ||
66 | + char *p = str + strlen(str); | ||
67 | + | ||
68 | + while (--p > str && *p == '/') | ||
69 | + *p = '\0'; | ||
70 | +} | ||
71 | + | ||
72 | +static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
73 | +{ | ||
74 | + struct protect_dir_s *dir = idata->protect_dirs; | ||
75 | + char tmpbuf[MAGIC_LNK_FD_SIZE]; | ||
76 | + | ||
77 | + while (dir != NULL) { | ||
78 | + if (strcmp(path, dir->dir) == 0) { | ||
79 | + return 0; | ||
80 | + } | ||
81 | + dir = dir->next; | ||
82 | + } | ||
83 | + | ||
84 | + if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0) | ||
85 | + return -1; | ||
86 | + | ||
87 | + dir = calloc(1, sizeof(*dir)); | ||
88 | + | ||
89 | + if (dir == NULL) { | ||
90 | + return -1; | ||
91 | + } | ||
92 | + | ||
93 | + dir->dir = strdup(path); | ||
94 | + | ||
95 | + if (dir->dir == NULL) { | ||
96 | + free(dir); | ||
97 | + return -1; | ||
98 | + } | ||
99 | + | ||
100 | + if (idata->flags & PAMNS_DEBUG) { | ||
101 | + pam_syslog(idata->pamh, LOG_INFO, | ||
102 | + "Protect mount of %s over itself", path); | ||
103 | + } | ||
104 | + | ||
105 | + if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
106 | + int save_errno = errno; | ||
107 | + pam_syslog(idata->pamh, LOG_ERR, | ||
108 | + "Protect mount of %s failed: %m", tmpbuf); | ||
109 | + free(dir->dir); | ||
110 | + free(dir); | ||
111 | + errno = save_errno; | ||
112 | + return -1; | ||
113 | + } | ||
114 | + | ||
115 | + dir->next = idata->protect_dirs; | ||
116 | + idata->protect_dirs = dir; | ||
117 | + | ||
118 | + return 0; | ||
119 | +} | ||
120 | + | ||
121 | +/* | ||
122 | + * Returns a fd to the given absolute path, acquired securely. This means: | ||
123 | + * - iterating on each segment of the path, | ||
124 | + * - not following user symlinks, | ||
125 | + * - using race-free operations. | ||
126 | + * | ||
127 | + * Takes a bit mask to specify the operation mode: | ||
128 | + * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path | ||
129 | + * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist | ||
130 | + * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH, | ||
131 | + * allowing more operations to be done with the returned fd | ||
132 | + * | ||
133 | + * Be aware that using SECURE_OPENDIR_PROTECT: | ||
134 | + * - will modify some external state (global structure...) and should not be | ||
135 | + * called in cleanup code paths. See wrapper secure_opendir_stateless() | ||
136 | + * - need a non-NULL idata to call protect_mount() | ||
137 | + */ | ||
138 | +static int secure_opendir(const char *path, int opm, mode_t mode, | ||
139 | + struct instance_data *idata) | ||
140 | +{ | ||
141 | + char *p; | ||
142 | + char *d; | ||
143 | + char *dir; | ||
144 | + int dfd = -1; | ||
145 | + int dfd_next; | ||
146 | + int save_errno; | ||
147 | + int flags = O_DIRECTORY | O_CLOEXEC; | ||
148 | + int rv = -1; | ||
149 | + struct stat st; | ||
150 | + | ||
151 | + if (opm & SECURE_OPENDIR_FULL_FD) | ||
152 | + flags |= O_RDONLY; | ||
153 | + else | ||
154 | + flags |= O_PATH; | ||
155 | + | ||
156 | + /* Check for args consistency */ | ||
157 | + if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL) | ||
158 | + return -1; | ||
159 | + | ||
160 | + /* Accept only absolute paths */ | ||
161 | + if (*path != '/') | ||
162 | + return -1; | ||
163 | + | ||
164 | + dir = p = strdup(path); | ||
165 | + if (p == NULL) | ||
166 | + return -1; | ||
167 | + | ||
168 | + /* Assume '/' is safe */ | ||
169 | + dfd = open("/", flags); | ||
170 | + if (dfd == -1) | ||
171 | + goto error; | ||
172 | + | ||
173 | + /* Needed to not loop too far and call openat() on NULL */ | ||
174 | + strip_trailing_slashes(p); | ||
175 | + | ||
176 | + dir++; | ||
177 | + | ||
178 | + /* In case path is '/' */ | ||
179 | + if (*dir == '\0') { | ||
180 | + free(p); | ||
181 | + return dfd; | ||
182 | + } | ||
183 | + | ||
184 | + while ((d=strchr(dir, '/')) != NULL) { | ||
185 | + *d = '\0'; | ||
186 | + | ||
187 | + dfd_next = openat(dfd, dir, flags); | ||
188 | + if (dfd_next == -1) | ||
189 | + goto error; | ||
190 | + | ||
191 | + if (fstat(dfd_next, &st) != 0) { | ||
192 | + close(dfd_next); | ||
193 | + goto error; | ||
194 | + } | ||
195 | + | ||
196 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
197 | + /* we are inside user-owned dir - protect */ | ||
198 | + if (protect_mount(dfd_next, p, idata) == -1) { | ||
199 | + close(dfd_next); | ||
200 | + goto error; | ||
201 | + } | ||
202 | + /* | ||
203 | + * Reopen the directory to obtain a new descriptor | ||
204 | + * after protect_mount(), this is necessary in cases | ||
205 | + * when another directory is going to be mounted over | ||
206 | + * the given path. | ||
207 | + */ | ||
208 | + close(dfd_next); | ||
209 | + dfd_next = openat(dfd, dir, flags); | ||
210 | + if (dfd_next == -1) | ||
211 | + goto error; | ||
212 | + } else if (st.st_uid != 0 | ||
213 | + || (st.st_gid != 0 && (st.st_mode & S_IWGRP)) | ||
214 | + || (st.st_mode & S_IWOTH)) { | ||
215 | + /* do not follow symlinks on subdirectories */ | ||
216 | + flags |= O_NOFOLLOW; | ||
217 | + } | ||
218 | + | ||
219 | + close(dfd); | ||
220 | + dfd = dfd_next; | ||
221 | + | ||
222 | + *d = '/'; | ||
223 | + dir = d + 1; | ||
224 | + } | ||
225 | + | ||
226 | + rv = openat(dfd, dir, flags); | ||
227 | + | ||
228 | + if (rv == -1) { | ||
229 | + if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0) | ||
230 | + rv = openat(dfd, dir, flags); | ||
231 | + | ||
232 | + if (rv == -1) | ||
233 | + goto error; | ||
234 | + } | ||
235 | + | ||
236 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
237 | + /* we are inside user-owned dir - protect */ | ||
238 | + if (protect_mount(rv, p, idata) == -1) { | ||
239 | + save_errno = errno; | ||
240 | + close(rv); | ||
241 | + rv = -1; | ||
242 | + errno = save_errno; | ||
243 | + } | ||
244 | + /* | ||
245 | + * Reopen the directory to obtain a new descriptor after | ||
246 | + * protect_mount(), this is necessary in cases when another | ||
247 | + * directory is going to be mounted over the given path. | ||
248 | + */ | ||
249 | + close(rv); | ||
250 | + rv = openat(dfd, dir, flags); | ||
251 | + } | ||
252 | + | ||
253 | +error: | ||
254 | + save_errno = errno; | ||
255 | + free(p); | ||
256 | + if (dfd >= 0) | ||
257 | + close(dfd); | ||
258 | + errno = save_errno; | ||
259 | + | ||
260 | + return rv; | ||
261 | +} | ||
262 | + | ||
263 | +/* | ||
264 | + * Returns a fd to the given path, acquired securely. | ||
265 | + * It can be called in all situations, including in cleanup code paths, as | ||
266 | + * it does not modify external state (no access to global structures...). | ||
267 | + */ | ||
268 | +static int secure_opendir_stateless(const char *path) | ||
269 | +{ | ||
270 | + return secure_opendir(path, 0, 0, NULL); | ||
271 | +} | ||
272 | + | ||
273 | +/* | ||
274 | + * Umount securely the given path, even if the directories along | ||
275 | + * the path are under user control. It should protect against | ||
276 | + * symlinks attacks and race conditions. | ||
277 | + */ | ||
278 | +static int secure_umount(const char *path) | ||
279 | +{ | ||
280 | + int save_errno; | ||
281 | + int rv = -1; | ||
282 | + int dfd = -1; | ||
283 | + char s_path[MAGIC_LNK_FD_SIZE]; | ||
284 | + | ||
285 | + dfd = secure_opendir_stateless(path); | ||
286 | + if (dfd == -1) | ||
287 | + return rv; | ||
288 | + | ||
289 | + if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0) | ||
290 | + goto error; | ||
291 | + | ||
292 | + /* | ||
293 | + * We still have a fd open to path itself, | ||
294 | + * so we need to do a lazy umount. | ||
295 | + */ | ||
296 | + rv = umount2(s_path, MNT_DETACH); | ||
297 | + | ||
298 | +error: | ||
299 | + save_errno = errno; | ||
300 | + close(dfd); | ||
301 | + errno = save_errno; | ||
302 | + return rv; | ||
303 | +} | ||
304 | + | ||
305 | +/* | ||
306 | + * Rmdir the given path securely, protecting against symlinks attacks | ||
307 | + * and race conditions. | ||
308 | + * This function is currently called only in cleanup code paths where | ||
309 | + * any errors returned are not handled, so do not handle them either. | ||
310 | + * Basically, try to rmdir the path on a best-effort basis. | ||
311 | + */ | ||
312 | +static void secure_try_rmdir(const char *path) | ||
313 | +{ | ||
314 | + int dfd; | ||
315 | + char *buf; | ||
316 | + char *parent; | ||
317 | + | ||
318 | + buf = strdup(path); | ||
319 | + if (buf == NULL) | ||
320 | + return; | ||
321 | + | ||
322 | + parent = dirname(buf); | ||
323 | + | ||
324 | + dfd = secure_opendir_stateless(parent); | ||
325 | + if (dfd >= 0) { | ||
326 | + unlinkat(dfd, base_name(path), AT_REMOVEDIR); | ||
327 | + close(dfd); | ||
328 | + } | ||
329 | + | ||
330 | + free(buf); | ||
331 | +} | ||
332 | + | ||
333 | /* | ||
334 | * Adds an entry for a polyinstantiated directory to the linked list of | ||
335 | * polyinstantiated directories. It is called from process_line() while | ||
336 | @@ -73,6 +367,7 @@ static void del_polydir(struct polydir_s *poly) | ||
337 | } | ||
338 | } | ||
339 | |||
340 | + | ||
341 | /* | ||
342 | * Deletes all the entries in the linked list. | ||
343 | */ | ||
344 | @@ -92,7 +387,7 @@ static void unprotect_dirs(struct protect_dir_s *dir) | ||
345 | struct protect_dir_s *next; | ||
346 | |||
347 | while (dir != NULL) { | ||
348 | - umount(dir->dir); | ||
349 | + secure_umount(dir->dir); | ||
350 | free(dir->dir); | ||
351 | next = dir->next; | ||
352 | free(dir); | ||
353 | @@ -110,7 +405,7 @@ static void cleanup_protect_data(pam_handle_t *pamh UNUSED , void *data, int err | ||
354 | unprotect_dirs(data); | ||
355 | } | ||
356 | |||
357 | -static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[]) | ||
358 | +static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[]) | ||
359 | { | ||
360 | const char *src = orig; | ||
361 | char *dst; | ||
362 | @@ -121,7 +416,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
363 | if (*src == '$') { | ||
364 | int i; | ||
365 | for (i = 0; var_names[i]; i++) { | ||
366 | - int namelen = strlen(var_names[i]); | ||
367 | + size_t namelen = strlen(var_names[i]); | ||
368 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
369 | dstlen += strlen(var_values[i]) - 1; /* $ */ | ||
370 | src += namelen; | ||
371 | @@ -139,7 +434,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
372 | if (c == '$') { | ||
373 | int i; | ||
374 | for (i = 0; var_names[i]; i++) { | ||
375 | - int namelen = strlen(var_names[i]); | ||
376 | + size_t namelen = strlen(var_names[i]); | ||
377 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
378 | dst = stpcpy(dst, var_values[i]); | ||
379 | --dst; | ||
380 | @@ -223,8 +518,7 @@ static int parse_iscript_params(char *params, struct polydir_s *poly) | ||
381 | |||
382 | if (*params != '\0') { | ||
383 | if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */ | ||
384 | - if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1) | ||
385 | - return -1; | ||
386 | + poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params); | ||
387 | } else { | ||
388 | poly->init_script = strdup(params); | ||
389 | } | ||
390 | @@ -306,9 +600,9 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
391 | { | ||
392 | enum polymethod pm; | ||
393 | char *sptr = NULL; | ||
394 | - static const char *method_names[] = { "user", "context", "level", "tmpdir", | ||
395 | + static const char *const method_names[] = { "user", "context", "level", "tmpdir", | ||
396 | "tmpfs", NULL }; | ||
397 | - static const char *flag_names[] = { "create", "noinit", "iscript", | ||
398 | + static const char *const flag_names[] = { "create", "noinit", "iscript", | ||
399 | "shared", "mntopts", NULL }; | ||
400 | static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT, | ||
401 | POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS }; | ||
402 | @@ -333,7 +627,7 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
403 | |||
404 | while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) { | ||
405 | for (i = 0; flag_names[i]; i++) { | ||
406 | - int namelen = strlen(flag_names[i]); | ||
407 | + size_t namelen = strlen(flag_names[i]); | ||
408 | |||
409 | if (strncmp(flag, flag_names[i], namelen) == 0) { | ||
410 | poly->flags |= flag_values[i]; | ||
411 | @@ -379,27 +673,27 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
412 | * of the namespace configuration file. It skips over comments and incomplete | ||
413 | * or malformed lines. It processes a valid line with information on | ||
414 | * polyinstantiating a directory by populating appropriate fields of a | ||
415 | - * polyinstatiated directory structure and then calling add_polydir_entry to | ||
416 | + * polyinstantiated directory structure and then calling add_polydir_entry to | ||
417 | * add that entry to the linked list of polyinstantiated directories. | ||
418 | */ | ||
419 | static int process_line(char *line, const char *home, const char *rhome, | ||
420 | struct instance_data *idata) | ||
421 | { | ||
422 | char *dir = NULL, *instance_prefix = NULL, *rdir = NULL; | ||
423 | + const char *config_dir, *config_instance_prefix; | ||
424 | char *method, *uids; | ||
425 | char *tptr; | ||
426 | struct polydir_s *poly; | ||
427 | int retval = 0; | ||
428 | char **config_options = NULL; | ||
429 | - static const char *var_names[] = {"HOME", "USER", NULL}; | ||
430 | + static const char *const var_names[] = {"HOME", "USER", NULL}; | ||
431 | const char *var_values[] = {home, idata->user}; | ||
432 | const char *rvar_values[] = {rhome, idata->ruser}; | ||
433 | - int len; | ||
434 | |||
435 | /* | ||
436 | * skip the leading white space | ||
437 | */ | ||
438 | - while (*line && isspace(*line)) | ||
439 | + while (*line && isspace((unsigned char)*line)) | ||
440 | line++; | ||
441 | |||
442 | /* | ||
443 | @@ -435,22 +729,19 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
444 | goto erralloc; | ||
445 | } | ||
446 | |||
447 | - dir = config_options[0]; | ||
448 | - if (dir == NULL) { | ||
449 | + config_dir = config_options[0]; | ||
450 | + if (config_dir == NULL) { | ||
451 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); | ||
452 | goto skipping; | ||
453 | } | ||
454 | - instance_prefix = config_options[1]; | ||
455 | - if (instance_prefix == NULL) { | ||
456 | + config_instance_prefix = config_options[1]; | ||
457 | + if (config_instance_prefix == NULL) { | ||
458 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix"); | ||
459 | - instance_prefix = NULL; | ||
460 | goto skipping; | ||
461 | } | ||
462 | method = config_options[2]; | ||
463 | if (method == NULL) { | ||
464 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); | ||
465 | - instance_prefix = NULL; | ||
466 | - dir = NULL; | ||
467 | goto skipping; | ||
468 | } | ||
469 | |||
470 | @@ -465,19 +756,16 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
471 | /* | ||
472 | * Expand $HOME and $USER in poly dir and instance dir prefix | ||
473 | */ | ||
474 | - if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) { | ||
475 | - instance_prefix = NULL; | ||
476 | - dir = NULL; | ||
477 | + if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) { | ||
478 | goto erralloc; | ||
479 | } | ||
480 | |||
481 | - if ((dir=expand_variables(dir, var_names, var_values)) == NULL) { | ||
482 | - instance_prefix = NULL; | ||
483 | + if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) { | ||
484 | goto erralloc; | ||
485 | } | ||
486 | |||
487 | - if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values)) | ||
488 | - == NULL) { | ||
489 | + if ((instance_prefix = expand_variables(config_instance_prefix, | ||
490 | + var_names, var_values)) == NULL) { | ||
491 | goto erralloc; | ||
492 | } | ||
493 | |||
494 | @@ -487,15 +775,8 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
495 | pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix); | ||
496 | } | ||
497 | |||
498 | - len = strlen(dir); | ||
499 | - if (len > 0 && dir[len-1] == '/') { | ||
500 | - dir[len-1] = '\0'; | ||
501 | - } | ||
502 | - | ||
503 | - len = strlen(rdir); | ||
504 | - if (len > 0 && rdir[len-1] == '/') { | ||
505 | - rdir[len-1] = '\0'; | ||
506 | - } | ||
507 | + strip_trailing_slashes(dir); | ||
508 | + strip_trailing_slashes(rdir); | ||
509 | |||
510 | if (dir[0] == '\0' || rdir[0] == '\0') { | ||
511 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir"); | ||
512 | @@ -506,26 +787,15 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
513 | * Populate polyinstantiated directory structure with appropriate | ||
514 | * pathnames and the method with which to polyinstantiate. | ||
515 | */ | ||
516 | - if (strlen(dir) >= sizeof(poly->dir) | ||
517 | - || strlen(rdir) >= sizeof(poly->rdir) | ||
518 | - || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) { | ||
519 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
520 | - goto skipping; | ||
521 | - } | ||
522 | - strcpy(poly->dir, dir); | ||
523 | - strcpy(poly->rdir, rdir); | ||
524 | - strcpy(poly->instance_prefix, instance_prefix); | ||
525 | - | ||
526 | if (parse_method(method, poly, idata) != 0) { | ||
527 | goto skipping; | ||
528 | } | ||
529 | |||
530 | - if (poly->method == TMPDIR) { | ||
531 | - if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) { | ||
532 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
533 | - goto skipping; | ||
534 | - } | ||
535 | - strcat(poly->instance_prefix, "XXXXXX"); | ||
536 | + if (pam_sprintf(poly->dir, "%s", dir) < 0 | ||
537 | + || pam_sprintf(poly->rdir, "%s", rdir) < 0 | ||
538 | + || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) { | ||
539 | + pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
540 | + goto skipping; | ||
541 | } | ||
542 | |||
543 | /* | ||
544 | @@ -549,7 +819,7 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
545 | if (uids) { | ||
546 | uid_t *uidptr; | ||
547 | const char *ustr, *sstr; | ||
548 | - int count, i; | ||
549 | + size_t count, i; | ||
550 | |||
551 | if (*uids == '~') { | ||
552 | poly->flags |= POLYDIR_EXCLUSIVE; | ||
553 | @@ -558,8 +828,13 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
554 | for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++) | ||
555 | sstr = strchr(ustr, ','); | ||
556 | |||
557 | + if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
558 | + pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration"); | ||
559 | + goto skipping; | ||
560 | + } | ||
561 | + | ||
562 | poly->num_uids = count; | ||
563 | - poly->uid = (uid_t *) malloc(count * sizeof (uid_t)); | ||
564 | + poly->uid = malloc(count * sizeof (uid_t)); | ||
565 | uidptr = poly->uid; | ||
566 | if (uidptr == NULL) { | ||
567 | goto erralloc; | ||
568 | @@ -798,6 +1073,23 @@ static char *md5hash(const char *instname, struct instance_data *idata) | ||
569 | } | ||
570 | |||
571 | #ifdef WITH_SELINUX | ||
572 | +static char *secure_getfilecon(pam_handle_t *pamh, const char *dir) | ||
573 | +{ | ||
574 | + char *ctx = NULL; | ||
575 | + int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL); | ||
576 | + if (dfd < 0) { | ||
577 | + pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir); | ||
578 | + return NULL; | ||
579 | + } | ||
580 | + if (fgetfilecon(dfd, &ctx) < 0) | ||
581 | + ctx = NULL; | ||
582 | + if (ctx == NULL) | ||
583 | + pam_syslog(pamh, LOG_ERR, | ||
584 | + "Error getting poly dir context for %s: %m", dir); | ||
585 | + close(dfd); | ||
586 | + return ctx; | ||
587 | +} | ||
588 | + | ||
589 | static int form_context(const struct polydir_s *polyptr, | ||
590 | char **i_context, char **origcon, | ||
591 | struct instance_data *idata) | ||
592 | @@ -809,12 +1101,9 @@ static int form_context(const struct polydir_s *polyptr, | ||
593 | /* | ||
594 | * Get the security context of the directory to polyinstantiate. | ||
595 | */ | ||
596 | - rc = getfilecon(polyptr->dir, origcon); | ||
597 | - if (rc < 0 || *origcon == NULL) { | ||
598 | - pam_syslog(idata->pamh, LOG_ERR, | ||
599 | - "Error getting poly dir context, %m"); | ||
600 | + *origcon = secure_getfilecon(idata->pamh, polyptr->dir); | ||
601 | + if (*origcon == NULL) | ||
602 | return PAM_SESSION_ERR; | ||
603 | - } | ||
604 | |||
605 | if (polyptr->method == USER) return PAM_SUCCESS; | ||
606 | |||
607 | @@ -905,34 +1194,58 @@ static int form_context(const struct polydir_s *polyptr, | ||
608 | return rc; | ||
609 | } | ||
610 | /* Should never get here */ | ||
611 | + freecon(scon); | ||
612 | return PAM_SUCCESS; | ||
613 | } | ||
614 | #endif | ||
615 | |||
616 | /* | ||
617 | - * poly_name returns the name of the polyinstantiated instance directory | ||
618 | + * From the instance differentiation string, set in the polyptr structure: | ||
619 | + * - the absolute path to the instance dir, | ||
620 | + * - the absolute path to the previous dir (parent), | ||
621 | + * - the instance name (may be different than the instance differentiation string) | ||
622 | + */ | ||
623 | +static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation) | ||
624 | +{ | ||
625 | + char *tmp; | ||
626 | + | ||
627 | + if (pam_sprintf(polyptr->instance_absolute, "%s%s", | ||
628 | + polyptr->instance_prefix, inst_differentiation) < 0) | ||
629 | + return -1; | ||
630 | + | ||
631 | + polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1; | ||
632 | + | ||
633 | + if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0) | ||
634 | + return -1; | ||
635 | + | ||
636 | + tmp = strrchr(polyptr->instance_parent, '/') + 1; | ||
637 | + *tmp = '\0'; | ||
638 | + | ||
639 | + return 0; | ||
640 | +} | ||
641 | + | ||
642 | +/* | ||
643 | + * Set the name of the polyinstantiated instance directory | ||
644 | * based on the method used for polyinstantiation (user, context or level) | ||
645 | * In addition, the function also returns the security contexts of the | ||
646 | * original directory to polyinstantiate and the polyinstantiated instance | ||
647 | * directory. | ||
648 | */ | ||
649 | #ifdef WITH_SELINUX | ||
650 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
651 | - char **i_context, char **origcon, | ||
652 | - struct instance_data *idata) | ||
653 | +static int poly_name(struct polydir_s *polyptr, char **i_context, | ||
654 | + char **origcon, struct instance_data *idata) | ||
655 | #else | ||
656 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
657 | - struct instance_data *idata) | ||
658 | +static int poly_name(struct polydir_s *polyptr, struct instance_data *idata) | ||
659 | #endif | ||
660 | { | ||
661 | int rc; | ||
662 | + char *inst_differentiation = NULL; | ||
663 | char *hash = NULL; | ||
664 | enum polymethod pm; | ||
665 | #ifdef WITH_SELINUX | ||
666 | char *rawcon = NULL; | ||
667 | #endif | ||
668 | |||
669 | - *i_name = NULL; | ||
670 | #ifdef WITH_SELINUX | ||
671 | *i_context = NULL; | ||
672 | *origcon = NULL; | ||
673 | @@ -966,10 +1279,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
674 | |||
675 | switch (pm) { | ||
676 | case USER: | ||
677 | - if (asprintf(i_name, "%s", idata->user) < 0) { | ||
678 | - *i_name = NULL; | ||
679 | + if ((inst_differentiation = strdup(idata->user)) == NULL) | ||
680 | goto fail; | ||
681 | - } | ||
682 | break; | ||
683 | |||
684 | #ifdef WITH_SELINUX | ||
685 | @@ -979,26 +1290,25 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
686 | pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context"); | ||
687 | goto fail; | ||
688 | } | ||
689 | - if (polyptr->flags & POLYDIR_SHARED) { | ||
690 | - if (asprintf(i_name, "%s", rawcon) < 0) { | ||
691 | - *i_name = NULL; | ||
692 | - goto fail; | ||
693 | - } | ||
694 | - } else { | ||
695 | - if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { | ||
696 | - *i_name = NULL; | ||
697 | - goto fail; | ||
698 | - } | ||
699 | - } | ||
700 | + if (polyptr->flags & POLYDIR_SHARED) | ||
701 | + inst_differentiation = strdup(rawcon); | ||
702 | + else | ||
703 | + inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user); | ||
704 | + if (inst_differentiation == NULL) | ||
705 | + goto fail; | ||
706 | break; | ||
707 | |||
708 | #endif /* WITH_SELINUX */ | ||
709 | |||
710 | case TMPDIR: | ||
711 | + if ((inst_differentiation = strdup("XXXXXX")) == NULL) | ||
712 | + goto fail; | ||
713 | + goto success; | ||
714 | + | ||
715 | case TMPFS: | ||
716 | - if ((*i_name=strdup("")) == NULL) | ||
717 | + if ((inst_differentiation=strdup("")) == NULL) | ||
718 | goto fail; | ||
719 | - return PAM_SUCCESS; | ||
720 | + goto success; | ||
721 | |||
722 | default: | ||
723 | if (idata->flags & PAMNS_DEBUG) | ||
724 | @@ -1007,31 +1317,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
725 | } | ||
726 | |||
727 | if (idata->flags & PAMNS_DEBUG) | ||
728 | - pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name); | ||
729 | + pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation); | ||
730 | |||
731 | - if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) { | ||
732 | - hash = md5hash(*i_name, idata); | ||
733 | + if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) { | ||
734 | + hash = md5hash(inst_differentiation, idata); | ||
735 | if (hash == NULL) { | ||
736 | goto fail; | ||
737 | } | ||
738 | if (idata->flags & PAMNS_GEN_HASH) { | ||
739 | - free(*i_name); | ||
740 | - *i_name = hash; | ||
741 | + free(inst_differentiation); | ||
742 | + inst_differentiation = hash; | ||
743 | hash = NULL; | ||
744 | } else { | ||
745 | - char *newname; | ||
746 | - if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash), | ||
747 | - *i_name, hash) < 0) { | ||
748 | + char *newname = | ||
749 | + pam_asprintf("%.*s_%s", | ||
750 | + NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash), | ||
751 | + inst_differentiation, hash); | ||
752 | + if (newname == NULL) | ||
753 | goto fail; | ||
754 | - } | ||
755 | - free(*i_name); | ||
756 | - *i_name = newname; | ||
757 | + free(inst_differentiation); | ||
758 | + inst_differentiation = newname; | ||
759 | } | ||
760 | } | ||
761 | - rc = PAM_SUCCESS; | ||
762 | |||
763 | +success: | ||
764 | + if (set_polydir_paths(polyptr, inst_differentiation) == -1) | ||
765 | + goto fail; | ||
766 | + | ||
767 | + rc = PAM_SUCCESS; | ||
768 | fail: | ||
769 | free(hash); | ||
770 | + free(inst_differentiation); | ||
771 | #ifdef WITH_SELINUX | ||
772 | freecon(rawcon); | ||
773 | #endif | ||
774 | @@ -1042,187 +1358,35 @@ fail: | ||
775 | freecon(*origcon); | ||
776 | *origcon = NULL; | ||
777 | #endif | ||
778 | - free(*i_name); | ||
779 | - *i_name = NULL; | ||
780 | } | ||
781 | return rc; | ||
782 | } | ||
783 | |||
784 | -static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
785 | +static int check_inst_parent(int dfd, struct instance_data *idata) | ||
786 | { | ||
787 | - struct protect_dir_s *dir = idata->protect_dirs; | ||
788 | - char tmpbuf[64]; | ||
789 | - | ||
790 | - while (dir != NULL) { | ||
791 | - if (strcmp(path, dir->dir) == 0) { | ||
792 | - return 0; | ||
793 | - } | ||
794 | - dir = dir->next; | ||
795 | - } | ||
796 | - | ||
797 | - dir = calloc(1, sizeof(*dir)); | ||
798 | - | ||
799 | - if (dir == NULL) { | ||
800 | - return -1; | ||
801 | - } | ||
802 | - | ||
803 | - dir->dir = strdup(path); | ||
804 | - | ||
805 | - if (dir->dir == NULL) { | ||
806 | - free(dir); | ||
807 | - return -1; | ||
808 | - } | ||
809 | + struct stat instpbuf; | ||
810 | |||
811 | - snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd); | ||
812 | + /* | ||
813 | + * Stat the instance parent directory to make sure it's writable by | ||
814 | + * root only (unless the admin explicitly instructs to ignore the | ||
815 | + * instance parent mode by the "ignore_instance_parent_mode" argument). | ||
816 | + */ | ||
817 | |||
818 | - if (idata->flags & PAMNS_DEBUG) { | ||
819 | - pam_syslog(idata->pamh, LOG_INFO, | ||
820 | - "Protect mount of %s over itself", path); | ||
821 | - } | ||
822 | + if (idata->flags & PAMNS_IGN_INST_PARENT_MODE) | ||
823 | + return PAM_SUCCESS; | ||
824 | |||
825 | - if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
826 | - int save_errno = errno; | ||
827 | + if (fstat(dfd, &instpbuf) < 0) { | ||
828 | pam_syslog(idata->pamh, LOG_ERR, | ||
829 | - "Protect mount of %s failed: %m", tmpbuf); | ||
830 | - free(dir->dir); | ||
831 | - free(dir); | ||
832 | - errno = save_errno; | ||
833 | - return -1; | ||
834 | - } | ||
835 | - | ||
836 | - dir->next = idata->protect_dirs; | ||
837 | - idata->protect_dirs = dir; | ||
838 | - | ||
839 | - return 0; | ||
840 | -} | ||
841 | - | ||
842 | -static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
843 | - struct instance_data *idata) | ||
844 | -{ | ||
845 | - char *p = strdup(path); | ||
846 | - char *d; | ||
847 | - char *dir = p; | ||
848 | - int dfd = AT_FDCWD; | ||
849 | - int dfd_next; | ||
850 | - int save_errno; | ||
851 | - int flags = O_RDONLY | O_DIRECTORY; | ||
852 | - int rv = -1; | ||
853 | - struct stat st; | ||
854 | - | ||
855 | - if (p == NULL) { | ||
856 | - goto error; | ||
857 | - } | ||
858 | - | ||
859 | - if (*dir == '/') { | ||
860 | - dfd = open("/", flags); | ||
861 | - if (dfd == -1) { | ||
862 | - goto error; | ||
863 | - } | ||
864 | - dir++; /* assume / is safe */ | ||
865 | - } | ||
866 | - | ||
867 | - while ((d=strchr(dir, '/')) != NULL) { | ||
868 | - *d = '\0'; | ||
869 | - dfd_next = openat(dfd, dir, flags); | ||
870 | - if (dfd_next == -1) { | ||
871 | - goto error; | ||
872 | - } | ||
873 | - | ||
874 | - if (dfd != AT_FDCWD) | ||
875 | - close(dfd); | ||
876 | - dfd = dfd_next; | ||
877 | - | ||
878 | - if (fstat(dfd, &st) != 0) { | ||
879 | - goto error; | ||
880 | - } | ||
881 | - | ||
882 | - if (flags & O_NOFOLLOW) { | ||
883 | - /* we are inside user-owned dir - protect */ | ||
884 | - if (protect_mount(dfd, p, idata) == -1) | ||
885 | - goto error; | ||
886 | - } else if (st.st_uid != 0 || st.st_gid != 0 || | ||
887 | - (st.st_mode & S_IWOTH)) { | ||
888 | - /* do not follow symlinks on subdirectories */ | ||
889 | - flags |= O_NOFOLLOW; | ||
890 | - } | ||
891 | - | ||
892 | - *d = '/'; | ||
893 | - dir = d + 1; | ||
894 | - } | ||
895 | - | ||
896 | - rv = openat(dfd, dir, flags); | ||
897 | - | ||
898 | - if (rv == -1) { | ||
899 | - if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { | ||
900 | - goto error; | ||
901 | - } | ||
902 | - rv = openat(dfd, dir, flags); | ||
903 | - } | ||
904 | - | ||
905 | - if (flags & O_NOFOLLOW) { | ||
906 | - /* we are inside user-owned dir - protect */ | ||
907 | - if (protect_mount(rv, p, idata) == -1) { | ||
908 | - save_errno = errno; | ||
909 | - close(rv); | ||
910 | - rv = -1; | ||
911 | - errno = save_errno; | ||
912 | - } | ||
913 | - } | ||
914 | - | ||
915 | -error: | ||
916 | - save_errno = errno; | ||
917 | - free(p); | ||
918 | - if (dfd != AT_FDCWD && dfd >= 0) | ||
919 | - close(dfd); | ||
920 | - errno = save_errno; | ||
921 | - | ||
922 | - return rv; | ||
923 | -} | ||
924 | - | ||
925 | -static int check_inst_parent(char *ipath, struct instance_data *idata) | ||
926 | -{ | ||
927 | - struct stat instpbuf; | ||
928 | - char *inst_parent, *trailing_slash; | ||
929 | - int dfd; | ||
930 | - /* | ||
931 | - * stat the instance parent path to make sure it exists | ||
932 | - * and is a directory. Check that its mode is 000 (unless the | ||
933 | - * admin explicitly instructs to ignore the instance parent | ||
934 | - * mode by the "ignore_instance_parent_mode" argument). | ||
935 | - */ | ||
936 | - inst_parent = (char *) malloc(strlen(ipath)+1); | ||
937 | - if (!inst_parent) { | ||
938 | - pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string"); | ||
939 | + "Error accessing instance parent, %m"); | ||
940 | return PAM_SESSION_ERR; | ||
941 | } | ||
942 | |||
943 | - strcpy(inst_parent, ipath); | ||
944 | - trailing_slash = strrchr(inst_parent, '/'); | ||
945 | - if (trailing_slash) | ||
946 | - *trailing_slash = '\0'; | ||
947 | - | ||
948 | - dfd = protect_dir(inst_parent, 0, 1, idata); | ||
949 | - | ||
950 | - if (dfd == -1 || fstat(dfd, &instpbuf) < 0) { | ||
951 | + if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
952 | pam_syslog(idata->pamh, LOG_ERR, | ||
953 | - "Error creating or accessing instance parent %s, %m", inst_parent); | ||
954 | - if (dfd != -1) | ||
955 | - close(dfd); | ||
956 | - free(inst_parent); | ||
957 | + "Mode of inst parent not 000 or owner not root"); | ||
958 | return PAM_SESSION_ERR; | ||
959 | } | ||
960 | |||
961 | - if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) { | ||
962 | - if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
963 | - pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root", | ||
964 | - inst_parent); | ||
965 | - close(dfd); | ||
966 | - free(inst_parent); | ||
967 | - return PAM_SESSION_ERR; | ||
968 | - } | ||
969 | - } | ||
970 | - close(dfd); | ||
971 | - free(inst_parent); | ||
972 | return PAM_SUCCESS; | ||
973 | } | ||
974 | |||
975 | @@ -1271,9 +1435,10 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, | ||
976 | if (setuid(geteuid()) < 0) { | ||
977 | /* ignore failures, they don't matter */ | ||
978 | } | ||
979 | + close_fds_pre_exec(idata); | ||
980 | |||
981 | - if (execle(init_script, init_script, | ||
982 | - polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0) | ||
983 | + execle(init_script, init_script, | ||
984 | + polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); | ||
985 | _exit(1); | ||
986 | } else if (pid > 0) { | ||
987 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
988 | @@ -1324,7 +1489,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
989 | |||
990 | #ifdef WITH_SELINUX | ||
991 | if (idata->flags & PAMNS_SELINUX_ENABLED) { | ||
992 | - getfscreatecon_raw(&oldcon_raw); | ||
993 | + if (getfscreatecon_raw(&oldcon_raw) != 0) | ||
994 | + pam_syslog(idata->pamh, LOG_NOTICE, | ||
995 | + "Error retrieving fs create context: %m"); | ||
996 | |||
997 | label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); | ||
998 | if (!label_handle) { | ||
999 | @@ -1349,11 +1516,16 @@ static int create_polydir(struct polydir_s *polyptr, | ||
1000 | } | ||
1001 | #endif | ||
1002 | |||
1003 | - rc = protect_dir(dir, mode, 1, idata); | ||
1004 | + rc = secure_opendir(dir, | ||
1005 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD, | ||
1006 | + mode, idata); | ||
1007 | if (rc == -1) { | ||
1008 | pam_syslog(idata->pamh, LOG_ERR, | ||
1009 | "Error creating directory %s: %m", dir); | ||
1010 | - return PAM_SESSION_ERR; | ||
1011 | +#ifdef WITH_SELINUX | ||
1012 | + freecon(oldcon_raw); | ||
1013 | +#endif | ||
1014 | + return -1; | ||
1015 | } | ||
1016 | |||
1017 | #ifdef WITH_SELINUX | ||
1018 | @@ -1374,9 +1546,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
1019 | pam_syslog(idata->pamh, LOG_ERR, | ||
1020 | "Error changing mode of directory %s: %m", dir); | ||
1021 | close(rc); | ||
1022 | - umount(dir); /* undo the eventual protection bind mount */ | ||
1023 | - rmdir(dir); | ||
1024 | - return PAM_SESSION_ERR; | ||
1025 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
1026 | + secure_try_rmdir(dir); | ||
1027 | + return -1; | ||
1028 | } | ||
1029 | } | ||
1030 | |||
1031 | @@ -1394,41 +1566,37 @@ static int create_polydir(struct polydir_s *polyptr, | ||
1032 | pam_syslog(idata->pamh, LOG_ERR, | ||
1033 | "Unable to change owner on directory %s: %m", dir); | ||
1034 | close(rc); | ||
1035 | - umount(dir); /* undo the eventual protection bind mount */ | ||
1036 | - rmdir(dir); | ||
1037 | - return PAM_SESSION_ERR; | ||
1038 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
1039 | + secure_try_rmdir(dir); | ||
1040 | + return -1; | ||
1041 | } | ||
1042 | |||
1043 | - close(rc); | ||
1044 | - | ||
1045 | if (idata->flags & PAMNS_DEBUG) | ||
1046 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
1047 | "Polydir owner %u group %u", uid, gid); | ||
1048 | |||
1049 | - return PAM_SUCCESS; | ||
1050 | + return rc; | ||
1051 | } | ||
1052 | |||
1053 | /* | ||
1054 | - * Create polyinstantiated instance directory (ipath). | ||
1055 | + * Create polyinstantiated instance directory. | ||
1056 | + * To protect against races, changes are done on a fd to the parent of the | ||
1057 | + * instance directory (dfd_iparent) and a relative path (polyptr->instname). | ||
1058 | + * The absolute path (polyptr->instance_absolute) is only updated when creating | ||
1059 | + * a tmpdir and used for logging purposes. | ||
1060 | */ | ||
1061 | #ifdef WITH_SELINUX | ||
1062 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
1063 | - const char *icontext, const char *ocontext, | ||
1064 | - struct instance_data *idata) | ||
1065 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
1066 | + struct stat *statbuf, const char *icontext, const char *ocontext, | ||
1067 | + struct instance_data *idata) | ||
1068 | #else | ||
1069 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
1070 | - struct instance_data *idata) | ||
1071 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
1072 | + struct stat *statbuf, struct instance_data *idata) | ||
1073 | #endif | ||
1074 | { | ||
1075 | struct stat newstatbuf; | ||
1076 | int fd; | ||
1077 | |||
1078 | - /* | ||
1079 | - * Check to make sure instance parent is valid. | ||
1080 | - */ | ||
1081 | - if (check_inst_parent(ipath, idata)) | ||
1082 | - return PAM_SESSION_ERR; | ||
1083 | - | ||
1084 | /* | ||
1085 | * Create instance directory and set its security context to the context | ||
1086 | * returned by the security policy. Set its mode and ownership | ||
1087 | @@ -1437,29 +1605,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
1088 | */ | ||
1089 | |||
1090 | if (polyptr->method == TMPDIR) { | ||
1091 | - if (mkdtemp(polyptr->instance_prefix) == NULL) { | ||
1092 | - pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m", | ||
1093 | - polyptr->instance_prefix); | ||
1094 | - polyptr->method = NONE; /* do not clean up! */ | ||
1095 | - return PAM_SESSION_ERR; | ||
1096 | - } | ||
1097 | - /* copy the actual directory name to ipath */ | ||
1098 | - strcpy(ipath, polyptr->instance_prefix); | ||
1099 | - } else if (mkdir(ipath, S_IRUSR) < 0) { | ||
1100 | + char s_path[PATH_MAX]; | ||
1101 | + /* | ||
1102 | + * Create the template for mkdtemp() as a magic link based on | ||
1103 | + * our existing fd to avoid symlink attacks and races. | ||
1104 | + */ | ||
1105 | + if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0 | ||
1106 | + || mkdtemp(s_path) == NULL) { | ||
1107 | + pam_syslog(idata->pamh, LOG_ERR, | ||
1108 | + "Error creating temporary instance dir %s, %m", | ||
1109 | + polyptr->instance_absolute); | ||
1110 | + polyptr->method = NONE; /* do not clean up! */ | ||
1111 | + return PAM_SESSION_ERR; | ||
1112 | + } | ||
1113 | + | ||
1114 | + /* Copy the actual directory name to polyptr->instname */ | ||
1115 | + strcpy(polyptr->instname, base_name(s_path)); | ||
1116 | + } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) { | ||
1117 | if (errno == EEXIST) | ||
1118 | return PAM_IGNORE; | ||
1119 | else { | ||
1120 | pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m", | ||
1121 | - ipath); | ||
1122 | + polyptr->instance_absolute); | ||
1123 | return PAM_SESSION_ERR; | ||
1124 | } | ||
1125 | } | ||
1126 | |||
1127 | - /* Open a descriptor to it to prevent races */ | ||
1128 | - fd = open(ipath, O_DIRECTORY | O_RDONLY); | ||
1129 | + /* Open a descriptor to prevent races, based on our existing fd. */ | ||
1130 | + fd = openat(dfd_iparent, polyptr->instname, | ||
1131 | + O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
1132 | if (fd < 0) { | ||
1133 | - pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath); | ||
1134 | - rmdir(ipath); | ||
1135 | + pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", | ||
1136 | + polyptr->instance_absolute); | ||
1137 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1138 | return PAM_SESSION_ERR; | ||
1139 | } | ||
1140 | #ifdef WITH_SELINUX | ||
1141 | @@ -1469,17 +1647,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
1142 | if (icontext) { | ||
1143 | if (fsetfilecon(fd, icontext) < 0) { | ||
1144 | pam_syslog(idata->pamh, LOG_ERR, | ||
1145 | - "Error setting context of %s to %s", ipath, icontext); | ||
1146 | + "Error setting context of %s to %s", | ||
1147 | + polyptr->instance_absolute, icontext); | ||
1148 | close(fd); | ||
1149 | - rmdir(ipath); | ||
1150 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1151 | return PAM_SESSION_ERR; | ||
1152 | } | ||
1153 | } else { | ||
1154 | if (fsetfilecon(fd, ocontext) < 0) { | ||
1155 | pam_syslog(idata->pamh, LOG_ERR, | ||
1156 | - "Error setting context of %s to %s", ipath, ocontext); | ||
1157 | + "Error setting context of %s to %s", | ||
1158 | + polyptr->instance_absolute, ocontext); | ||
1159 | close(fd); | ||
1160 | - rmdir(ipath); | ||
1161 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1162 | return PAM_SESSION_ERR; | ||
1163 | } | ||
1164 | } | ||
1165 | @@ -1487,9 +1667,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
1166 | #endif | ||
1167 | if (fstat(fd, &newstatbuf) < 0) { | ||
1168 | pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", | ||
1169 | - ipath); | ||
1170 | + polyptr->instance_absolute); | ||
1171 | close(fd); | ||
1172 | - rmdir(ipath); | ||
1173 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1174 | return PAM_SESSION_ERR; | ||
1175 | } | ||
1176 | if (newstatbuf.st_uid != statbuf->st_uid || | ||
1177 | @@ -1497,17 +1677,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
1178 | if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) { | ||
1179 | pam_syslog(idata->pamh, LOG_ERR, | ||
1180 | "Error changing owner for %s, %m", | ||
1181 | - ipath); | ||
1182 | + polyptr->instance_absolute); | ||
1183 | close(fd); | ||
1184 | - rmdir(ipath); | ||
1185 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1186 | return PAM_SESSION_ERR; | ||
1187 | } | ||
1188 | } | ||
1189 | if (fchmod(fd, statbuf->st_mode & 07777) < 0) { | ||
1190 | pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m", | ||
1191 | - ipath); | ||
1192 | + polyptr->instance_absolute); | ||
1193 | close(fd); | ||
1194 | - rmdir(ipath); | ||
1195 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
1196 | return PAM_SESSION_ERR; | ||
1197 | } | ||
1198 | close(fd); | ||
1199 | @@ -1526,9 +1706,12 @@ static int ns_setup(struct polydir_s *polyptr, | ||
1200 | struct instance_data *idata) | ||
1201 | { | ||
1202 | int retval; | ||
1203 | + int dfd_iparent = -1; | ||
1204 | + int dfd_ipath = -1; | ||
1205 | + int dfd_pptrdir = -1; | ||
1206 | int newdir = 1; | ||
1207 | - char *inst_dir = NULL; | ||
1208 | - char *instname = NULL; | ||
1209 | + char s_ipath[MAGIC_LNK_FD_SIZE]; | ||
1210 | + char s_pptrdir[MAGIC_LNK_FD_SIZE]; | ||
1211 | struct stat statbuf; | ||
1212 | #ifdef WITH_SELINUX | ||
1213 | char *instcontext = NULL, *origcontext = NULL; | ||
1214 | @@ -1538,39 +1721,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
1215 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
1216 | "Set namespace for directory %s", polyptr->dir); | ||
1217 | |||
1218 | - retval = protect_dir(polyptr->dir, 0, 0, idata); | ||
1219 | - | ||
1220 | - if (retval < 0 && errno != ENOENT) { | ||
1221 | - pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
1222 | - polyptr->dir); | ||
1223 | - return PAM_SESSION_ERR; | ||
1224 | - } | ||
1225 | + dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata); | ||
1226 | |||
1227 | - if (retval < 0) { | ||
1228 | - if ((polyptr->flags & POLYDIR_CREATE) && | ||
1229 | - create_polydir(polyptr, idata) != PAM_SUCCESS) | ||
1230 | - return PAM_SESSION_ERR; | ||
1231 | - } else { | ||
1232 | - close(retval); | ||
1233 | + if (dfd_pptrdir < 0) { | ||
1234 | + if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) { | ||
1235 | + pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
1236 | + polyptr->dir); | ||
1237 | + return PAM_SESSION_ERR; | ||
1238 | + } | ||
1239 | + dfd_pptrdir = create_polydir(polyptr, idata); | ||
1240 | + if (dfd_pptrdir < 0) | ||
1241 | + return PAM_SESSION_ERR; | ||
1242 | } | ||
1243 | |||
1244 | if (polyptr->method == TMPFS) { | ||
1245 | - if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
1246 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
1247 | - polyptr->dir); | ||
1248 | - return PAM_SESSION_ERR; | ||
1249 | - } | ||
1250 | + /* | ||
1251 | + * There is no function mount() that operate on a fd, so instead, we | ||
1252 | + * get the magic link corresponding to the fd and give it to mount(). | ||
1253 | + * This protects against potential races exploitable by an unpriv user. | ||
1254 | + */ | ||
1255 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
1256 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
1257 | + goto error_out; | ||
1258 | + } | ||
1259 | + | ||
1260 | + if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
1261 | + pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
1262 | + polyptr->dir); | ||
1263 | + goto error_out; | ||
1264 | + } | ||
1265 | |||
1266 | - if (polyptr->flags & POLYDIR_NOINIT) | ||
1267 | - return PAM_SUCCESS; | ||
1268 | + if (polyptr->flags & POLYDIR_NOINIT) { | ||
1269 | + retval = PAM_SUCCESS; | ||
1270 | + goto cleanup; | ||
1271 | + } | ||
1272 | |||
1273 | - return inst_init(polyptr, "tmpfs", idata, 1); | ||
1274 | + retval = inst_init(polyptr, "tmpfs", idata, 1); | ||
1275 | + goto cleanup; | ||
1276 | } | ||
1277 | |||
1278 | - if (stat(polyptr->dir, &statbuf) < 0) { | ||
1279 | - pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", | ||
1280 | - polyptr->dir); | ||
1281 | - return PAM_SESSION_ERR; | ||
1282 | + if (fstat(dfd_pptrdir, &statbuf) < 0) { | ||
1283 | + pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir); | ||
1284 | + goto error_out; | ||
1285 | } | ||
1286 | |||
1287 | /* | ||
1288 | @@ -1579,15 +1771,16 @@ static int ns_setup(struct polydir_s *polyptr, | ||
1289 | * security policy. | ||
1290 | */ | ||
1291 | #ifdef WITH_SELINUX | ||
1292 | - retval = poly_name(polyptr, &instname, &instcontext, | ||
1293 | - &origcontext, idata); | ||
1294 | + retval = poly_name(polyptr, &instcontext, &origcontext, idata); | ||
1295 | #else | ||
1296 | - retval = poly_name(polyptr, &instname, idata); | ||
1297 | + retval = poly_name(polyptr, idata); | ||
1298 | #endif | ||
1299 | |||
1300 | if (retval != PAM_SUCCESS) { | ||
1301 | - if (retval != PAM_IGNORE) | ||
1302 | + if (retval != PAM_IGNORE) { | ||
1303 | pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name"); | ||
1304 | + goto error_out; | ||
1305 | + } | ||
1306 | goto cleanup; | ||
1307 | } else { | ||
1308 | #ifdef WITH_SELINUX | ||
1309 | @@ -1598,22 +1791,33 @@ static int ns_setup(struct polydir_s *polyptr, | ||
1310 | #endif | ||
1311 | } | ||
1312 | |||
1313 | - if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0) | ||
1314 | - goto error_out; | ||
1315 | - | ||
1316 | - if (idata->flags & PAMNS_DEBUG) | ||
1317 | - pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s", | ||
1318 | - inst_dir); | ||
1319 | + /* | ||
1320 | + * Gets a fd in a secure manner (we may be operating on a path under | ||
1321 | + * user control), and check it's compliant. | ||
1322 | + * Then, we should *always* operate on *this* fd and a relative path | ||
1323 | + * to be protected against race conditions. | ||
1324 | + */ | ||
1325 | + dfd_iparent = secure_opendir(polyptr->instance_parent, | ||
1326 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata); | ||
1327 | + if (dfd_iparent == -1) { | ||
1328 | + pam_syslog(idata->pamh, LOG_ERR, | ||
1329 | + "polyptr->instance_parent %s access error", | ||
1330 | + polyptr->instance_parent); | ||
1331 | + goto error_out; | ||
1332 | + } | ||
1333 | + if (check_inst_parent(dfd_iparent, idata)) { | ||
1334 | + goto error_out; | ||
1335 | + } | ||
1336 | |||
1337 | /* | ||
1338 | * Create instance directory with appropriate security | ||
1339 | * contexts, owner, group and mode bits. | ||
1340 | */ | ||
1341 | #ifdef WITH_SELINUX | ||
1342 | - retval = create_instance(polyptr, inst_dir, &statbuf, instcontext, | ||
1343 | - origcontext, idata); | ||
1344 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext, | ||
1345 | + origcontext, idata); | ||
1346 | #else | ||
1347 | - retval = create_instance(polyptr, inst_dir, &statbuf, idata); | ||
1348 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, idata); | ||
1349 | #endif | ||
1350 | |||
1351 | if (retval == PAM_IGNORE) { | ||
1352 | @@ -1625,19 +1829,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
1353 | goto error_out; | ||
1354 | } | ||
1355 | |||
1356 | + /* | ||
1357 | + * Instead of getting a new secure fd, we reuse the fd opened on directory | ||
1358 | + * polyptr->instance_parent to ensure we are working on the same dir as | ||
1359 | + * previously, and thus ensure that previous checks (e.g. check_inst_parent()) | ||
1360 | + * are still relevant. | ||
1361 | + */ | ||
1362 | + dfd_ipath = openat(dfd_iparent, polyptr->instname, | ||
1363 | + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
1364 | + if (dfd_ipath == -1) { | ||
1365 | + pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m", | ||
1366 | + polyptr->instname); | ||
1367 | + goto error_out; | ||
1368 | + } | ||
1369 | + | ||
1370 | + if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) { | ||
1371 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath"); | ||
1372 | + goto error_out; | ||
1373 | + } | ||
1374 | + | ||
1375 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
1376 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
1377 | + goto error_out; | ||
1378 | + } | ||
1379 | + | ||
1380 | /* | ||
1381 | * Bind mount instance directory on top of the polyinstantiated | ||
1382 | * directory to provide an instance of polyinstantiated directory | ||
1383 | * based on polyinstantiated method. | ||
1384 | + * | ||
1385 | + * Operates on magic links created from two fd obtained securely | ||
1386 | + * to protect against race conditions and symlink attacks. Indeed, | ||
1387 | + * the source and destination can be in a user controled path. | ||
1388 | */ | ||
1389 | - if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) { | ||
1390 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m", | ||
1391 | - inst_dir, polyptr->dir); | ||
1392 | + if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) { | ||
1393 | + pam_syslog(idata->pamh, LOG_ERR, | ||
1394 | + "Error mounting %s on %s (%s on %s), %m", | ||
1395 | + s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir); | ||
1396 | goto error_out; | ||
1397 | } | ||
1398 | |||
1399 | if (!(polyptr->flags & POLYDIR_NOINIT)) | ||
1400 | - retval = inst_init(polyptr, inst_dir, idata, newdir); | ||
1401 | + retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir); | ||
1402 | |||
1403 | goto cleanup; | ||
1404 | |||
1405 | @@ -1649,8 +1882,12 @@ error_out: | ||
1406 | retval = PAM_SESSION_ERR; | ||
1407 | |||
1408 | cleanup: | ||
1409 | - free(inst_dir); | ||
1410 | - free(instname); | ||
1411 | + if (dfd_iparent != -1) | ||
1412 | + close(dfd_iparent); | ||
1413 | + if (dfd_ipath != -1) | ||
1414 | + close(dfd_ipath); | ||
1415 | + if (dfd_pptrdir != -1) | ||
1416 | + close(dfd_pptrdir); | ||
1417 | #ifdef WITH_SELINUX | ||
1418 | freecon(instcontext); | ||
1419 | freecon(origcontext); | ||
1420 | @@ -1689,6 +1926,7 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1421 | { | ||
1422 | struct polydir_s *pptr; | ||
1423 | pid_t rc, pid; | ||
1424 | + int dfd = -1; | ||
1425 | struct sigaction newsa, oldsa; | ||
1426 | int status; | ||
1427 | |||
1428 | @@ -1700,7 +1938,17 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1429 | } | ||
1430 | |||
1431 | for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) { | ||
1432 | - if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) { | ||
1433 | + if (pptr->method == TMPDIR) { | ||
1434 | + | ||
1435 | + dfd = secure_opendir_stateless(pptr->instance_parent); | ||
1436 | + if (dfd == -1) | ||
1437 | + continue; | ||
1438 | + | ||
1439 | + if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { | ||
1440 | + close(dfd); | ||
1441 | + continue; | ||
1442 | + } | ||
1443 | + | ||
1444 | pid = fork(); | ||
1445 | if (pid == 0) { | ||
1446 | static char *envp[] = { NULL }; | ||
1447 | @@ -1710,9 +1958,21 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1448 | _exit(1); | ||
1449 | } | ||
1450 | #endif | ||
1451 | - if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0) | ||
1452 | - _exit(1); | ||
1453 | + if (fchdir(dfd) == -1) { | ||
1454 | + pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m", | ||
1455 | + pptr->instance_absolute); | ||
1456 | + _exit(1); | ||
1457 | + } | ||
1458 | + | ||
1459 | + close_fds_pre_exec(idata); | ||
1460 | + | ||
1461 | + execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp); | ||
1462 | + _exit(1); | ||
1463 | } else if (pid > 0) { | ||
1464 | + | ||
1465 | + if (dfd != -1) | ||
1466 | + close(dfd); | ||
1467 | + | ||
1468 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
1469 | (errno == EINTR)); | ||
1470 | if (rc == (pid_t)-1) { | ||
1471 | @@ -1725,8 +1985,12 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1472 | "Error removing %s", pptr->instance_prefix); | ||
1473 | } | ||
1474 | } else if (pid < 0) { | ||
1475 | + | ||
1476 | + if (dfd != -1) | ||
1477 | + close(dfd); | ||
1478 | + | ||
1479 | pam_syslog(idata->pamh, LOG_ERR, | ||
1480 | - "Cannot fork to run namespace init script, %m"); | ||
1481 | + "Cannot fork to cleanup temporary directory, %m"); | ||
1482 | rc = PAM_SESSION_ERR; | ||
1483 | goto out; | ||
1484 | } | ||
1485 | @@ -1748,6 +2012,7 @@ out: | ||
1486 | static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
1487 | { | ||
1488 | int retval = 0, need_poly = 0, changing_dir = 0; | ||
1489 | + int dfd = -1; | ||
1490 | char *cptr, *fptr, poly_parent[PATH_MAX]; | ||
1491 | struct polydir_s *pptr; | ||
1492 | |||
1493 | @@ -1863,13 +2128,21 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
1494 | strcpy(poly_parent, "/"); | ||
1495 | else if (cptr) | ||
1496 | *cptr = '\0'; | ||
1497 | - if (chdir(poly_parent) < 0) { | ||
1498 | + | ||
1499 | + dfd = secure_opendir_stateless(poly_parent); | ||
1500 | + if (dfd == -1) { | ||
1501 | + pam_syslog(idata->pamh, LOG_ERR, | ||
1502 | + "Failed opening %s to fchdir: %m", poly_parent); | ||
1503 | + } | ||
1504 | + else if (fchdir(dfd) == -1) { | ||
1505 | pam_syslog(idata->pamh, LOG_ERR, | ||
1506 | - "Can't chdir to %s, %m", poly_parent); | ||
1507 | + "Failed fchdir to %s: %m", poly_parent); | ||
1508 | } | ||
1509 | + if (dfd != -1) | ||
1510 | + close(dfd); | ||
1511 | } | ||
1512 | |||
1513 | - if (umount(pptr->rdir) < 0) { | ||
1514 | + if (secure_umount(pptr->rdir) < 0) { | ||
1515 | int saved_errno = errno; | ||
1516 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
1517 | pptr->rdir); | ||
1518 | @@ -1939,7 +2212,7 @@ static int orig_namespace(struct instance_data *idata) | ||
1519 | "Unmounting instance dir for user %d & dir %s", | ||
1520 | idata->uid, pptr->dir); | ||
1521 | |||
1522 | - if (umount(pptr->dir) < 0) { | ||
1523 | + if (secure_umount(pptr->dir) < 0) { | ||
1524 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
1525 | pptr->dir); | ||
1526 | return PAM_SESSION_ERR; | ||
1527 | diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h | ||
1528 | index b51f284..abd570d 100644 | ||
1529 | --- a/modules/pam_namespace/pam_namespace.h | ||
1530 | +++ b/modules/pam_namespace/pam_namespace.h | ||
1531 | @@ -44,21 +44,16 @@ | ||
1532 | #include <stdlib.h> | ||
1533 | #include <errno.h> | ||
1534 | #include <syslog.h> | ||
1535 | -#include <dlfcn.h> | ||
1536 | -#include <stdarg.h> | ||
1537 | #include <pwd.h> | ||
1538 | #include <grp.h> | ||
1539 | #include <limits.h> | ||
1540 | #include <sys/types.h> | ||
1541 | #include <sys/stat.h> | ||
1542 | -#include <sys/resource.h> | ||
1543 | #include <sys/mount.h> | ||
1544 | #include <sys/wait.h> | ||
1545 | -#include <libgen.h> | ||
1546 | #include <fcntl.h> | ||
1547 | #include <sched.h> | ||
1548 | #include <glob.h> | ||
1549 | -#include <locale.h> | ||
1550 | #include "security/pam_modules.h" | ||
1551 | #include "security/pam_modutil.h" | ||
1552 | #include "security/pam_ext.h" | ||
1553 | @@ -112,7 +107,7 @@ | ||
1554 | #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */ | ||
1555 | |||
1556 | /* polydir flags */ | ||
1557 | -#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */ | ||
1558 | +#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */ | ||
1559 | #define POLYDIR_CREATE 0x00000002 /* create the polydir */ | ||
1560 | #define POLYDIR_NOINIT 0x00000004 /* no init script */ | ||
1561 | #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */ | ||
1562 | @@ -124,6 +119,13 @@ | ||
1563 | #define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data" | ||
1564 | #define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data" | ||
1565 | |||
1566 | +/* | ||
1567 | + * Operation mode for function secure_opendir() | ||
1568 | + */ | ||
1569 | +#define SECURE_OPENDIR_PROTECT 0x00000001 | ||
1570 | +#define SECURE_OPENDIR_MKDIR 0x00000002 | ||
1571 | +#define SECURE_OPENDIR_FULL_FD 0x00000004 | ||
1572 | + | ||
1573 | /* | ||
1574 | * Polyinstantiation method options, based on user, security context | ||
1575 | * or both | ||
1576 | @@ -161,6 +163,9 @@ struct polydir_s { | ||
1577 | char dir[PATH_MAX]; /* directory to polyinstantiate */ | ||
1578 | char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */ | ||
1579 | char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */ | ||
1580 | + char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */ | ||
1581 | + char instance_parent[PATH_MAX]; /* parent dir of the instance dir */ | ||
1582 | + char *instname; /* last segment of the path to the instance dir */ | ||
1583 | enum polymethod method; /* method used to polyinstantiate */ | ||
1584 | unsigned int num_uids; /* number of override uids */ | ||
1585 | uid_t *uid; /* list of override uids */ | ||
1586 | -- | ||
1587 | 2.50.1 | ||
1588 | |||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch new file mode 100644 index 0000000000..712d60581c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch | |||
@@ -0,0 +1,187 @@ | |||
1 | From 592d84e1265d04c3104acee815a503856db503a1 Mon Sep 17 00:00:00 2001 | ||
2 | From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
3 | Date: Tue, 4 Mar 2025 14:37:02 +0100 | ||
4 | Subject: [PATCH] pam_namespace: add flags to indicate path safety | ||
5 | |||
6 | Add two flags in the script to indicate if the paths to the polydir | ||
7 | and the instance directories are safe (root owned and writable by | ||
8 | root only). | ||
9 | |||
10 | Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
11 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
12 | |||
13 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/592d84e1265d04c3104acee815a503856db503a1] | ||
14 | CVE: CVE-2025-6020 | ||
15 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
16 | --- | ||
17 | modules/pam_namespace/namespace.init | 56 ++++++++++++------- | ||
18 | modules/pam_namespace/pam_namespace.c | 79 ++++++++++++++++++++++++++- | ||
19 | 2 files changed, 115 insertions(+), 20 deletions(-) | ||
20 | |||
21 | diff --git a/modules/pam_namespace/namespace.init b/modules/pam_namespace/namespace.init | ||
22 | index 67d4aa2..8782178 100755 | ||
23 | --- a/modules/pam_namespace/namespace.init | ||
24 | +++ b/modules/pam_namespace/namespace.init | ||
25 | @@ -1,25 +1,43 @@ | ||
26 | #!/bin/sh | ||
27 | -# It receives polydir path as $1, the instance path as $2, | ||
28 | -# a flag whether the instance dir was newly created (0 - no, 1 - yes) in $3, | ||
29 | -# and user name in $4. | ||
30 | +# It receives as arguments: | ||
31 | +# - $1 polydir path (see WARNING below) | ||
32 | +# - $2 instance path (see WARNING below) | ||
33 | +# - $3 flag whether the instance dir was newly created (0 - no, 1 - yes) | ||
34 | +# - $4 user name | ||
35 | +# - $5 flag whether the polydir path ($1) is safe (0 - unsafe, 1 -safe) | ||
36 | +# - $6 flag whether the instance path ($2) is safe (0 - unsafe, 1 - safe) | ||
37 | +# | ||
38 | +# WARNING: This script is invoked with full root privileges. Accessing | ||
39 | +# the polydir ($1) and the instance ($2) directories in this context may be | ||
40 | +# extremely dangerous as those can be under user control. The flags $5 and $6 | ||
41 | +# are provided to let you know if all the segments part of the path (except the | ||
42 | +# last one) are owned by root and are writable by root only. If the path does | ||
43 | +# not meet these criteria, you expose yourself to possible symlink attacks when | ||
44 | +# accessing these path. | ||
45 | +# However, even if the path components are safe, the content of the | ||
46 | +# directories may still be owned/writable by a user, so care must be taken! | ||
47 | # | ||
48 | # The following section will copy the contents of /etc/skel if this is a | ||
49 | # newly created home directory. | ||
50 | -if [ "$3" = 1 ]; then | ||
51 | - # This line will fix the labeling on all newly created directories | ||
52 | - [ -x /sbin/restorecon ] && /sbin/restorecon "$1" | ||
53 | - user="$4" | ||
54 | - passwd=$(getent passwd "$user") | ||
55 | - homedir=$(echo "$passwd" | cut -f6 -d":") | ||
56 | - if [ "$1" = "$homedir" ]; then | ||
57 | - gid=$(echo "$passwd" | cut -f4 -d":") | ||
58 | - cp -rT /etc/skel "$homedir" | ||
59 | - chown -R "$user":"$gid" "$homedir" | ||
60 | - mask=$(awk '/^UMASK/{gsub("#.*$", "", $2); print $2; exit}' /etc/login.defs) | ||
61 | - mode=$(printf "%o" $((0777 & ~$mask))) | ||
62 | - chmod ${mode:-700} "$homedir" | ||
63 | - [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir" | ||
64 | - fi | ||
65 | -fi | ||
66 | |||
67 | +# Executes only if the polydir path is safe | ||
68 | +if [ "$5" = 1 ]; then | ||
69 | + | ||
70 | + if [ "$3" = 1 ]; then | ||
71 | + # This line will fix the labeling on all newly created directories | ||
72 | + [ -x /sbin/restorecon ] && /sbin/restorecon "$1" | ||
73 | + user="$4" | ||
74 | + passwd=$(getent passwd "$user") | ||
75 | + homedir=$(echo "$passwd" | cut -f6 -d":") | ||
76 | + if [ "$1" = "$homedir" ]; then | ||
77 | + gid=$(echo "$passwd" | cut -f4 -d":") | ||
78 | + cp -rT /etc/skel "$homedir" | ||
79 | + chown -R "$user":"$gid" "$homedir" | ||
80 | + mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs) | ||
81 | + mode=$(printf "%o" $((0777 & ~mask))) | ||
82 | + chmod ${mode:-700} "$homedir" | ||
83 | + [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir" | ||
84 | + fi | ||
85 | + fi | ||
86 | +fi | ||
87 | exit 0 | ||
88 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
89 | index 22d8445..8cba036 100644 | ||
90 | --- a/modules/pam_namespace/pam_namespace.c | ||
91 | +++ b/modules/pam_namespace/pam_namespace.c | ||
92 | @@ -1390,6 +1390,79 @@ static int check_inst_parent(int dfd, struct instance_data *idata) | ||
93 | return PAM_SUCCESS; | ||
94 | } | ||
95 | |||
96 | +/* | ||
97 | + * Check for a given absolute path that all segments except the last one are: | ||
98 | + * 1. a directory owned by root and not writable by group or others | ||
99 | + * 2. a symlink owned by root and referencing a directory respecting 1. | ||
100 | + * Returns 0 if safe, -1 is unsafe. | ||
101 | + * If the path is not accessible (does not exist, hidden under a mount...), | ||
102 | + * returns -1 (unsafe). | ||
103 | + */ | ||
104 | +static int check_safe_path(const char *path, struct instance_data *idata) | ||
105 | +{ | ||
106 | + char *p = strdup(path); | ||
107 | + char *d; | ||
108 | + char *dir = p; | ||
109 | + struct stat st; | ||
110 | + | ||
111 | + if (p == NULL) | ||
112 | + return -1; | ||
113 | + | ||
114 | + /* Check path is absolute */ | ||
115 | + if (p[0] != '/') | ||
116 | + goto error; | ||
117 | + | ||
118 | + strip_trailing_slashes(p); | ||
119 | + | ||
120 | + /* Last segment of the path may be owned by the user */ | ||
121 | + if ((d = strrchr(dir, '/')) != NULL) | ||
122 | + *d = '\0'; | ||
123 | + | ||
124 | + while ((d=strrchr(dir, '/')) != NULL) { | ||
125 | + | ||
126 | + /* Do not follow symlinks */ | ||
127 | + if (lstat(dir, &st) != 0) | ||
128 | + goto error; | ||
129 | + | ||
130 | + if (S_ISLNK(st.st_mode)) { | ||
131 | + if (st.st_uid != 0) { | ||
132 | + if (idata->flags & PAMNS_DEBUG) | ||
133 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
134 | + "Path deemed unsafe: Symlink %s should be owned by root", dir); | ||
135 | + goto error; | ||
136 | + } | ||
137 | + | ||
138 | + /* Follow symlinks */ | ||
139 | + if (stat(dir, &st) != 0) | ||
140 | + goto error; | ||
141 | + } | ||
142 | + | ||
143 | + if (!S_ISDIR(st.st_mode)) { | ||
144 | + if (idata->flags & PAMNS_DEBUG) | ||
145 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
146 | + "Path deemed unsafe: %s is expected to be a directory", dir); | ||
147 | + goto error; | ||
148 | + } | ||
149 | + | ||
150 | + if (st.st_uid != 0 || | ||
151 | + ((st.st_mode & (S_IWGRP|S_IWOTH)) && !(st.st_mode & S_ISVTX))) { | ||
152 | + if (idata->flags & PAMNS_DEBUG) | ||
153 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
154 | + "Path deemed unsafe: %s should be owned by root, and not be writable by group or others", dir); | ||
155 | + goto error; | ||
156 | + } | ||
157 | + | ||
158 | + *d = '\0'; | ||
159 | + } | ||
160 | + | ||
161 | + free(p); | ||
162 | + return 0; | ||
163 | + | ||
164 | +error: | ||
165 | + free(p); | ||
166 | + return -1; | ||
167 | +} | ||
168 | + | ||
169 | /* | ||
170 | * Check to see if there is a namespace initialization script in | ||
171 | * the /etc/security directory. If such a script exists | ||
172 | @@ -1438,7 +1511,11 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, | ||
173 | close_fds_pre_exec(idata); | ||
174 | |||
175 | execle(init_script, init_script, | ||
176 | - polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); | ||
177 | + polyptr->dir, ipath, | ||
178 | + newdir ? "1":"0", idata->user, | ||
179 | + (check_safe_path(polyptr->dir, idata) == -1) ? "0":"1", | ||
180 | + (check_safe_path(ipath, idata) == -1) ? "0":"1", | ||
181 | + NULL, envp); | ||
182 | _exit(1); | ||
183 | } else if (pid > 0) { | ||
184 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
185 | -- | ||
186 | 2.50.1 | ||
187 | |||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch new file mode 100644 index 0000000000..2a0450092e --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch | |||
@@ -0,0 +1,35 @@ | |||
1 | From 976c20079358d133514568fc7fd95c02df8b5773 Mon Sep 17 00:00:00 2001 | ||
2 | From: "Dmitry V. Levin" <ldv@strace.io> | ||
3 | Date: Tue, 27 May 2025 08:00:00 +0000 | ||
4 | Subject: [PATCH] pam_namespace: secure_opendir: do not look at the group | ||
5 | ownership | ||
6 | |||
7 | When the directory is not group-writable, the group ownership does | ||
8 | not matter, and when it is group-writable, there should not be any | ||
9 | exceptions for the root group as there is no guarantee that the root | ||
10 | group does not include non-root users. | ||
11 | |||
12 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/976c20079358d133514568fc7fd95c02df8b5773] | ||
13 | CVE: CVE-2025-6020 | ||
14 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
15 | --- | ||
16 | modules/pam_namespace/pam_namespace.c | 3 +-- | ||
17 | 1 file changed, 1 insertion(+), 2 deletions(-) | ||
18 | |||
19 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
20 | index 8cba036..630cf0a 100644 | ||
21 | --- a/modules/pam_namespace/pam_namespace.c | ||
22 | +++ b/modules/pam_namespace/pam_namespace.c | ||
23 | @@ -215,8 +215,7 @@ static int secure_opendir(const char *path, int opm, mode_t mode, | ||
24 | if (dfd_next == -1) | ||
25 | goto error; | ||
26 | } else if (st.st_uid != 0 | ||
27 | - || (st.st_gid != 0 && (st.st_mode & S_IWGRP)) | ||
28 | - || (st.st_mode & S_IWOTH)) { | ||
29 | + || (st.st_mode & (S_IWGRP|S_IWOTH))) { | ||
30 | /* do not follow symlinks on subdirectories */ | ||
31 | flags |= O_NOFOLLOW; | ||
32 | } | ||
33 | -- | ||
34 | 2.50.1 | ||
35 | |||
diff --git a/meta/recipes-extended/pam/libpam_1.5.2.bb b/meta/recipes-extended/pam/libpam_1.5.2.bb index 567f9741cb..658212dd82 100644 --- a/meta/recipes-extended/pam/libpam_1.5.2.bb +++ b/meta/recipes-extended/pam/libpam_1.5.2.bb | |||
@@ -29,6 +29,11 @@ SRC_URI = "https://github.com/linux-pam/linux-pam/releases/download/v${PV}/Linux | |||
29 | file://CVE-2024-22365.patch \ | 29 | file://CVE-2024-22365.patch \ |
30 | file://CVE-2024-10041-1.patch \ | 30 | file://CVE-2024-10041-1.patch \ |
31 | file://CVE-2024-10041-2.patch \ | 31 | file://CVE-2024-10041-2.patch \ |
32 | file://0001-pam_namespace-include-stdint-h.patch \ | ||
33 | file://0001-pam_inline-introduce-pam_asprint.patch \ | ||
34 | file://CVE-2025-6020-01.patch \ | ||
35 | file://CVE-2025-6020-02.patch \ | ||
36 | file://CVE-2025-6020-03.patch \ | ||
32 | " | 37 | " |
33 | 38 | ||
34 | SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d" | 39 | SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d" |