summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHitendra Prajapati <hprajapati@mvista.com>2025-07-22 17:57:29 +0530
committerSteve Sakoman <steve@sakoman.com>2025-07-29 07:59:52 -0700
commitcf89d7b3bf621240876d2f0215395d8034e81fdc (patch)
tree74c20902e3007dd100ec09d3e287d81053caea84
parent99f48be958e8b141502f8fea0ef34e1c9b85cb27 (diff)
downloadpoky-cf89d7b3bf621240876d2f0215395d8034e81fdc.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: dd5bbac75b1d8f7ebd83d5c9945bd860e397ba07) Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch101
-rw-r--r--meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch750
-rw-r--r--meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch1128
-rw-r--r--meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch187
-rw-r--r--meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch35
-rw-r--r--meta/recipes-extended/pam/libpam_1.5.3.bb5
6 files changed, 2206 insertions, 0 deletions
diff --git a/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch b/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch
new file mode 100644
index 0000000000..9d1a0223df
--- /dev/null
+++ b/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch
@@ -0,0 +1,101 @@
1From 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc Mon Sep 17 00:00:00 2001
2From: "Dmitry V. Levin" <ldv@strace.io>
3Date: Tue, 18 Feb 2025 08:00:00 +0000
4Subject: [PATCH] pam_inline: introduce pam_asprintf(), pam_snprintf(), and
5 pam_sprintf()
6
7pam_asprintf() is essentially asprintf() with the following semantic
8difference: it returns the string itself instead of its length.
9
10pam_snprintf() is essentially snprintf() with the following semantic
11difference: it returns -1 in case of truncation.
12
13pam_sprintf() is essentially snprintf() but with a check that the buffer
14is an array, and with an automatically calculated buffer size.
15
16Use of these helpers would make error checking simpler.
17
18(cherry picked from commit 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc)
19Signed-off-by: Dmitry V. Levin <ldv@strace.io>
20
21Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc]
22Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
23---
24 libpam/include/pam_cc_compat.h | 6 ++++++
25 libpam/include/pam_inline.h | 36 ++++++++++++++++++++++++++++++++++
26 2 files changed, 42 insertions(+)
27
28diff --git a/libpam/include/pam_cc_compat.h b/libpam/include/pam_cc_compat.h
29index 0a6e32d..af05428 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"); \
45diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h
46index 7721c0b..ec0497c 100644
47--- a/libpam/include/pam_inline.h
48+++ b/libpam/include/pam_inline.h
49@@ -9,6 +9,8 @@
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@@ -126,6 +128,40 @@ pam_drop_response(struct pam_response *reply, int replies)
59 }
60
61
62+static inline char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC
63+pam_asprintf(const char *fmt, ...)
64+{
65+ int rc;
66+ char *res;
67+ va_list ap;
68+
69+ va_start(ap, fmt);
70+ rc = vasprintf(&res, fmt, ap);
71+ va_end(ap);
72+
73+ return rc < 0 ? NULL : res;
74+}
75+
76+static inline int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3))
77+pam_snprintf(char *str, size_t size, const char *fmt, ...)
78+{
79+ int rc;
80+ va_list ap;
81+
82+ va_start(ap, fmt);
83+ rc = vsnprintf(str, size, fmt, ap);
84+ va_end(ap);
85+
86+ if (rc < 0 || (unsigned int) rc >= size)
87+ return -1;
88+ return rc;
89+}
90+
91+#define pam_sprintf(str_, fmt_, ...) \
92+ pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \
93+ ##__VA_ARGS__)
94+
95+
96 static inline int
97 pam_read_passwords(int fd, int npass, char **passwords)
98 {
99--
1002.49.0
101
diff --git a/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch b/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch
new file mode 100644
index 0000000000..ff5a8a4946
--- /dev/null
+++ b/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch
@@ -0,0 +1,750 @@
1From df1dab1a1a7900650ad4be157fea1a002048cc49 Mon Sep 17 00:00:00 2001
2From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
3Date: Tue, 4 Mar 2025 14:37:02 +0100
4Subject: [PATCH ] pam-namespace-rebase
5
6Refresh the pam-namespace.
7
8Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/a8b4dce7b53d73de372e150028c970ee0a2a2e97]
9Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
10---
11 modules/pam_namespace/pam_namespace.c | 444 +++++++++++++-------------
12 modules/pam_namespace/pam_namespace.h | 7 +-
13 2 files changed, 224 insertions(+), 227 deletions(-)
14
15diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
16index b026861..166bfce 100644
17--- a/modules/pam_namespace/pam_namespace.c
18+++ b/modules/pam_namespace/pam_namespace.c
19@@ -41,7 +41,7 @@
20 #include "pam_namespace.h"
21 #include "argv_parse.h"
22
23-/* --- evaluting all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
24+/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
25 static const char *base_name(const char *path)
26 {
27 const char *base = strrchr(path, '/');
28@@ -55,6 +55,155 @@ compare_filename(const void *a, const void *b)
29 base_name(* (char * const *) b));
30 }
31
32+static void close_fds_pre_exec(struct instance_data *idata)
33+{
34+ if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD,
35+ PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) {
36+ _exit(1);
37+ }
38+}
39+
40+static void
41+strip_trailing_slashes(char *str)
42+{
43+ char *p = str + strlen(str);
44+
45+ while (--p > str && *p == '/')
46+ *p = '\0';
47+}
48+
49+static int protect_mount(int dfd, const char *path, struct instance_data *idata)
50+{
51+ struct protect_dir_s *dir = idata->protect_dirs;
52+ char tmpbuf[64];
53+
54+ while (dir != NULL) {
55+ if (strcmp(path, dir->dir) == 0) {
56+ return 0;
57+ }
58+ dir = dir->next;
59+ }
60+
61+ if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0)
62+ return -1;
63+
64+ dir = calloc(1, sizeof(*dir));
65+
66+ if (dir == NULL) {
67+ return -1;
68+ }
69+
70+ dir->dir = strdup(path);
71+
72+ if (dir->dir == NULL) {
73+ free(dir);
74+ return -1;
75+ }
76+
77+ if (idata->flags & PAMNS_DEBUG) {
78+ pam_syslog(idata->pamh, LOG_INFO,
79+ "Protect mount of %s over itself", path);
80+ }
81+
82+ if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) {
83+ int save_errno = errno;
84+ pam_syslog(idata->pamh, LOG_ERR,
85+ "Protect mount of %s failed: %m", tmpbuf);
86+ free(dir->dir);
87+ free(dir);
88+ errno = save_errno;
89+ return -1;
90+ }
91+
92+ dir->next = idata->protect_dirs;
93+ idata->protect_dirs = dir;
94+
95+ return 0;
96+}
97+
98+static int protect_dir(const char *path, mode_t mode, int do_mkdir,
99+ struct instance_data *idata)
100+{
101+ char *p = strdup(path);
102+ char *d;
103+ char *dir = p;
104+ int dfd = AT_FDCWD;
105+ int dfd_next;
106+ int save_errno;
107+ int flags = O_RDONLY | O_DIRECTORY;
108+ int rv = -1;
109+ struct stat st;
110+
111+ if (p == NULL) {
112+ return -1;
113+ }
114+
115+ if (*dir == '/') {
116+ dfd = open("/", flags);
117+ if (dfd == -1) {
118+ goto error;
119+ }
120+ dir++; /* assume / is safe */
121+ }
122+
123+ while ((d=strchr(dir, '/')) != NULL) {
124+ *d = '\0';
125+ dfd_next = openat(dfd, dir, flags);
126+ if (dfd_next == -1) {
127+ goto error;
128+ }
129+
130+ if (dfd != AT_FDCWD)
131+ close(dfd);
132+ dfd = dfd_next;
133+
134+ if (fstat(dfd, &st) != 0) {
135+ goto error;
136+ }
137+
138+ if (flags & O_NOFOLLOW) {
139+ /* we are inside user-owned dir - protect */
140+ if (protect_mount(dfd, p, idata) == -1)
141+ goto error;
142+ } else if (st.st_uid != 0 || st.st_gid != 0 ||
143+ (st.st_mode & S_IWOTH)) {
144+ /* do not follow symlinks on subdirectories */
145+ flags |= O_NOFOLLOW;
146+ }
147+
148+ *d = '/';
149+ dir = d + 1;
150+ }
151+
152+ rv = openat(dfd, dir, flags);
153+
154+ if (rv == -1) {
155+ if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
156+ goto error;
157+ }
158+ rv = openat(dfd, dir, flags);
159+ }
160+
161+ if (flags & O_NOFOLLOW) {
162+ /* we are inside user-owned dir - protect */
163+ if (protect_mount(rv, p, idata) == -1) {
164+ save_errno = errno;
165+ close(rv);
166+ rv = -1;
167+ errno = save_errno;
168+ }
169+ }
170+
171+error:
172+ save_errno = errno;
173+ free(p);
174+ if (dfd != AT_FDCWD && dfd >= 0)
175+ close(dfd);
176+ errno = save_errno;
177+
178+ return rv;
179+}
180+
181 /* Evaluating a list of files which have to be parsed in the right order:
182 *
183 * - If etc/security/namespace.d/@filename@.conf exists, then
184@@ -129,6 +278,7 @@ static char **read_namespace_dir(struct instance_data *idata)
185 return file_list;
186 }
187
188+
189 /*
190 * Adds an entry for a polyinstantiated directory to the linked list of
191 * polyinstantiated directories. It is called from process_line() while
192@@ -198,7 +348,7 @@ static void cleanup_protect_data(pam_handle_t *pamh UNUSED , void *data, int err
193 unprotect_dirs(data);
194 }
195
196-static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[])
197+static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[])
198 {
199 const char *src = orig;
200 char *dst;
201@@ -209,7 +359,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c
202 if (*src == '$') {
203 int i;
204 for (i = 0; var_names[i]; i++) {
205- int namelen = strlen(var_names[i]);
206+ size_t namelen = strlen(var_names[i]);
207 if (strncmp(var_names[i], src+1, namelen) == 0) {
208 dstlen += strlen(var_values[i]) - 1; /* $ */
209 src += namelen;
210@@ -227,7 +377,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c
211 if (c == '$') {
212 int i;
213 for (i = 0; var_names[i]; i++) {
214- int namelen = strlen(var_names[i]);
215+ size_t namelen = strlen(var_names[i]);
216 if (strncmp(var_names[i], src+1, namelen) == 0) {
217 dst = stpcpy(dst, var_values[i]);
218 --dst;
219@@ -311,8 +461,7 @@ static int parse_iscript_params(char *params, struct polydir_s *poly)
220
221 if (*params != '\0') {
222 if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */
223- if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1)
224- return -1;
225+ poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params);
226 } else {
227 poly->init_script = strdup(params);
228 }
229@@ -394,9 +543,9 @@ static int parse_method(char *method, struct polydir_s *poly,
230 {
231 enum polymethod pm;
232 char *sptr = NULL;
233- static const char *method_names[] = { "user", "context", "level", "tmpdir",
234+ static const char *const method_names[] = { "user", "context", "level", "tmpdir",
235 "tmpfs", NULL };
236- static const char *flag_names[] = { "create", "noinit", "iscript",
237+ static const char *const flag_names[] = { "create", "noinit", "iscript",
238 "shared", "mntopts", NULL };
239 static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT,
240 POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS };
241@@ -421,7 +570,7 @@ static int parse_method(char *method, struct polydir_s *poly,
242
243 while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) {
244 for (i = 0; flag_names[i]; i++) {
245- int namelen = strlen(flag_names[i]);
246+ size_t namelen = strlen(flag_names[i]);
247
248 if (strncmp(flag, flag_names[i], namelen) == 0) {
249 poly->flags |= flag_values[i];
250@@ -467,27 +616,27 @@ static int parse_method(char *method, struct polydir_s *poly,
251 * of the namespace configuration file. It skips over comments and incomplete
252 * or malformed lines. It processes a valid line with information on
253 * polyinstantiating a directory by populating appropriate fields of a
254- * polyinstatiated directory structure and then calling add_polydir_entry to
255+ * polyinstantiated directory structure and then calling add_polydir_entry to
256 * add that entry to the linked list of polyinstantiated directories.
257 */
258 static int process_line(char *line, const char *home, const char *rhome,
259 struct instance_data *idata)
260 {
261 char *dir = NULL, *instance_prefix = NULL, *rdir = NULL;
262+ const char *config_dir, *config_instance_prefix;
263 char *method, *uids;
264 char *tptr;
265 struct polydir_s *poly;
266 int retval = 0;
267 char **config_options = NULL;
268- static const char *var_names[] = {"HOME", "USER", NULL};
269+ static const char *const var_names[] = {"HOME", "USER", NULL};
270 const char *var_values[] = {home, idata->user};
271 const char *rvar_values[] = {rhome, idata->ruser};
272- int len;
273
274 /*
275 * skip the leading white space
276 */
277- while (*line && isspace(*line))
278+ while (*line && isspace((unsigned char)*line))
279 line++;
280
281 /*
282@@ -523,22 +672,19 @@ static int process_line(char *line, const char *home, const char *rhome,
283 goto erralloc;
284 }
285
286- dir = config_options[0];
287- if (dir == NULL) {
288+ config_dir = config_options[0];
289+ if (config_dir == NULL) {
290 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir");
291 goto skipping;
292 }
293- instance_prefix = config_options[1];
294- if (instance_prefix == NULL) {
295+ config_instance_prefix = config_options[1];
296+ if (config_instance_prefix == NULL) {
297 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix");
298- instance_prefix = NULL;
299 goto skipping;
300 }
301 method = config_options[2];
302 if (method == NULL) {
303 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method");
304- instance_prefix = NULL;
305- dir = NULL;
306 goto skipping;
307 }
308
309@@ -553,19 +699,16 @@ static int process_line(char *line, const char *home, const char *rhome,
310 /*
311 * Expand $HOME and $USER in poly dir and instance dir prefix
312 */
313- if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) {
314- instance_prefix = NULL;
315- dir = NULL;
316+ if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) {
317 goto erralloc;
318 }
319
320- if ((dir=expand_variables(dir, var_names, var_values)) == NULL) {
321- instance_prefix = NULL;
322+ if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) {
323 goto erralloc;
324 }
325
326- if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values))
327- == NULL) {
328+ if ((instance_prefix = expand_variables(config_instance_prefix,
329+ var_names, var_values)) == NULL) {
330 goto erralloc;
331 }
332
333@@ -575,15 +718,8 @@ static int process_line(char *line, const char *home, const char *rhome,
334 pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix);
335 }
336
337- len = strlen(dir);
338- if (len > 0 && dir[len-1] == '/') {
339- dir[len-1] = '\0';
340- }
341-
342- len = strlen(rdir);
343- if (len > 0 && rdir[len-1] == '/') {
344- rdir[len-1] = '\0';
345- }
346+ strip_trailing_slashes(dir);
347+ strip_trailing_slashes(rdir);
348
349 if (dir[0] == '\0' || rdir[0] == '\0') {
350 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir");
351@@ -594,26 +730,19 @@ static int process_line(char *line, const char *home, const char *rhome,
352 * Populate polyinstantiated directory structure with appropriate
353 * pathnames and the method with which to polyinstantiate.
354 */
355- if (strlen(dir) >= sizeof(poly->dir)
356- || strlen(rdir) >= sizeof(poly->rdir)
357- || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) {
358- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
359- goto skipping;
360- }
361- strcpy(poly->dir, dir);
362- strcpy(poly->rdir, rdir);
363- strcpy(poly->instance_prefix, instance_prefix);
364-
365 if (parse_method(method, poly, idata) != 0) {
366 goto skipping;
367 }
368
369- if (poly->method == TMPDIR) {
370- if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) {
371- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
372- goto skipping;
373- }
374- strcat(poly->instance_prefix, "XXXXXX");
375+#define COPY_STR(dst, src, apd) \
376+ pam_sprintf((dst), "%s%s", (src), (apd))
377+
378+ if (COPY_STR(poly->dir, dir, "") < 0
379+ || COPY_STR(poly->rdir, rdir, "") < 0
380+ || COPY_STR(poly->instance_prefix, instance_prefix,
381+ poly->method == TMPDIR ? "XXXXXX" : "") < 0) {
382+ pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
383+ goto skipping;
384 }
385
386 /*
387@@ -637,7 +766,7 @@ static int process_line(char *line, const char *home, const char *rhome,
388 if (uids) {
389 uid_t *uidptr;
390 const char *ustr, *sstr;
391- int count, i;
392+ size_t count, i;
393
394 if (*uids == '~') {
395 poly->flags |= POLYDIR_EXCLUSIVE;
396@@ -646,8 +775,13 @@ static int process_line(char *line, const char *home, const char *rhome,
397 for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++)
398 sstr = strchr(ustr, ',');
399
400+ if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) {
401+ pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration");
402+ goto skipping;
403+ }
404+
405 poly->num_uids = count;
406- poly->uid = (uid_t *) malloc(count * sizeof (uid_t));
407+ poly->uid = malloc(count * sizeof (uid_t));
408 uidptr = poly->uid;
409 if (uidptr == NULL) {
410 goto erralloc;
411@@ -996,6 +1130,7 @@ static int form_context(const struct polydir_s *polyptr,
412 return rc;
413 }
414 /* Should never get here */
415+ freecon(scon);
416 return PAM_SUCCESS;
417 }
418 #endif
419@@ -1057,10 +1192,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
420
421 switch (pm) {
422 case USER:
423- if (asprintf(i_name, "%s", idata->user) < 0) {
424- *i_name = NULL;
425+ if ((*i_name = strdup(idata->user)) == NULL)
426 goto fail;
427- }
428 break;
429
430 #ifdef WITH_SELINUX
431@@ -1070,17 +1203,12 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
432 pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context");
433 goto fail;
434 }
435- if (polyptr->flags & POLYDIR_SHARED) {
436- if (asprintf(i_name, "%s", rawcon) < 0) {
437- *i_name = NULL;
438- goto fail;
439- }
440- } else {
441- if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) {
442- *i_name = NULL;
443- goto fail;
444- }
445- }
446+ if (polyptr->flags & POLYDIR_SHARED)
447+ *i_name = strdup(rawcon);
448+ else
449+ *i_name = pam_asprintf("%s_%s", rawcon, idata->user);
450+ if (*i_name == NULL)
451+ goto fail;
452 break;
453
454 #endif /* WITH_SELINUX */
455@@ -1110,11 +1238,12 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
456 *i_name = hash;
457 hash = NULL;
458 } else {
459- char *newname;
460- if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash),
461- *i_name, hash) < 0) {
462+ char *newname =
463+ pam_asprintf("%.*s_%s",
464+ NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash),
465+ *i_name, hash);
466+ if (newname == NULL)
467 goto fail;
468- }
469 free(*i_name);
470 *i_name = newname;
471 }
472@@ -1139,137 +1268,6 @@ fail:
473 return rc;
474 }
475
476-static int protect_mount(int dfd, const char *path, struct instance_data *idata)
477-{
478- struct protect_dir_s *dir = idata->protect_dirs;
479- char tmpbuf[64];
480-
481- while (dir != NULL) {
482- if (strcmp(path, dir->dir) == 0) {
483- return 0;
484- }
485- dir = dir->next;
486- }
487-
488- dir = calloc(1, sizeof(*dir));
489-
490- if (dir == NULL) {
491- return -1;
492- }
493-
494- dir->dir = strdup(path);
495-
496- if (dir->dir == NULL) {
497- free(dir);
498- return -1;
499- }
500-
501- snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd);
502-
503- if (idata->flags & PAMNS_DEBUG) {
504- pam_syslog(idata->pamh, LOG_INFO,
505- "Protect mount of %s over itself", path);
506- }
507-
508- if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) {
509- int save_errno = errno;
510- pam_syslog(idata->pamh, LOG_ERR,
511- "Protect mount of %s failed: %m", tmpbuf);
512- free(dir->dir);
513- free(dir);
514- errno = save_errno;
515- return -1;
516- }
517-
518- dir->next = idata->protect_dirs;
519- idata->protect_dirs = dir;
520-
521- return 0;
522-}
523-
524-static int protect_dir(const char *path, mode_t mode, int do_mkdir,
525- struct instance_data *idata)
526-{
527- char *p = strdup(path);
528- char *d;
529- char *dir = p;
530- int dfd = AT_FDCWD;
531- int dfd_next;
532- int save_errno;
533- int flags = O_RDONLY | O_DIRECTORY;
534- int rv = -1;
535- struct stat st;
536-
537- if (p == NULL) {
538- goto error;
539- }
540-
541- if (*dir == '/') {
542- dfd = open("/", flags);
543- if (dfd == -1) {
544- goto error;
545- }
546- dir++; /* assume / is safe */
547- }
548-
549- while ((d=strchr(dir, '/')) != NULL) {
550- *d = '\0';
551- dfd_next = openat(dfd, dir, flags);
552- if (dfd_next == -1) {
553- goto error;
554- }
555-
556- if (dfd != AT_FDCWD)
557- close(dfd);
558- dfd = dfd_next;
559-
560- if (fstat(dfd, &st) != 0) {
561- goto error;
562- }
563-
564- if (flags & O_NOFOLLOW) {
565- /* we are inside user-owned dir - protect */
566- if (protect_mount(dfd, p, idata) == -1)
567- goto error;
568- } else if (st.st_uid != 0 || st.st_gid != 0 ||
569- (st.st_mode & S_IWOTH)) {
570- /* do not follow symlinks on subdirectories */
571- flags |= O_NOFOLLOW;
572- }
573-
574- *d = '/';
575- dir = d + 1;
576- }
577-
578- rv = openat(dfd, dir, flags);
579-
580- if (rv == -1) {
581- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
582- goto error;
583- }
584- rv = openat(dfd, dir, flags);
585- }
586-
587- if (flags & O_NOFOLLOW) {
588- /* we are inside user-owned dir - protect */
589- if (protect_mount(rv, p, idata) == -1) {
590- save_errno = errno;
591- close(rv);
592- rv = -1;
593- errno = save_errno;
594- }
595- }
596-
597-error:
598- save_errno = errno;
599- free(p);
600- if (dfd != AT_FDCWD && dfd >= 0)
601- close(dfd);
602- errno = save_errno;
603-
604- return rv;
605-}
606-
607 static int check_inst_parent(char *ipath, struct instance_data *idata)
608 {
609 struct stat instpbuf;
610@@ -1281,13 +1279,12 @@ static int check_inst_parent(char *ipath, struct instance_data *idata)
611 * admin explicitly instructs to ignore the instance parent
612 * mode by the "ignore_instance_parent_mode" argument).
613 */
614- inst_parent = (char *) malloc(strlen(ipath)+1);
615+ inst_parent = strdup(ipath);
616 if (!inst_parent) {
617 pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string");
618 return PAM_SESSION_ERR;
619 }
620
621- strcpy(inst_parent, ipath);
622 trailing_slash = strrchr(inst_parent, '/');
623 if (trailing_slash)
624 *trailing_slash = '\0';
625@@ -1371,9 +1368,10 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath,
626 if (setuid(geteuid()) < 0) {
627 /* ignore failures, they don't matter */
628 }
629+ close_fds_pre_exec(idata);
630
631- if (execle(init_script, init_script,
632- polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0)
633+ execle(init_script, init_script,
634+ polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp);
635 _exit(1);
636 } else if (pid > 0) {
637 while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
638@@ -1424,7 +1422,9 @@ static int create_polydir(struct polydir_s *polyptr,
639
640 #ifdef WITH_SELINUX
641 if (idata->flags & PAMNS_SELINUX_ENABLED) {
642- getfscreatecon_raw(&oldcon_raw);
643+ if (getfscreatecon_raw(&oldcon_raw) != 0)
644+ pam_syslog(idata->pamh, LOG_NOTICE,
645+ "Error retrieving fs create context: %m");
646
647 label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
648 if (!label_handle) {
649@@ -1453,6 +1453,9 @@ static int create_polydir(struct polydir_s *polyptr,
650 if (rc == -1) {
651 pam_syslog(idata->pamh, LOG_ERR,
652 "Error creating directory %s: %m", dir);
653+#ifdef WITH_SELINUX
654+ freecon(oldcon_raw);
655+#endif
656 return PAM_SESSION_ERR;
657 }
658
659@@ -1640,16 +1643,14 @@ static int ns_setup(struct polydir_s *polyptr,
660
661 retval = protect_dir(polyptr->dir, 0, 0, idata);
662
663- if (retval < 0 && errno != ENOENT) {
664- pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
665- polyptr->dir);
666- return PAM_SESSION_ERR;
667- }
668-
669 if (retval < 0) {
670- if ((polyptr->flags & POLYDIR_CREATE) &&
671- create_polydir(polyptr, idata) != PAM_SUCCESS)
672- return PAM_SESSION_ERR;
673+ if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) {
674+ pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
675+ polyptr->dir);
676+ return PAM_SESSION_ERR;
677+ }
678+ if (create_polydir(polyptr, idata) != PAM_SUCCESS)
679+ return PAM_SESSION_ERR;
680 } else {
681 close(retval);
682 }
683@@ -1698,7 +1699,7 @@ static int ns_setup(struct polydir_s *polyptr,
684 #endif
685 }
686
687- if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0)
688+ if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL)
689 goto error_out;
690
691 if (idata->flags & PAMNS_DEBUG)
692@@ -1810,8 +1811,9 @@ static int cleanup_tmpdirs(struct instance_data *idata)
693 _exit(1);
694 }
695 #endif
696- if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0)
697- _exit(1);
698+ close_fds_pre_exec(idata);
699+ execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp);
700+ _exit(1);
701 } else if (pid > 0) {
702 while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
703 (errno == EINTR));
704@@ -1826,7 +1828,7 @@ static int cleanup_tmpdirs(struct instance_data *idata)
705 }
706 } else if (pid < 0) {
707 pam_syslog(idata->pamh, LOG_ERR,
708- "Cannot fork to run namespace init script, %m");
709+ "Cannot fork to cleanup temporary directory, %m");
710 rc = PAM_SESSION_ERR;
711 goto out;
712 }
713diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h
714index a991b4c..180e042 100644
715--- a/modules/pam_namespace/pam_namespace.h
716+++ b/modules/pam_namespace/pam_namespace.h
717@@ -44,21 +44,16 @@
718 #include <stdlib.h>
719 #include <errno.h>
720 #include <syslog.h>
721-#include <dlfcn.h>
722-#include <stdarg.h>
723 #include <pwd.h>
724 #include <grp.h>
725 #include <limits.h>
726 #include <sys/types.h>
727 #include <sys/stat.h>
728-#include <sys/resource.h>
729 #include <sys/mount.h>
730 #include <sys/wait.h>
731-#include <libgen.h>
732 #include <fcntl.h>
733 #include <sched.h>
734 #include <glob.h>
735-#include <locale.h>
736 #include "security/pam_modules.h"
737 #include "security/pam_modutil.h"
738 #include "security/pam_ext.h"
739@@ -114,7 +109,7 @@
740 #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */
741
742 /* polydir flags */
743-#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */
744+#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */
745 #define POLYDIR_CREATE 0x00000002 /* create the polydir */
746 #define POLYDIR_NOINIT 0x00000004 /* no init script */
747 #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */
748--
7492.49.0
750
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..ff0331aa38
--- /dev/null
+++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch
@@ -0,0 +1,1128 @@
1From 475bd60c552b98c7eddb3270b0b4196847c0072e Mon Sep 17 00:00:00 2001
2From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
3Date: Tue, 4 Mar 2025 14:37:02 +0100
4Subject: [PATCH] pam_namespace: fix potential privilege escalation
5
6Existing protection provided by protect_dir() and protect_mount() were
7bind mounting on themselves all directories part of the to-be-secured
8paths. However, this works *only* against attacks executed by processes
9in the same mount namespace as the one the mountpoint was created in.
10Therefore, a user with an out-of-mount-namespace access, or multiple
11users colluding, could exploit multiple race conditions, and, for
12instance, elevate their privileges to root.
13
14This commit keeps the existing protection as a defense in depth
15measure, and to keep the existing behavior of the module. However,
16it converts all the needed function calls to operate on file
17descriptors instead of absolute paths to protect against race
18conditions globally.
19
20Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
21Signed-off-by: Dmitry V. Levin <ldv@strace.io>
22
23Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e]
24CVE: CVE-2025-6020
25Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
26---
27 modules/pam_namespace/pam_namespace.c | 637 ++++++++++++++++++--------
28 modules/pam_namespace/pam_namespace.h | 10 +
29 2 files changed, 457 insertions(+), 190 deletions(-)
30
31diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
32index 166bfce..9d993d4 100644
33--- a/modules/pam_namespace/pam_namespace.c
34+++ b/modules/pam_namespace/pam_namespace.c
35@@ -41,6 +41,8 @@
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@@ -75,7 +77,7 @@ strip_trailing_slashes(char *str)
45 static int protect_mount(int dfd, const char *path, struct instance_data *idata)
46 {
47 struct protect_dir_s *dir = idata->protect_dirs;
48- char tmpbuf[64];
49+ char tmpbuf[MAGIC_LNK_FD_SIZE];
50
51 while (dir != NULL) {
52 if (strcmp(path, dir->dir) == 0) {
53@@ -121,56 +123,107 @@ static int protect_mount(int dfd, const char *path, struct instance_data *idata)
54 return 0;
55 }
56
57-static int protect_dir(const char *path, mode_t mode, int do_mkdir,
58+/*
59+ * Returns a fd to the given absolute path, acquired securely. This means:
60+ * - iterating on each segment of the path,
61+ * - not following user symlinks,
62+ * - using race-free operations.
63+ *
64+ * Takes a bit mask to specify the operation mode:
65+ * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path
66+ * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist
67+ * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH,
68+ * allowing more operations to be done with the returned fd
69+ *
70+ * Be aware that using SECURE_OPENDIR_PROTECT:
71+ * - will modify some external state (global structure...) and should not be
72+ * called in cleanup code paths. See wrapper secure_opendir_stateless()
73+ * - need a non-NULL idata to call protect_mount()
74+ */
75+static int secure_opendir(const char *path, int opm, mode_t mode,
76 struct instance_data *idata)
77 {
78- char *p = strdup(path);
79+ char *p;
80 char *d;
81- char *dir = p;
82- int dfd = AT_FDCWD;
83+ char *dir;
84+ int dfd = -1;
85 int dfd_next;
86 int save_errno;
87- int flags = O_RDONLY | O_DIRECTORY;
88+ int flags = O_DIRECTORY | O_CLOEXEC;
89 int rv = -1;
90 struct stat st;
91
92- if (p == NULL) {
93+ if (opm & SECURE_OPENDIR_FULL_FD)
94+ flags |= O_RDONLY;
95+ else
96+ flags |= O_PATH;
97+
98+ /* Check for args consistency */
99+ if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL)
100 return -1;
101- }
102
103- if (*dir == '/') {
104- dfd = open("/", flags);
105- if (dfd == -1) {
106- goto error;
107- }
108- dir++; /* assume / is safe */
109+ /* Accept only absolute paths */
110+ if (*path != '/')
111+ return -1;
112+
113+ dir = p = strdup(path);
114+ if (p == NULL)
115+ return -1;
116+
117+ /* Assume '/' is safe */
118+ dfd = open("/", flags);
119+ if (dfd == -1)
120+ goto error;
121+
122+ /* Needed to not loop too far and call openat() on NULL */
123+ strip_trailing_slashes(p);
124+
125+ dir++;
126+
127+ /* In case path is '/' */
128+ if (*dir == '\0') {
129+ free(p);
130+ return dfd;
131 }
132
133 while ((d=strchr(dir, '/')) != NULL) {
134 *d = '\0';
135+
136 dfd_next = openat(dfd, dir, flags);
137- if (dfd_next == -1) {
138+ if (dfd_next == -1)
139 goto error;
140- }
141-
142- if (dfd != AT_FDCWD)
143- close(dfd);
144- dfd = dfd_next;
145
146- if (fstat(dfd, &st) != 0) {
147+ if (fstat(dfd_next, &st) != 0) {
148+ close(dfd_next);
149 goto error;
150 }
151
152- if (flags & O_NOFOLLOW) {
153+ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
154 /* we are inside user-owned dir - protect */
155- if (protect_mount(dfd, p, idata) == -1)
156+ if (protect_mount(dfd_next, p, idata) == -1) {
157+ close(dfd_next);
158+ goto error;
159+ }
160+ /*
161+ * Reopen the directory to obtain a new descriptor
162+ * after protect_mount(), this is necessary in cases
163+ * when another directory is going to be mounted over
164+ * the given path.
165+ */
166+ close(dfd_next);
167+ dfd_next = openat(dfd, dir, flags);
168+ if (dfd_next == -1)
169 goto error;
170- } else if (st.st_uid != 0 || st.st_gid != 0 ||
171- (st.st_mode & S_IWOTH)) {
172+ } else if (st.st_uid != 0
173+ || (st.st_gid != 0 && (st.st_mode & S_IWGRP))
174+ || (st.st_mode & S_IWOTH)) {
175 /* do not follow symlinks on subdirectories */
176 flags |= O_NOFOLLOW;
177 }
178
179+ close(dfd);
180+ dfd = dfd_next;
181+
182 *d = '/';
183 dir = d + 1;
184 }
185@@ -178,13 +231,14 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir,
186 rv = openat(dfd, dir, flags);
187
188 if (rv == -1) {
189- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
190+ if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0)
191+ rv = openat(dfd, dir, flags);
192+
193+ if (rv == -1)
194 goto error;
195- }
196- rv = openat(dfd, dir, flags);
197 }
198
199- if (flags & O_NOFOLLOW) {
200+ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
201 /* we are inside user-owned dir - protect */
202 if (protect_mount(rv, p, idata) == -1) {
203 save_errno = errno;
204@@ -192,18 +246,95 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir,
205 rv = -1;
206 errno = save_errno;
207 }
208+ /*
209+ * Reopen the directory to obtain a new descriptor after
210+ * protect_mount(), this is necessary in cases when another
211+ * directory is going to be mounted over the given path.
212+ */
213+ close(rv);
214+ rv = openat(dfd, dir, flags);
215 }
216
217 error:
218 save_errno = errno;
219 free(p);
220- if (dfd != AT_FDCWD && dfd >= 0)
221+ if (dfd >= 0)
222 close(dfd);
223 errno = save_errno;
224
225 return rv;
226 }
227
228+/*
229+ * Returns a fd to the given path, acquired securely.
230+ * It can be called in all situations, including in cleanup code paths, as
231+ * it does not modify external state (no access to global structures...).
232+ */
233+static int secure_opendir_stateless(const char *path)
234+{
235+ return secure_opendir(path, 0, 0, NULL);
236+}
237+
238+/*
239+ * Umount securely the given path, even if the directories along
240+ * the path are under user control. It should protect against
241+ * symlinks attacks and race conditions.
242+ */
243+static int secure_umount(const char *path)
244+{
245+ int save_errno;
246+ int rv = -1;
247+ int dfd = -1;
248+ char s_path[MAGIC_LNK_FD_SIZE];
249+
250+ dfd = secure_opendir_stateless(path);
251+ if (dfd == -1)
252+ return rv;
253+
254+ if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0)
255+ goto error;
256+
257+ /*
258+ * We still have a fd open to path itself,
259+ * so we need to do a lazy umount.
260+ */
261+ rv = umount2(s_path, MNT_DETACH);
262+
263+error:
264+ save_errno = errno;
265+ close(dfd);
266+ errno = save_errno;
267+ return rv;
268+}
269+
270+/*
271+ * Rmdir the given path securely, protecting against symlinks attacks
272+ * and race conditions.
273+ * This function is currently called only in cleanup code paths where
274+ * any errors returned are not handled, so do not handle them either.
275+ * Basically, try to rmdir the path on a best-effort basis.
276+ */
277+static void secure_try_rmdir(const char *path)
278+{
279+ int dfd;
280+ char *buf;
281+ char *parent;
282+
283+ buf = strdup(path);
284+ if (buf == NULL)
285+ return;
286+
287+ parent = dirname(buf);
288+
289+ dfd = secure_opendir_stateless(parent);
290+ if (dfd >= 0) {
291+ unlinkat(dfd, base_name(path), AT_REMOVEDIR);
292+ close(dfd);
293+ }
294+
295+ free(buf);
296+}
297+
298 /* Evaluating a list of files which have to be parsed in the right order:
299 *
300 * - If etc/security/namespace.d/@filename@.conf exists, then
301@@ -330,7 +461,7 @@ static void unprotect_dirs(struct protect_dir_s *dir)
302 struct protect_dir_s *next;
303
304 while (dir != NULL) {
305- umount(dir->dir);
306+ secure_umount(dir->dir);
307 free(dir->dir);
308 next = dir->next;
309 free(dir);
310@@ -734,13 +865,9 @@ static int process_line(char *line, const char *home, const char *rhome,
311 goto skipping;
312 }
313
314-#define COPY_STR(dst, src, apd) \
315- pam_sprintf((dst), "%s%s", (src), (apd))
316-
317- if (COPY_STR(poly->dir, dir, "") < 0
318- || COPY_STR(poly->rdir, rdir, "") < 0
319- || COPY_STR(poly->instance_prefix, instance_prefix,
320- poly->method == TMPDIR ? "XXXXXX" : "") < 0) {
321+ if (pam_sprintf(poly->dir, "%s", dir) < 0
322+ || pam_sprintf(poly->rdir, "%s", rdir) < 0
323+ || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) {
324 pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
325 goto skipping;
326 }
327@@ -1023,6 +1150,23 @@ static char *md5hash(const char *instname, struct instance_data *idata)
328 }
329
330 #ifdef WITH_SELINUX
331+static char *secure_getfilecon(pam_handle_t *pamh, const char *dir)
332+{
333+ char *ctx = NULL;
334+ int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL);
335+ if (dfd < 0) {
336+ pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir);
337+ return NULL;
338+ }
339+ if (fgetfilecon(dfd, &ctx) < 0)
340+ ctx = NULL;
341+ if (ctx == NULL)
342+ pam_syslog(pamh, LOG_ERR,
343+ "Error getting poly dir context for %s: %m", dir);
344+ close(dfd);
345+ return ctx;
346+}
347+
348 static int form_context(const struct polydir_s *polyptr,
349 char **i_context, char **origcon,
350 struct instance_data *idata)
351@@ -1034,12 +1178,9 @@ static int form_context(const struct polydir_s *polyptr,
352 /*
353 * Get the security context of the directory to polyinstantiate.
354 */
355- rc = getfilecon(polyptr->dir, origcon);
356- if (rc < 0 || *origcon == NULL) {
357- pam_syslog(idata->pamh, LOG_ERR,
358- "Error getting poly dir context, %m");
359+ *origcon = secure_getfilecon(idata->pamh, polyptr->dir);
360+ if (*origcon == NULL)
361 return PAM_SESSION_ERR;
362- }
363
364 if (polyptr->method == USER) return PAM_SUCCESS;
365
366@@ -1136,29 +1277,52 @@ static int form_context(const struct polydir_s *polyptr,
367 #endif
368
369 /*
370- * poly_name returns the name of the polyinstantiated instance directory
371+ * From the instance differentiation string, set in the polyptr structure:
372+ * - the absolute path to the instance dir,
373+ * - the absolute path to the previous dir (parent),
374+ * - the instance name (may be different than the instance differentiation string)
375+ */
376+static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation)
377+{
378+ char *tmp;
379+
380+ if (pam_sprintf(polyptr->instance_absolute, "%s%s",
381+ polyptr->instance_prefix, inst_differentiation) < 0)
382+ return -1;
383+
384+ polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1;
385+
386+ if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0)
387+ return -1;
388+
389+ tmp = strrchr(polyptr->instance_parent, '/') + 1;
390+ *tmp = '\0';
391+
392+ return 0;
393+}
394+
395+/*
396+ * Set the name of the polyinstantiated instance directory
397 * based on the method used for polyinstantiation (user, context or level)
398 * In addition, the function also returns the security contexts of the
399 * original directory to polyinstantiate and the polyinstantiated instance
400 * directory.
401 */
402 #ifdef WITH_SELINUX
403-static int poly_name(const struct polydir_s *polyptr, char **i_name,
404- char **i_context, char **origcon,
405- struct instance_data *idata)
406+static int poly_name(struct polydir_s *polyptr, char **i_context,
407+ char **origcon, struct instance_data *idata)
408 #else
409-static int poly_name(const struct polydir_s *polyptr, char **i_name,
410- struct instance_data *idata)
411+static int poly_name(struct polydir_s *polyptr, struct instance_data *idata)
412 #endif
413 {
414 int rc;
415+ char *inst_differentiation = NULL;
416 char *hash = NULL;
417 enum polymethod pm;
418 #ifdef WITH_SELINUX
419 char *rawcon = NULL;
420 #endif
421
422- *i_name = NULL;
423 #ifdef WITH_SELINUX
424 *i_context = NULL;
425 *origcon = NULL;
426@@ -1192,7 +1356,7 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
427
428 switch (pm) {
429 case USER:
430- if ((*i_name = strdup(idata->user)) == NULL)
431+ if ((inst_differentiation = strdup(idata->user)) == NULL)
432 goto fail;
433 break;
434
435@@ -1204,20 +1368,24 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
436 goto fail;
437 }
438 if (polyptr->flags & POLYDIR_SHARED)
439- *i_name = strdup(rawcon);
440+ inst_differentiation = strdup(rawcon);
441 else
442- *i_name = pam_asprintf("%s_%s", rawcon, idata->user);
443- if (*i_name == NULL)
444+ inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user);
445+ if (inst_differentiation == NULL)
446 goto fail;
447 break;
448
449 #endif /* WITH_SELINUX */
450
451 case TMPDIR:
452+ if ((inst_differentiation = strdup("XXXXXX")) == NULL)
453+ goto fail;
454+ goto success;
455+
456 case TMPFS:
457- if ((*i_name=strdup("")) == NULL)
458+ if ((inst_differentiation=strdup("")) == NULL)
459 goto fail;
460- return PAM_SUCCESS;
461+ goto success;
462
463 default:
464 if (idata->flags & PAMNS_DEBUG)
465@@ -1226,32 +1394,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
466 }
467
468 if (idata->flags & PAMNS_DEBUG)
469- pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name);
470+ pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation);
471
472- if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) {
473- hash = md5hash(*i_name, idata);
474+ if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) {
475+ hash = md5hash(inst_differentiation, idata);
476 if (hash == NULL) {
477 goto fail;
478 }
479 if (idata->flags & PAMNS_GEN_HASH) {
480- free(*i_name);
481- *i_name = hash;
482+ free(inst_differentiation);
483+ inst_differentiation = hash;
484 hash = NULL;
485 } else {
486 char *newname =
487 pam_asprintf("%.*s_%s",
488 NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash),
489- *i_name, hash);
490+ inst_differentiation, hash);
491 if (newname == NULL)
492 goto fail;
493- free(*i_name);
494- *i_name = newname;
495+ free(inst_differentiation);
496+ inst_differentiation = newname;
497 }
498 }
499- rc = PAM_SUCCESS;
500
501+success:
502+ if (set_polydir_paths(polyptr, inst_differentiation) == -1)
503+ goto fail;
504+
505+ rc = PAM_SUCCESS;
506 fail:
507 free(hash);
508+ free(inst_differentiation);
509 #ifdef WITH_SELINUX
510 freecon(rawcon);
511 #endif
512@@ -1262,55 +1435,35 @@ fail:
513 freecon(*origcon);
514 *origcon = NULL;
515 #endif
516- free(*i_name);
517- *i_name = NULL;
518 }
519 return rc;
520 }
521
522-static int check_inst_parent(char *ipath, struct instance_data *idata)
523+static int check_inst_parent(int dfd, struct instance_data *idata)
524 {
525 struct stat instpbuf;
526- char *inst_parent, *trailing_slash;
527- int dfd;
528+
529 /*
530- * stat the instance parent path to make sure it exists
531- * and is a directory. Check that its mode is 000 (unless the
532- * admin explicitly instructs to ignore the instance parent
533- * mode by the "ignore_instance_parent_mode" argument).
534+ * Stat the instance parent directory to make sure it's writable by
535+ * root only (unless the admin explicitly instructs to ignore the
536+ * instance parent mode by the "ignore_instance_parent_mode" argument).
537 */
538- inst_parent = strdup(ipath);
539- if (!inst_parent) {
540- pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string");
541- return PAM_SESSION_ERR;
542- }
543
544- trailing_slash = strrchr(inst_parent, '/');
545- if (trailing_slash)
546- *trailing_slash = '\0';
547-
548- dfd = protect_dir(inst_parent, 0, 1, idata);
549+ if (idata->flags & PAMNS_IGN_INST_PARENT_MODE)
550+ return PAM_SUCCESS;
551
552- if (dfd == -1 || fstat(dfd, &instpbuf) < 0) {
553+ if (fstat(dfd, &instpbuf) < 0) {
554 pam_syslog(idata->pamh, LOG_ERR,
555- "Error creating or accessing instance parent %s, %m", inst_parent);
556- if (dfd != -1)
557- close(dfd);
558- free(inst_parent);
559+ "Error accessing instance parent, %m");
560 return PAM_SESSION_ERR;
561 }
562
563- if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) {
564- if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
565- pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root",
566- inst_parent);
567- close(dfd);
568- free(inst_parent);
569- return PAM_SESSION_ERR;
570- }
571+ if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
572+ pam_syslog(idata->pamh, LOG_ERR,
573+ "Mode of inst parent not 000 or owner not root");
574+ return PAM_SESSION_ERR;
575 }
576- close(dfd);
577- free(inst_parent);
578+
579 return PAM_SUCCESS;
580 }
581
582@@ -1449,14 +1602,16 @@ static int create_polydir(struct polydir_s *polyptr,
583 }
584 #endif
585
586- rc = protect_dir(dir, mode, 1, idata);
587+ rc = secure_opendir(dir,
588+ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD,
589+ mode, idata);
590 if (rc == -1) {
591 pam_syslog(idata->pamh, LOG_ERR,
592 "Error creating directory %s: %m", dir);
593 #ifdef WITH_SELINUX
594 freecon(oldcon_raw);
595 #endif
596- return PAM_SESSION_ERR;
597+ return -1;
598 }
599
600 #ifdef WITH_SELINUX
601@@ -1477,9 +1632,9 @@ static int create_polydir(struct polydir_s *polyptr,
602 pam_syslog(idata->pamh, LOG_ERR,
603 "Error changing mode of directory %s: %m", dir);
604 close(rc);
605- umount(dir); /* undo the eventual protection bind mount */
606- rmdir(dir);
607- return PAM_SESSION_ERR;
608+ secure_umount(dir); /* undo the eventual protection bind mount */
609+ secure_try_rmdir(dir);
610+ return -1;
611 }
612 }
613
614@@ -1497,41 +1652,37 @@ static int create_polydir(struct polydir_s *polyptr,
615 pam_syslog(idata->pamh, LOG_ERR,
616 "Unable to change owner on directory %s: %m", dir);
617 close(rc);
618- umount(dir); /* undo the eventual protection bind mount */
619- rmdir(dir);
620- return PAM_SESSION_ERR;
621+ secure_umount(dir); /* undo the eventual protection bind mount */
622+ secure_try_rmdir(dir);
623+ return -1;
624 }
625
626- close(rc);
627-
628 if (idata->flags & PAMNS_DEBUG)
629 pam_syslog(idata->pamh, LOG_DEBUG,
630 "Polydir owner %u group %u", uid, gid);
631
632- return PAM_SUCCESS;
633+ return rc;
634 }
635
636 /*
637- * Create polyinstantiated instance directory (ipath).
638+ * Create polyinstantiated instance directory.
639+ * To protect against races, changes are done on a fd to the parent of the
640+ * instance directory (dfd_iparent) and a relative path (polyptr->instname).
641+ * The absolute path (polyptr->instance_absolute) is only updated when creating
642+ * a tmpdir and used for logging purposes.
643 */
644 #ifdef WITH_SELINUX
645-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
646- const char *icontext, const char *ocontext,
647- struct instance_data *idata)
648+static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
649+ struct stat *statbuf, const char *icontext, const char *ocontext,
650+ struct instance_data *idata)
651 #else
652-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
653- struct instance_data *idata)
654+static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
655+ struct stat *statbuf, struct instance_data *idata)
656 #endif
657 {
658 struct stat newstatbuf;
659 int fd;
660
661- /*
662- * Check to make sure instance parent is valid.
663- */
664- if (check_inst_parent(ipath, idata))
665- return PAM_SESSION_ERR;
666-
667 /*
668 * Create instance directory and set its security context to the context
669 * returned by the security policy. Set its mode and ownership
670@@ -1540,29 +1691,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
671 */
672
673 if (polyptr->method == TMPDIR) {
674- if (mkdtemp(polyptr->instance_prefix) == NULL) {
675- pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m",
676- polyptr->instance_prefix);
677- polyptr->method = NONE; /* do not clean up! */
678- return PAM_SESSION_ERR;
679- }
680- /* copy the actual directory name to ipath */
681- strcpy(ipath, polyptr->instance_prefix);
682- } else if (mkdir(ipath, S_IRUSR) < 0) {
683+ char s_path[PATH_MAX];
684+ /*
685+ * Create the template for mkdtemp() as a magic link based on
686+ * our existing fd to avoid symlink attacks and races.
687+ */
688+ if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0
689+ || mkdtemp(s_path) == NULL) {
690+ pam_syslog(idata->pamh, LOG_ERR,
691+ "Error creating temporary instance dir %s, %m",
692+ polyptr->instance_absolute);
693+ polyptr->method = NONE; /* do not clean up! */
694+ return PAM_SESSION_ERR;
695+ }
696+
697+ /* Copy the actual directory name to polyptr->instname */
698+ strcpy(polyptr->instname, base_name(s_path));
699+ } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) {
700 if (errno == EEXIST)
701 return PAM_IGNORE;
702 else {
703 pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m",
704- ipath);
705+ polyptr->instance_absolute);
706 return PAM_SESSION_ERR;
707 }
708 }
709
710- /* Open a descriptor to it to prevent races */
711- fd = open(ipath, O_DIRECTORY | O_RDONLY);
712+ /* Open a descriptor to prevent races, based on our existing fd. */
713+ fd = openat(dfd_iparent, polyptr->instname,
714+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
715 if (fd < 0) {
716- pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath);
717- rmdir(ipath);
718+ pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m",
719+ polyptr->instance_absolute);
720+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
721 return PAM_SESSION_ERR;
722 }
723 #ifdef WITH_SELINUX
724@@ -1572,17 +1733,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
725 if (icontext) {
726 if (fsetfilecon(fd, icontext) < 0) {
727 pam_syslog(idata->pamh, LOG_ERR,
728- "Error setting context of %s to %s", ipath, icontext);
729+ "Error setting context of %s to %s",
730+ polyptr->instance_absolute, icontext);
731 close(fd);
732- rmdir(ipath);
733+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
734 return PAM_SESSION_ERR;
735 }
736 } else {
737 if (fsetfilecon(fd, ocontext) < 0) {
738 pam_syslog(idata->pamh, LOG_ERR,
739- "Error setting context of %s to %s", ipath, ocontext);
740+ "Error setting context of %s to %s",
741+ polyptr->instance_absolute, ocontext);
742 close(fd);
743- rmdir(ipath);
744+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
745 return PAM_SESSION_ERR;
746 }
747 }
748@@ -1590,9 +1753,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
749 #endif
750 if (fstat(fd, &newstatbuf) < 0) {
751 pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m",
752- ipath);
753+ polyptr->instance_absolute);
754 close(fd);
755- rmdir(ipath);
756+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
757 return PAM_SESSION_ERR;
758 }
759 if (newstatbuf.st_uid != statbuf->st_uid ||
760@@ -1600,17 +1763,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
761 if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) {
762 pam_syslog(idata->pamh, LOG_ERR,
763 "Error changing owner for %s, %m",
764- ipath);
765+ polyptr->instance_absolute);
766 close(fd);
767- rmdir(ipath);
768+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
769 return PAM_SESSION_ERR;
770 }
771 }
772 if (fchmod(fd, statbuf->st_mode & 07777) < 0) {
773 pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m",
774- ipath);
775+ polyptr->instance_absolute);
776 close(fd);
777- rmdir(ipath);
778+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
779 return PAM_SESSION_ERR;
780 }
781 close(fd);
782@@ -1629,9 +1792,12 @@ static int ns_setup(struct polydir_s *polyptr,
783 struct instance_data *idata)
784 {
785 int retval;
786+ int dfd_iparent = -1;
787+ int dfd_ipath = -1;
788+ int dfd_pptrdir = -1;
789 int newdir = 1;
790- char *inst_dir = NULL;
791- char *instname = NULL;
792+ char s_ipath[MAGIC_LNK_FD_SIZE];
793+ char s_pptrdir[MAGIC_LNK_FD_SIZE];
794 struct stat statbuf;
795 #ifdef WITH_SELINUX
796 char *instcontext = NULL, *origcontext = NULL;
797@@ -1641,37 +1807,48 @@ static int ns_setup(struct polydir_s *polyptr,
798 pam_syslog(idata->pamh, LOG_DEBUG,
799 "Set namespace for directory %s", polyptr->dir);
800
801- retval = protect_dir(polyptr->dir, 0, 0, idata);
802+ dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata);
803
804- if (retval < 0) {
805+ if (dfd_pptrdir < 0) {
806 if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) {
807 pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
808 polyptr->dir);
809 return PAM_SESSION_ERR;
810 }
811- if (create_polydir(polyptr, idata) != PAM_SUCCESS)
812+ dfd_pptrdir = create_polydir(polyptr, idata);
813+ if (dfd_pptrdir < 0)
814 return PAM_SESSION_ERR;
815- } else {
816- close(retval);
817 }
818
819 if (polyptr->method == TMPFS) {
820- if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
821- pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m",
822- polyptr->dir);
823- return PAM_SESSION_ERR;
824- }
825+ /*
826+ * There is no function mount() that operate on a fd, so instead, we
827+ * get the magic link corresponding to the fd and give it to mount().
828+ * This protects against potential races exploitable by an unpriv user.
829+ */
830+ if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) {
831+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir");
832+ goto error_out;
833+ }
834
835- if (polyptr->flags & POLYDIR_NOINIT)
836- return PAM_SUCCESS;
837+ if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
838+ pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m",
839+ polyptr->dir);
840+ goto error_out;
841+ }
842+
843+ if (polyptr->flags & POLYDIR_NOINIT) {
844+ retval = PAM_SUCCESS;
845+ goto cleanup;
846+ }
847
848- return inst_init(polyptr, "tmpfs", idata, 1);
849+ retval = inst_init(polyptr, "tmpfs", idata, 1);
850+ goto cleanup;
851 }
852
853- if (stat(polyptr->dir, &statbuf) < 0) {
854- pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m",
855- polyptr->dir);
856- return PAM_SESSION_ERR;
857+ if (fstat(dfd_pptrdir, &statbuf) < 0) {
858+ pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir);
859+ goto error_out;
860 }
861
862 /*
863@@ -1680,15 +1857,16 @@ static int ns_setup(struct polydir_s *polyptr,
864 * security policy.
865 */
866 #ifdef WITH_SELINUX
867- retval = poly_name(polyptr, &instname, &instcontext,
868- &origcontext, idata);
869+ retval = poly_name(polyptr, &instcontext, &origcontext, idata);
870 #else
871- retval = poly_name(polyptr, &instname, idata);
872+ retval = poly_name(polyptr, idata);
873 #endif
874
875 if (retval != PAM_SUCCESS) {
876- if (retval != PAM_IGNORE)
877+ if (retval != PAM_IGNORE) {
878 pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name");
879+ goto error_out;
880+ }
881 goto cleanup;
882 } else {
883 #ifdef WITH_SELINUX
884@@ -1699,22 +1877,33 @@ static int ns_setup(struct polydir_s *polyptr,
885 #endif
886 }
887
888- if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL)
889- goto error_out;
890-
891- if (idata->flags & PAMNS_DEBUG)
892- pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s",
893- inst_dir);
894+ /*
895+ * Gets a fd in a secure manner (we may be operating on a path under
896+ * user control), and check it's compliant.
897+ * Then, we should *always* operate on *this* fd and a relative path
898+ * to be protected against race conditions.
899+ */
900+ dfd_iparent = secure_opendir(polyptr->instance_parent,
901+ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata);
902+ if (dfd_iparent == -1) {
903+ pam_syslog(idata->pamh, LOG_ERR,
904+ "polyptr->instance_parent %s access error",
905+ polyptr->instance_parent);
906+ goto error_out;
907+ }
908+ if (check_inst_parent(dfd_iparent, idata)) {
909+ goto error_out;
910+ }
911
912 /*
913 * Create instance directory with appropriate security
914 * contexts, owner, group and mode bits.
915 */
916 #ifdef WITH_SELINUX
917- retval = create_instance(polyptr, inst_dir, &statbuf, instcontext,
918- origcontext, idata);
919+ retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext,
920+ origcontext, idata);
921 #else
922- retval = create_instance(polyptr, inst_dir, &statbuf, idata);
923+ retval = create_instance(polyptr, dfd_iparent, &statbuf, idata);
924 #endif
925
926 if (retval == PAM_IGNORE) {
927@@ -1726,19 +1915,48 @@ static int ns_setup(struct polydir_s *polyptr,
928 goto error_out;
929 }
930
931+ /*
932+ * Instead of getting a new secure fd, we reuse the fd opened on directory
933+ * polyptr->instance_parent to ensure we are working on the same dir as
934+ * previously, and thus ensure that previous checks (e.g. check_inst_parent())
935+ * are still relevant.
936+ */
937+ dfd_ipath = openat(dfd_iparent, polyptr->instname,
938+ O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
939+ if (dfd_ipath == -1) {
940+ pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m",
941+ polyptr->instname);
942+ goto error_out;
943+ }
944+
945+ if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) {
946+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath");
947+ goto error_out;
948+ }
949+
950+ if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) {
951+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir");
952+ goto error_out;
953+ }
954+
955 /*
956 * Bind mount instance directory on top of the polyinstantiated
957 * directory to provide an instance of polyinstantiated directory
958 * based on polyinstantiated method.
959+ *
960+ * Operates on magic links created from two fd obtained securely
961+ * to protect against race conditions and symlink attacks. Indeed,
962+ * the source and destination can be in a user controled path.
963 */
964- if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) {
965- pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m",
966- inst_dir, polyptr->dir);
967+ if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) {
968+ pam_syslog(idata->pamh, LOG_ERR,
969+ "Error mounting %s on %s (%s on %s), %m",
970+ s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir);
971 goto error_out;
972 }
973
974 if (!(polyptr->flags & POLYDIR_NOINIT))
975- retval = inst_init(polyptr, inst_dir, idata, newdir);
976+ retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir);
977
978 goto cleanup;
979
980@@ -1750,8 +1968,12 @@ error_out:
981 retval = PAM_SESSION_ERR;
982
983 cleanup:
984- free(inst_dir);
985- free(instname);
986+ if (dfd_iparent != -1)
987+ close(dfd_iparent);
988+ if (dfd_ipath != -1)
989+ close(dfd_ipath);
990+ if (dfd_pptrdir != -1)
991+ close(dfd_pptrdir);
992 #ifdef WITH_SELINUX
993 freecon(instcontext);
994 freecon(origcontext);
995@@ -1790,6 +2012,7 @@ static int cleanup_tmpdirs(struct instance_data *idata)
996 {
997 struct polydir_s *pptr;
998 pid_t rc, pid;
999+ int dfd = -1;
1000 struct sigaction newsa, oldsa;
1001 int status;
1002
1003@@ -1801,7 +2024,17 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1004 }
1005
1006 for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
1007- if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) {
1008+ if (pptr->method == TMPDIR) {
1009+
1010+ dfd = secure_opendir_stateless(pptr->instance_parent);
1011+ if (dfd == -1)
1012+ continue;
1013+
1014+ if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
1015+ close(dfd);
1016+ continue;
1017+ }
1018+
1019 pid = fork();
1020 if (pid == 0) {
1021 static char *envp[] = { NULL };
1022@@ -1811,10 +2044,21 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1023 _exit(1);
1024 }
1025 #endif
1026+ if (fchdir(dfd) == -1) {
1027+ pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m",
1028+ pptr->instance_absolute);
1029+ _exit(1);
1030+ }
1031+
1032 close_fds_pre_exec(idata);
1033- execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp);
1034+
1035+ execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp);
1036 _exit(1);
1037 } else if (pid > 0) {
1038+
1039+ if (dfd != -1)
1040+ close(dfd);
1041+
1042 while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
1043 (errno == EINTR));
1044 if (rc == (pid_t)-1) {
1045@@ -1827,6 +2071,10 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1046 "Error removing %s", pptr->instance_prefix);
1047 }
1048 } else if (pid < 0) {
1049+
1050+ if (dfd != -1)
1051+ close(dfd);
1052+
1053 pam_syslog(idata->pamh, LOG_ERR,
1054 "Cannot fork to cleanup temporary directory, %m");
1055 rc = PAM_SESSION_ERR;
1056@@ -1850,6 +2098,7 @@ out:
1057 static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
1058 {
1059 int retval = 0, need_poly = 0, changing_dir = 0;
1060+ int dfd = -1;
1061 char *cptr, *fptr, poly_parent[PATH_MAX];
1062 struct polydir_s *pptr;
1063
1064@@ -1965,13 +2214,21 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
1065 strcpy(poly_parent, "/");
1066 else if (cptr)
1067 *cptr = '\0';
1068- if (chdir(poly_parent) < 0) {
1069+
1070+ dfd = secure_opendir_stateless(poly_parent);
1071+ if (dfd == -1) {
1072+ pam_syslog(idata->pamh, LOG_ERR,
1073+ "Failed opening %s to fchdir: %m", poly_parent);
1074+ }
1075+ else if (fchdir(dfd) == -1) {
1076 pam_syslog(idata->pamh, LOG_ERR,
1077- "Can't chdir to %s, %m", poly_parent);
1078+ "Failed fchdir to %s: %m", poly_parent);
1079 }
1080+ if (dfd != -1)
1081+ close(dfd);
1082 }
1083
1084- if (umount(pptr->rdir) < 0) {
1085+ if (secure_umount(pptr->rdir) < 0) {
1086 int saved_errno = errno;
1087 pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1088 pptr->rdir);
1089@@ -2041,7 +2298,7 @@ static int orig_namespace(struct instance_data *idata)
1090 "Unmounting instance dir for user %d & dir %s",
1091 idata->uid, pptr->dir);
1092
1093- if (umount(pptr->dir) < 0) {
1094+ if (secure_umount(pptr->dir) < 0) {
1095 pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1096 pptr->dir);
1097 return PAM_SESSION_ERR;
1098diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h
1099index 180e042..721d39a 100644
1100--- a/modules/pam_namespace/pam_namespace.h
1101+++ b/modules/pam_namespace/pam_namespace.h
1102@@ -121,6 +121,13 @@
1103 #define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data"
1104 #define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data"
1105
1106+/*
1107+ * Operation mode for function secure_opendir()
1108+ */
1109+#define SECURE_OPENDIR_PROTECT 0x00000001
1110+#define SECURE_OPENDIR_MKDIR 0x00000002
1111+#define SECURE_OPENDIR_FULL_FD 0x00000004
1112+
1113 /*
1114 * Polyinstantiation method options, based on user, security context
1115 * or both
1116@@ -158,6 +165,9 @@ struct polydir_s {
1117 char dir[PATH_MAX]; /* directory to polyinstantiate */
1118 char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */
1119 char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */
1120+ char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */
1121+ char instance_parent[PATH_MAX]; /* parent dir of the instance dir */
1122+ char *instname; /* last segment of the path to the instance dir */
1123 enum polymethod method; /* method used to polyinstantiate */
1124 unsigned int num_uids; /* number of override uids */
1125 uid_t *uid; /* list of override uids */
1126--
11272.49.0
1128
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..18c2a82fb4
--- /dev/null
+++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch
@@ -0,0 +1,187 @@
1From 592d84e1265d04c3104acee815a503856db503a1 Mon Sep 17 00:00:00 2001
2From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
3Date: Tue, 4 Mar 2025 14:37:02 +0100
4Subject: [PATCH] pam_namespace: add flags to indicate path safety
5
6Add two flags in the script to indicate if the paths to the polydir
7and the instance directories are safe (root owned and writable by
8root only).
9
10Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
11Signed-off-by: Dmitry V. Levin <ldv@strace.io>
12
13Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/592d84e1265d04c3104acee815a503856db503a1]
14CVE: CVE-2025-6020
15Signed-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
21diff --git a/modules/pam_namespace/namespace.init b/modules/pam_namespace/namespace.init
22index d9053a1..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
88diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
89index 9d993d4..4c8153b 100644
90--- a/modules/pam_namespace/pam_namespace.c
91+++ b/modules/pam_namespace/pam_namespace.c
92@@ -1467,6 +1467,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@@ -1524,7 +1597,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--
1862.49.0
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..238bef47ec
--- /dev/null
+++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch
@@ -0,0 +1,35 @@
1From 976c20079358d133514568fc7fd95c02df8b5773 Mon Sep 17 00:00:00 2001
2From: "Dmitry V. Levin" <ldv@strace.io>
3Date: Tue, 27 May 2025 08:00:00 +0000
4Subject: [PATCH] pam_namespace: secure_opendir: do not look at the group
5 ownership
6
7When the directory is not group-writable, the group ownership does
8not matter, and when it is group-writable, there should not be any
9exceptions for the root group as there is no guarantee that the root
10group does not include non-root users.
11
12Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/976c20079358d133514568fc7fd95c02df8b5773]
13CVE: CVE-2025-6020
14Signed-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
19diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
20index 4c8153b..791dd07 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--
342.49.0
35
diff --git a/meta/recipes-extended/pam/libpam_1.5.3.bb b/meta/recipes-extended/pam/libpam_1.5.3.bb
index 714cdb6552..815085cc82 100644
--- a/meta/recipes-extended/pam/libpam_1.5.3.bb
+++ b/meta/recipes-extended/pam/libpam_1.5.3.bb
@@ -29,6 +29,11 @@ SRC_URI = "${GITHUB_BASE_URI}/download/v${PV}/Linux-PAM-${PV}.tar.xz \
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-inline-pam-asprintf.patch \
33 file://0002-pam-namespace-rebase.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
34SRC_URI[sha256sum] = "7ac4b50feee004a9fa88f1dfd2d2fa738a82896763050cd773b3c54b0a818283" 39SRC_URI[sha256sum] = "7ac4b50feee004a9fa88f1dfd2d2fa738a82896763050cd773b3c54b0a818283"