summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHitendra Prajapati <hprajapati@mvista.com>2025-07-24 10:02:52 +0530
committerSteve Sakoman <steve@sakoman.com>2025-07-30 07:47:48 -0700
commita485d82c25b97f4b7b9f656d60849136fbbde40a (patch)
treeda8fd17427835b957a9053729d23afba9d708dbd
parent875170d8f8e33fd19abd6f492d6449a608a6aea4 (diff)
downloadpoky-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>
-rw-r--r--meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch102
-rw-r--r--meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch42
-rw-r--r--meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch1588
-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.2.bb5
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 @@
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 | 37 ++++++++++++++++++++++++++++++++++
26 2 files changed, 43 insertions(+)
27
28diff --git a/libpam/include/pam_cc_compat.h b/libpam/include/pam_cc_compat.h
29index 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"); \
45diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h
46index 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--
1012.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 @@
1From cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13 Mon Sep 17 00:00:00 2001
2From: Jacob Heider <jacob@pkgx.dev>
3Date: Wed, 17 Jan 2024 11:49:26 -0500
4Subject: [PATCH] pam_namespace: include stdint.h
5
6pam_namespace.c makes use of SIZE_MAX but doesn't include stdint.h,
7resulting 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
21Fixes: v1.6.0~100 ("pam_namespace: validate amount of uids in config")
22Resolves: https://github.com/linux-pam/linux-pam/issues/733
23
24Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13]
25Signed-off-by: Khem Raj <raj.khem@gmail.com>
26---
27 modules/pam_namespace/pam_namespace.c | 2 ++
28 1 file changed, 2 insertions(+)
29
30diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
31index 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 @@
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 | 999 ++++++++++++++++----------
28 modules/pam_namespace/pam_namespace.h | 17 +-
29 2 files changed, 647 insertions(+), 369 deletions(-)
30
31diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
32index 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;
1527diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h
1528index 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--
15872.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 @@
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 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
88diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
89index 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--
1862.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 @@
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 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--
342.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
34SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d" 39SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d"