tangled
alpha
login
or
join now
pyrox.dev
/
nixpkgs
lol
0
fork
atom
overview
issues
pulls
pipelines
linux-pam: apply patch for CVE-2025-6020
Leona Maroni
7 months ago
26c092bb
4894ec50
+1323
2 changed files
expand all
collapse all
unified
split
pkgs
by-name
li
linux-pam
CVE-2025-6020.patch
package.nix
+1315
pkgs/by-name/li/linux-pam/CVE-2025-6020.patch
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
From 8a44d6aecab1239b79f7b2455d0a70603df2d33c Mon Sep 17 00:00:00 2001
2
+
From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
3
+
Date: Tue, 4 Mar 2025 14:37:02 +0100
4
+
Subject: [PATCH] pam_namespace: fix potential privilege escalation
5
+
6
+
Existing protection provided by protect_dir() and protect_mount() were
7
+
bind mounting on themselves all directories part of the to-be-secured
8
+
paths. However, this works *only* against attacks executed by processes
9
+
in the same mount namespace as the one the mountpoint was created in.
10
+
Therefore, a user with an out-of-mount-namespace access, or multiple
11
+
users colluding, could exploit multiple race conditions, and, for
12
+
instance, elevate their privileges to root.
13
+
14
+
This commit keeps the existing protection as a defense in depth
15
+
measure, and to keep the existing behavior of the module. However,
16
+
it converts all the needed function calls to operate on file
17
+
descriptors instead of absolute paths to protect against race
18
+
conditions globally.
19
+
20
+
Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr>
21
+
Signed-off-by: Dmitry V. Levin <ldv@strace.io>
22
+
23
+
(cherry-picked from 475bd60c552b98c7eddb3270b0b4196847c0072e)
24
+
---
25
+
modules/pam_namespace/pam_namespace.c | 896 +++++++++++++++++---------
26
+
modules/pam_namespace/pam_namespace.h | 10 +
27
+
2 files changed, 588 insertions(+), 318 deletions(-)
28
+
29
+
diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
30
+
index e499d95a..93c4dbe0 100644
31
+
--- a/modules/pam_namespace/pam_namespace.c
32
+
+++ b/modules/pam_namespace/pam_namespace.c
33
+
@@ -41,6 +41,8 @@
34
+
#include "pam_namespace.h"
35
+
#include "argv_parse.h"
36
+
37
+
+#define MAGIC_LNK_FD_SIZE 64
38
+
+
39
+
/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
40
+
static const char *base_name(const char *path)
41
+
{
42
+
@@ -63,6 +65,248 @@ static void close_fds_pre_exec(struct instance_data *idata)
43
+
}
44
+
}
45
+
46
+
+static void
47
+
+strip_trailing_slashes(char *str)
48
+
+{
49
+
+ char *p = str + strlen(str);
50
+
+
51
+
+ while (--p > str && *p == '/')
52
+
+ *p = '\0';
53
+
+}
54
+
+
55
+
+static int protect_mount(int dfd, const char *path, struct instance_data *idata)
56
+
+{
57
+
+ struct protect_dir_s *dir = idata->protect_dirs;
58
+
+ char tmpbuf[MAGIC_LNK_FD_SIZE];
59
+
+
60
+
+ while (dir != NULL) {
61
+
+ if (strcmp(path, dir->dir) == 0) {
62
+
+ return 0;
63
+
+ }
64
+
+ dir = dir->next;
65
+
+ }
66
+
+
67
+
+ if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0)
68
+
+ return -1;
69
+
+
70
+
+ dir = calloc(1, sizeof(*dir));
71
+
+
72
+
+ if (dir == NULL) {
73
+
+ return -1;
74
+
+ }
75
+
+
76
+
+ dir->dir = strdup(path);
77
+
+
78
+
+ if (dir->dir == NULL) {
79
+
+ free(dir);
80
+
+ return -1;
81
+
+ }
82
+
+
83
+
+ if (idata->flags & PAMNS_DEBUG) {
84
+
+ pam_syslog(idata->pamh, LOG_INFO,
85
+
+ "Protect mount of %s over itself", path);
86
+
+ }
87
+
+
88
+
+ if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) {
89
+
+ int save_errno = errno;
90
+
+ pam_syslog(idata->pamh, LOG_ERR,
91
+
+ "Protect mount of %s failed: %m", tmpbuf);
92
+
+ free(dir->dir);
93
+
+ free(dir);
94
+
+ errno = save_errno;
95
+
+ return -1;
96
+
+ }
97
+
+
98
+
+ dir->next = idata->protect_dirs;
99
+
+ idata->protect_dirs = dir;
100
+
+
101
+
+ return 0;
102
+
+}
103
+
+
104
+
+/*
105
+
+ * Returns a fd to the given absolute path, acquired securely. This means:
106
+
+ * - iterating on each segment of the path,
107
+
+ * - not following user symlinks,
108
+
+ * - using race-free operations.
109
+
+ *
110
+
+ * Takes a bit mask to specify the operation mode:
111
+
+ * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path
112
+
+ * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist
113
+
+ * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH,
114
+
+ * allowing more operations to be done with the returned fd
115
+
+ *
116
+
+ * Be aware that using SECURE_OPENDIR_PROTECT:
117
+
+ * - will modify some external state (global structure...) and should not be
118
+
+ * called in cleanup code paths. See wrapper secure_opendir_stateless()
119
+
+ * - need a non-NULL idata to call protect_mount()
120
+
+ */
121
+
+static int secure_opendir(const char *path, int opm, mode_t mode,
122
+
+ struct instance_data *idata)
123
+
+{
124
+
+ char *p;
125
+
+ char *d;
126
+
+ char *dir;
127
+
+ int dfd = -1;
128
+
+ int dfd_next;
129
+
+ int save_errno;
130
+
+ int flags = O_DIRECTORY | O_CLOEXEC;
131
+
+ int rv = -1;
132
+
+ struct stat st;
133
+
+
134
+
+ if (opm & SECURE_OPENDIR_FULL_FD)
135
+
+ flags |= O_RDONLY;
136
+
+ else
137
+
+ flags |= O_PATH;
138
+
+
139
+
+ /* Check for args consistency */
140
+
+ if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL)
141
+
+ return -1;
142
+
+
143
+
+ /* Accept only absolute paths */
144
+
+ if (*path != '/')
145
+
+ return -1;
146
+
+
147
+
+ dir = p = strdup(path);
148
+
+ if (p == NULL)
149
+
+ return -1;
150
+
+
151
+
+ /* Assume '/' is safe */
152
+
+ dfd = open("/", flags);
153
+
+ if (dfd == -1)
154
+
+ goto error;
155
+
+
156
+
+ /* Needed to not loop too far and call openat() on NULL */
157
+
+ strip_trailing_slashes(p);
158
+
+
159
+
+ dir++;
160
+
+
161
+
+ /* In case path is '/' */
162
+
+ if (*dir == '\0') {
163
+
+ free(p);
164
+
+ return dfd;
165
+
+ }
166
+
+
167
+
+ while ((d=strchr(dir, '/')) != NULL) {
168
+
+ *d = '\0';
169
+
+
170
+
+ dfd_next = openat(dfd, dir, flags);
171
+
+ if (dfd_next == -1)
172
+
+ goto error;
173
+
+
174
+
+ if (fstat(dfd_next, &st) != 0) {
175
+
+ close(dfd_next);
176
+
+ goto error;
177
+
+ }
178
+
+
179
+
+ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
180
+
+ /* we are inside user-owned dir - protect */
181
+
+ if (protect_mount(dfd_next, p, idata) == -1) {
182
+
+ close(dfd_next);
183
+
+ goto error;
184
+
+ }
185
+
+ /*
186
+
+ * Reopen the directory to obtain a new descriptor
187
+
+ * after protect_mount(), this is necessary in cases
188
+
+ * when another directory is going to be mounted over
189
+
+ * the given path.
190
+
+ */
191
+
+ close(dfd_next);
192
+
+ dfd_next = openat(dfd, dir, flags);
193
+
+ if (dfd_next == -1)
194
+
+ goto error;
195
+
+ } else if (st.st_uid != 0
196
+
+ || (st.st_gid != 0 && (st.st_mode & S_IWGRP))
197
+
+ || (st.st_mode & S_IWOTH)) {
198
+
+ /* do not follow symlinks on subdirectories */
199
+
+ flags |= O_NOFOLLOW;
200
+
+ }
201
+
+
202
+
+ close(dfd);
203
+
+ dfd = dfd_next;
204
+
+
205
+
+ *d = '/';
206
+
+ dir = d + 1;
207
+
+ }
208
+
+
209
+
+ rv = openat(dfd, dir, flags);
210
+
+
211
+
+ if (rv == -1) {
212
+
+ if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0)
213
+
+ rv = openat(dfd, dir, flags);
214
+
+
215
+
+ if (rv == -1)
216
+
+ goto error;
217
+
+ }
218
+
+
219
+
+ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
220
+
+ /* we are inside user-owned dir - protect */
221
+
+ if (protect_mount(rv, p, idata) == -1) {
222
+
+ save_errno = errno;
223
+
+ close(rv);
224
+
+ rv = -1;
225
+
+ errno = save_errno;
226
+
+ }
227
+
+ /*
228
+
+ * Reopen the directory to obtain a new descriptor after
229
+
+ * protect_mount(), this is necessary in cases when another
230
+
+ * directory is going to be mounted over the given path.
231
+
+ */
232
+
+ close(rv);
233
+
+ rv = openat(dfd, dir, flags);
234
+
+ }
235
+
+
236
+
+error:
237
+
+ save_errno = errno;
238
+
+ free(p);
239
+
+ if (dfd >= 0)
240
+
+ close(dfd);
241
+
+ errno = save_errno;
242
+
+
243
+
+ return rv;
244
+
+}
245
+
+
246
+
+/*
247
+
+ * Returns a fd to the given path, acquired securely.
248
+
+ * It can be called in all situations, including in cleanup code paths, as
249
+
+ * it does not modify external state (no access to global structures...).
250
+
+ */
251
+
+static int secure_opendir_stateless(const char *path)
252
+
+{
253
+
+ return secure_opendir(path, 0, 0, NULL);
254
+
+}
255
+
+
256
+
+/*
257
+
+ * Umount securely the given path, even if the directories along
258
+
+ * the path are under user control. It should protect against
259
+
+ * symlinks attacks and race conditions.
260
+
+ */
261
+
+static int secure_umount(const char *path)
262
+
+{
263
+
+ int save_errno;
264
+
+ int rv = -1;
265
+
+ int dfd = -1;
266
+
+ char s_path[MAGIC_LNK_FD_SIZE];
267
+
+
268
+
+ dfd = secure_opendir_stateless(path);
269
+
+ if (dfd == -1)
270
+
+ return rv;
271
+
+
272
+
+ if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0)
273
+
+ goto error;
274
+
+
275
+
+ /*
276
+
+ * We still have a fd open to path itself,
277
+
+ * so we need to do a lazy umount.
278
+
+ */
279
+
+ rv = umount2(s_path, MNT_DETACH);
280
+
+
281
+
+error:
282
+
+ save_errno = errno;
283
+
+ close(dfd);
284
+
+ errno = save_errno;
285
+
+ return rv;
286
+
+}
287
+
+
288
+
/* Evaluating a list of files which have to be parsed in the right order:
289
+
*
290
+
* - If etc/security/namespace.d/@filename@.conf exists, then
291
+
@@ -188,7 +432,7 @@ static void unprotect_dirs(struct protect_dir_s *dir)
292
+
struct protect_dir_s *next;
293
+
294
+
while (dir != NULL) {
295
+
- umount(dir->dir);
296
+
+ secure_umount(dir->dir);
297
+
free(dir->dir);
298
+
next = dir->next;
299
+
free(dir);
300
+
@@ -606,14 +850,9 @@ static int process_line(char *line, const char *home, const char *rhome,
301
+
goto skipping;
302
+
}
303
+
304
+
-#define COPY_STR(dst, src, apd) \
305
+
- (snprintf((dst), sizeof(dst), "%s%s", (src), (apd)) != \
306
+
- (ssize_t) (strlen(src) + strlen(apd)))
307
+
-
308
+
- if (COPY_STR(poly->dir, dir, "")
309
+
- || COPY_STR(poly->rdir, rdir, "")
310
+
- || COPY_STR(poly->instance_prefix, instance_prefix,
311
+
- poly->method == TMPDIR ? "XXXXXX" : "")) {
312
+
+ if (pam_sprintf(poly->dir, "%s", dir) < 0
313
+
+ || pam_sprintf(poly->rdir, "%s", rdir) < 0
314
+
+ || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) {
315
+
pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
316
+
goto skipping;
317
+
}
318
+
@@ -896,6 +1135,23 @@ static char *md5hash(const char *instname, struct instance_data *idata)
319
+
}
320
+
321
+
#ifdef WITH_SELINUX
322
+
+static char *secure_getfilecon(pam_handle_t *pamh, const char *dir)
323
+
+{
324
+
+ char *ctx = NULL;
325
+
+ int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL);
326
+
+ if (dfd < 0) {
327
+
+ pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir);
328
+
+ return NULL;
329
+
+ }
330
+
+ if (fgetfilecon(dfd, &ctx) < 0)
331
+
+ ctx = NULL;
332
+
+ if (ctx == NULL)
333
+
+ pam_syslog(pamh, LOG_ERR,
334
+
+ "Error getting poly dir context for %s: %m", dir);
335
+
+ close(dfd);
336
+
+ return ctx;
337
+
+}
338
+
+
339
+
static int form_context(const struct polydir_s *polyptr,
340
+
char **i_context, char **origcon,
341
+
struct instance_data *idata)
342
+
@@ -907,12 +1163,9 @@ static int form_context(const struct polydir_s *polyptr,
343
+
/*
344
+
* Get the security context of the directory to polyinstantiate.
345
+
*/
346
+
- rc = getfilecon(polyptr->dir, origcon);
347
+
- if (rc < 0 || *origcon == NULL) {
348
+
- pam_syslog(idata->pamh, LOG_ERR,
349
+
- "Error getting poly dir context, %m");
350
+
+ *origcon = secure_getfilecon(idata->pamh, polyptr->dir);
351
+
+ if (*origcon == NULL)
352
+
return PAM_SESSION_ERR;
353
+
- }
354
+
355
+
if (polyptr->method == USER) return PAM_SUCCESS;
356
+
357
+
@@ -1008,29 +1261,52 @@ static int form_context(const struct polydir_s *polyptr,
358
+
#endif
359
+
360
+
/*
361
+
- * poly_name returns the name of the polyinstantiated instance directory
362
+
+ * From the instance differentiation string, set in the polyptr structure:
363
+
+ * - the absolute path to the instance dir,
364
+
+ * - the absolute path to the previous dir (parent),
365
+
+ * - the instance name (may be different than the instance differentiation string)
366
+
+ */
367
+
+static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation)
368
+
+{
369
+
+ char *tmp;
370
+
+
371
+
+ if (pam_sprintf(polyptr->instance_absolute, "%s%s",
372
+
+ polyptr->instance_prefix, inst_differentiation) < 0)
373
+
+ return -1;
374
+
+
375
+
+ polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1;
376
+
+
377
+
+ if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0)
378
+
+ return -1;
379
+
+
380
+
+ tmp = strrchr(polyptr->instance_parent, '/') + 1;
381
+
+ *tmp = '\0';
382
+
+
383
+
+ return 0;
384
+
+}
385
+
+
386
+
+/*
387
+
+ * Set the name of the polyinstantiated instance directory
388
+
* based on the method used for polyinstantiation (user, context or level)
389
+
* In addition, the function also returns the security contexts of the
390
+
* original directory to polyinstantiate and the polyinstantiated instance
391
+
* directory.
392
+
*/
393
+
#ifdef WITH_SELINUX
394
+
-static int poly_name(const struct polydir_s *polyptr, char **i_name,
395
+
- char **i_context, char **origcon,
396
+
- struct instance_data *idata)
397
+
+static int poly_name(struct polydir_s *polyptr, char **i_context,
398
+
+ char **origcon, struct instance_data *idata)
399
+
#else
400
+
-static int poly_name(const struct polydir_s *polyptr, char **i_name,
401
+
- struct instance_data *idata)
402
+
+static int poly_name(struct polydir_s *polyptr, struct instance_data *idata)
403
+
#endif
404
+
{
405
+
int rc;
406
+
+ char *inst_differentiation = NULL;
407
+
char *hash = NULL;
408
+
enum polymethod pm;
409
+
#ifdef WITH_SELINUX
410
+
char *rawcon = NULL;
411
+
#endif
412
+
413
+
- *i_name = NULL;
414
+
#ifdef WITH_SELINUX
415
+
*i_context = NULL;
416
+
*origcon = NULL;
417
+
@@ -1064,10 +1340,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
418
+
419
+
switch (pm) {
420
+
case USER:
421
+
- if (asprintf(i_name, "%s", idata->user) < 0) {
422
+
- *i_name = NULL;
423
+
+ if ((inst_differentiation = strdup(idata->user)) == NULL)
424
+
goto fail;
425
+
- }
426
+
break;
427
+
428
+
#ifdef WITH_SELINUX
429
+
@@ -1077,26 +1351,23 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
430
+
pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context");
431
+
goto fail;
432
+
}
433
+
- if (polyptr->flags & POLYDIR_SHARED) {
434
+
- if (asprintf(i_name, "%s", rawcon) < 0) {
435
+
- *i_name = NULL;
436
+
- goto fail;
437
+
- }
438
+
- } else {
439
+
- if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) {
440
+
- *i_name = NULL;
441
+
- goto fail;
442
+
- }
443
+
- }
444
+
+ if (polyptr->flags & POLYDIR_SHARED)
445
+
+ inst_differentiation = strdup(rawcon);
446
+
+ else
447
+
+ inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user);
448
+
+ if (inst_differentiation == NULL)
449
+
+ goto fail;
450
+
break;
451
+
-
452
+
#endif /* WITH_SELINUX */
453
+
-
454
+
case TMPDIR:
455
+
+ if ((inst_differentiation = strdup("XXXXXX")) == NULL)
456
+
+ goto fail;
457
+
+ goto success;
458
+
+
459
+
case TMPFS:
460
+
- if ((*i_name=strdup("")) == NULL)
461
+
+ if ((inst_differentiation=strdup("")) == NULL)
462
+
goto fail;
463
+
- return PAM_SUCCESS;
464
+
+ goto success;
465
+
466
+
default:
467
+
if (idata->flags & PAMNS_DEBUG)
468
+
@@ -1105,31 +1376,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
469
+
}
470
+
471
+
if (idata->flags & PAMNS_DEBUG)
472
+
- pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name);
473
+
+ pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation);
474
+
475
+
- if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) {
476
+
- hash = md5hash(*i_name, idata);
477
+
+ if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) {
478
+
+ hash = md5hash(inst_differentiation, idata);
479
+
if (hash == NULL) {
480
+
goto fail;
481
+
}
482
+
if (idata->flags & PAMNS_GEN_HASH) {
483
+
- free(*i_name);
484
+
- *i_name = hash;
485
+
+ free(inst_differentiation);
486
+
+ inst_differentiation = hash;
487
+
hash = NULL;
488
+
} else {
489
+
- char *newname;
490
+
- if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash),
491
+
- *i_name, hash) < 0) {
492
+
+ char *newname =
493
+
+ pam_asprintf("%.*s_%s",
494
+
+ NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash),
495
+
+ inst_differentiation, hash);
496
+
+ if (newname == NULL)
497
+
goto fail;
498
+
- }
499
+
- free(*i_name);
500
+
- *i_name = newname;
501
+
+ free(inst_differentiation);
502
+
+ inst_differentiation = newname;
503
+
}
504
+
}
505
+
- rc = PAM_SUCCESS;
506
+
507
+
+success:
508
+
+ if (set_polydir_paths(polyptr, inst_differentiation) == -1)
509
+
+ goto fail;
510
+
+
511
+
+ rc = PAM_SUCCESS;
512
+
fail:
513
+
free(hash);
514
+
+ free(inst_differentiation);
515
+
#ifdef WITH_SELINUX
516
+
freecon(rawcon);
517
+
#endif
518
+
@@ -1140,186 +1417,65 @@ fail:
519
+
freecon(*origcon);
520
+
*origcon = NULL;
521
+
#endif
522
+
- free(*i_name);
523
+
- *i_name = NULL;
524
+
}
525
+
return rc;
526
+
}
527
+
528
+
-static int protect_mount(int dfd, const char *path, struct instance_data *idata)
529
+
+/*
530
+
+ * Rmdir the given path securely, protecting against symlinks attacks
531
+
+ * and race conditions.
532
+
+ * This function is currently called only in cleanup code paths where
533
+
+ * any errors returned are not handled, so do not handle them either.
534
+
+ * Basically, try to rmdir the path on a best-effort basis.
535
+
+ */
536
+
+static void secure_try_rmdir(const char *path)
537
+
{
538
+
- struct protect_dir_s *dir = idata->protect_dirs;
539
+
- char tmpbuf[64];
540
+
-
541
+
- while (dir != NULL) {
542
+
- if (strcmp(path, dir->dir) == 0) {
543
+
- return 0;
544
+
- }
545
+
- dir = dir->next;
546
+
- }
547
+
-
548
+
- dir = calloc(1, sizeof(*dir));
549
+
-
550
+
- if (dir == NULL) {
551
+
- return -1;
552
+
- }
553
+
-
554
+
- dir->dir = strdup(path);
555
+
-
556
+
- if (dir->dir == NULL) {
557
+
- free(dir);
558
+
- return -1;
559
+
- }
560
+
+ int dfd;
561
+
+ char *buf;
562
+
+ char *parent;
563
+
564
+
- snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd);
565
+
+ buf = strdup(path);
566
+
+ if (buf == NULL)
567
+
+ return;
568
+
569
+
- if (idata->flags & PAMNS_DEBUG) {
570
+
- pam_syslog(idata->pamh, LOG_INFO,
571
+
- "Protect mount of %s over itself", path);
572
+
- }
573
+
+ parent = dirname(buf);
574
+
575
+
- if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) {
576
+
- int save_errno = errno;
577
+
- pam_syslog(idata->pamh, LOG_ERR,
578
+
- "Protect mount of %s failed: %m", tmpbuf);
579
+
- free(dir->dir);
580
+
- free(dir);
581
+
- errno = save_errno;
582
+
- return -1;
583
+
+ dfd = secure_opendir_stateless(parent);
584
+
+ if (dfd >= 0) {
585
+
+ unlinkat(dfd, base_name(path), AT_REMOVEDIR);
586
+
+ close(dfd);
587
+
}
588
+
589
+
- dir->next = idata->protect_dirs;
590
+
- idata->protect_dirs = dir;
591
+
-
592
+
- return 0;
593
+
+ free(buf);
594
+
}
595
+
596
+
-static int protect_dir(const char *path, mode_t mode, int do_mkdir,
597
+
- struct instance_data *idata)
598
+
-{
599
+
- char *p = strdup(path);
600
+
- char *d;
601
+
- char *dir = p;
602
+
- int dfd = AT_FDCWD;
603
+
- int dfd_next;
604
+
- int save_errno;
605
+
- int flags = O_RDONLY | O_DIRECTORY;
606
+
- int rv = -1;
607
+
- struct stat st;
608
+
-
609
+
- if (p == NULL) {
610
+
- goto error;
611
+
- }
612
+
-
613
+
- if (*dir == '/') {
614
+
- dfd = open("/", flags);
615
+
- if (dfd == -1) {
616
+
- goto error;
617
+
- }
618
+
- dir++; /* assume / is safe */
619
+
- }
620
+
-
621
+
- while ((d=strchr(dir, '/')) != NULL) {
622
+
- *d = '\0';
623
+
- dfd_next = openat(dfd, dir, flags);
624
+
- if (dfd_next == -1) {
625
+
- goto error;
626
+
- }
627
+
-
628
+
- if (dfd != AT_FDCWD)
629
+
- close(dfd);
630
+
- dfd = dfd_next;
631
+
-
632
+
- if (fstat(dfd, &st) != 0) {
633
+
- goto error;
634
+
- }
635
+
-
636
+
- if (flags & O_NOFOLLOW) {
637
+
- /* we are inside user-owned dir - protect */
638
+
- if (protect_mount(dfd, p, idata) == -1)
639
+
- goto error;
640
+
- } else if (st.st_uid != 0 || st.st_gid != 0 ||
641
+
- (st.st_mode & S_IWOTH)) {
642
+
- /* do not follow symlinks on subdirectories */
643
+
- flags |= O_NOFOLLOW;
644
+
- }
645
+
-
646
+
- *d = '/';
647
+
- dir = d + 1;
648
+
- }
649
+
-
650
+
- rv = openat(dfd, dir, flags);
651
+
-
652
+
- if (rv == -1) {
653
+
- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
654
+
- goto error;
655
+
- }
656
+
- rv = openat(dfd, dir, flags);
657
+
- }
658
+
659
+
- if (flags & O_NOFOLLOW) {
660
+
- /* we are inside user-owned dir - protect */
661
+
- if (protect_mount(rv, p, idata) == -1) {
662
+
- save_errno = errno;
663
+
- close(rv);
664
+
- rv = -1;
665
+
- errno = save_errno;
666
+
- }
667
+
- }
668
+
669
+
-error:
670
+
- save_errno = errno;
671
+
- free(p);
672
+
- if (dfd != AT_FDCWD && dfd >= 0)
673
+
- close(dfd);
674
+
- errno = save_errno;
675
+
-
676
+
- return rv;
677
+
-}
678
+
-
679
+
-static int check_inst_parent(char *ipath, struct instance_data *idata)
680
+
+static int check_inst_parent(int dfd, struct instance_data *idata)
681
+
{
682
+
struct stat instpbuf;
683
+
- char *inst_parent, *trailing_slash;
684
+
- int dfd;
685
+
+
686
+
/*
687
+
- * stat the instance parent path to make sure it exists
688
+
- * and is a directory. Check that its mode is 000 (unless the
689
+
- * admin explicitly instructs to ignore the instance parent
690
+
- * mode by the "ignore_instance_parent_mode" argument).
691
+
+ * Stat the instance parent directory to make sure it's writable by
692
+
+ * root only (unless the admin explicitly instructs to ignore the
693
+
+ * instance parent mode by the "ignore_instance_parent_mode" argument).
694
+
*/
695
+
- inst_parent = strdup(ipath);
696
+
- if (!inst_parent) {
697
+
- pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string");
698
+
- return PAM_SESSION_ERR;
699
+
- }
700
+
701
+
- trailing_slash = strrchr(inst_parent, '/');
702
+
- if (trailing_slash)
703
+
- *trailing_slash = '\0';
704
+
-
705
+
- dfd = protect_dir(inst_parent, 0, 1, idata);
706
+
+ if (idata->flags & PAMNS_IGN_INST_PARENT_MODE)
707
+
+ return PAM_SUCCESS;
708
+
709
+
- if (dfd == -1 || fstat(dfd, &instpbuf) < 0) {
710
+
+ if (fstat(dfd, &instpbuf) < 0) {
711
+
pam_syslog(idata->pamh, LOG_ERR,
712
+
- "Error creating or accessing instance parent %s, %m", inst_parent);
713
+
- if (dfd != -1)
714
+
- close(dfd);
715
+
- free(inst_parent);
716
+
+ "Error accessing instance parent, %m");
717
+
return PAM_SESSION_ERR;
718
+
}
719
+
720
+
- if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) {
721
+
- if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
722
+
- pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root",
723
+
- inst_parent);
724
+
- close(dfd);
725
+
- free(inst_parent);
726
+
- return PAM_SESSION_ERR;
727
+
- }
728
+
+ if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
729
+
+ pam_syslog(idata->pamh, LOG_ERR,
730
+
+ "Mode of inst parent not 000 or owner not root");
731
+
+ return PAM_SESSION_ERR;
732
+
}
733
+
- close(dfd);
734
+
- free(inst_parent);
735
+
+
736
+
return PAM_SUCCESS;
737
+
}
738
+
739
+
@@ -1457,11 +1613,16 @@ static int create_polydir(struct polydir_s *polyptr,
740
+
}
741
+
#endif
742
+
743
+
- rc = protect_dir(dir, mode, 1, idata);
744
+
+ rc = secure_opendir(dir,
745
+
+ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD,
746
+
+ mode, idata);
747
+
if (rc == -1) {
748
+
pam_syslog(idata->pamh, LOG_ERR,
749
+
"Error creating directory %s: %m", dir);
750
+
- return PAM_SESSION_ERR;
751
+
+#ifdef WITH_SELINUX
752
+
+ freecon(oldcon_raw);
753
+
+#endif
754
+
+ return -1;
755
+
}
756
+
757
+
#ifdef WITH_SELINUX
758
+
@@ -1482,9 +1643,9 @@ static int create_polydir(struct polydir_s *polyptr,
759
+
pam_syslog(idata->pamh, LOG_ERR,
760
+
"Error changing mode of directory %s: %m", dir);
761
+
close(rc);
762
+
- umount(dir); /* undo the eventual protection bind mount */
763
+
- rmdir(dir);
764
+
- return PAM_SESSION_ERR;
765
+
+ secure_umount(dir); /* undo the eventual protection bind mount */
766
+
+ secure_try_rmdir(dir);
767
+
+ return -1;
768
+
}
769
+
}
770
+
771
+
@@ -1502,41 +1663,37 @@ static int create_polydir(struct polydir_s *polyptr,
772
+
pam_syslog(idata->pamh, LOG_ERR,
773
+
"Unable to change owner on directory %s: %m", dir);
774
+
close(rc);
775
+
- umount(dir); /* undo the eventual protection bind mount */
776
+
- rmdir(dir);
777
+
- return PAM_SESSION_ERR;
778
+
+ secure_umount(dir); /* undo the eventual protection bind mount */
779
+
+ secure_try_rmdir(dir);
780
+
+ return -1;
781
+
}
782
+
783
+
- close(rc);
784
+
-
785
+
if (idata->flags & PAMNS_DEBUG)
786
+
pam_syslog(idata->pamh, LOG_DEBUG,
787
+
"Polydir owner %u group %u", uid, gid);
788
+
789
+
- return PAM_SUCCESS;
790
+
+ return rc;
791
+
}
792
+
793
+
/*
794
+
- * Create polyinstantiated instance directory (ipath).
795
+
+ * Create polyinstantiated instance directory.
796
+
+ * To protect against races, changes are done on a fd to the parent of the
797
+
+ * instance directory (dfd_iparent) and a relative path (polyptr->instname).
798
+
+ * The absolute path (polyptr->instance_absolute) is only updated when creating
799
+
+ * a tmpdir and used for logging purposes.
800
+
*/
801
+
#ifdef WITH_SELINUX
802
+
-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
803
+
- const char *icontext, const char *ocontext,
804
+
- struct instance_data *idata)
805
+
+static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
806
+
+ struct stat *statbuf, const char *icontext, const char *ocontext,
807
+
+ struct instance_data *idata)
808
+
#else
809
+
-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
810
+
- struct instance_data *idata)
811
+
+static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
812
+
+ struct stat *statbuf, struct instance_data *idata)
813
+
#endif
814
+
{
815
+
struct stat newstatbuf;
816
+
int fd;
817
+
818
+
- /*
819
+
- * Check to make sure instance parent is valid.
820
+
- */
821
+
- if (check_inst_parent(ipath, idata))
822
+
- return PAM_SESSION_ERR;
823
+
-
824
+
/*
825
+
* Create instance directory and set its security context to the context
826
+
* returned by the security policy. Set its mode and ownership
827
+
@@ -1545,29 +1702,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
828
+
*/
829
+
830
+
if (polyptr->method == TMPDIR) {
831
+
- if (mkdtemp(polyptr->instance_prefix) == NULL) {
832
+
- pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m",
833
+
- polyptr->instance_prefix);
834
+
- polyptr->method = NONE; /* do not clean up! */
835
+
- return PAM_SESSION_ERR;
836
+
- }
837
+
- /* copy the actual directory name to ipath */
838
+
- strcpy(ipath, polyptr->instance_prefix);
839
+
- } else if (mkdir(ipath, S_IRUSR) < 0) {
840
+
+ char s_path[PATH_MAX];
841
+
+ /*
842
+
+ * Create the template for mkdtemp() as a magic link based on
843
+
+ * our existing fd to avoid symlink attacks and races.
844
+
+ */
845
+
+ if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0
846
+
+ || mkdtemp(s_path) == NULL) {
847
+
+ pam_syslog(idata->pamh, LOG_ERR,
848
+
+ "Error creating temporary instance dir %s, %m",
849
+
+ polyptr->instance_absolute);
850
+
+ polyptr->method = NONE; /* do not clean up! */
851
+
+ return PAM_SESSION_ERR;
852
+
+ }
853
+
+
854
+
+ /* Copy the actual directory name to polyptr->instname */
855
+
+ strcpy(polyptr->instname, base_name(s_path));
856
+
+ } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) {
857
+
if (errno == EEXIST)
858
+
return PAM_IGNORE;
859
+
else {
860
+
pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m",
861
+
- ipath);
862
+
+ polyptr->instance_absolute);
863
+
return PAM_SESSION_ERR;
864
+
}
865
+
}
866
+
867
+
- /* Open a descriptor to it to prevent races */
868
+
- fd = open(ipath, O_DIRECTORY | O_RDONLY);
869
+
+ /* Open a descriptor to prevent races, based on our existing fd. */
870
+
+ fd = openat(dfd_iparent, polyptr->instname,
871
+
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
872
+
if (fd < 0) {
873
+
- pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath);
874
+
- rmdir(ipath);
875
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m",
876
+
+ polyptr->instance_absolute);
877
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
878
+
return PAM_SESSION_ERR;
879
+
}
880
+
#ifdef WITH_SELINUX
881
+
@@ -1577,17 +1744,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
882
+
if (icontext) {
883
+
if (fsetfilecon(fd, icontext) < 0) {
884
+
pam_syslog(idata->pamh, LOG_ERR,
885
+
- "Error setting context of %s to %s", ipath, icontext);
886
+
+ "Error setting context of %s to %s",
887
+
+ polyptr->instance_absolute, icontext);
888
+
close(fd);
889
+
- rmdir(ipath);
890
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
891
+
return PAM_SESSION_ERR;
892
+
}
893
+
} else {
894
+
if (fsetfilecon(fd, ocontext) < 0) {
895
+
pam_syslog(idata->pamh, LOG_ERR,
896
+
- "Error setting context of %s to %s", ipath, ocontext);
897
+
+ "Error setting context of %s to %s",
898
+
+ polyptr->instance_absolute, ocontext);
899
+
close(fd);
900
+
- rmdir(ipath);
901
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
902
+
return PAM_SESSION_ERR;
903
+
}
904
+
}
905
+
@@ -1595,9 +1764,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
906
+
#endif
907
+
if (fstat(fd, &newstatbuf) < 0) {
908
+
pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m",
909
+
- ipath);
910
+
+ polyptr->instance_absolute);
911
+
close(fd);
912
+
- rmdir(ipath);
913
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
914
+
return PAM_SESSION_ERR;
915
+
}
916
+
if (newstatbuf.st_uid != statbuf->st_uid ||
917
+
@@ -1605,17 +1774,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
918
+
if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) {
919
+
pam_syslog(idata->pamh, LOG_ERR,
920
+
"Error changing owner for %s, %m",
921
+
- ipath);
922
+
+ polyptr->instance_absolute);
923
+
close(fd);
924
+
- rmdir(ipath);
925
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
926
+
return PAM_SESSION_ERR;
927
+
}
928
+
}
929
+
if (fchmod(fd, statbuf->st_mode & 07777) < 0) {
930
+
pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m",
931
+
- ipath);
932
+
+ polyptr->instance_absolute);
933
+
close(fd);
934
+
- rmdir(ipath);
935
+
+ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
936
+
return PAM_SESSION_ERR;
937
+
}
938
+
close(fd);
939
+
@@ -1634,9 +1803,12 @@ static int ns_setup(struct polydir_s *polyptr,
940
+
struct instance_data *idata)
941
+
{
942
+
int retval;
943
+
+ int dfd_iparent = -1;
944
+
+ int dfd_ipath = -1;
945
+
+ int dfd_pptrdir = -1;
946
+
int newdir = 1;
947
+
- char *inst_dir = NULL;
948
+
- char *instname = NULL;
949
+
+ char s_ipath[MAGIC_LNK_FD_SIZE];
950
+
+ char s_pptrdir[MAGIC_LNK_FD_SIZE];
951
+
struct stat statbuf;
952
+
#ifdef WITH_SELINUX
953
+
char *instcontext = NULL, *origcontext = NULL;
954
+
@@ -1646,39 +1818,48 @@ static int ns_setup(struct polydir_s *polyptr,
955
+
pam_syslog(idata->pamh, LOG_DEBUG,
956
+
"Set namespace for directory %s", polyptr->dir);
957
+
958
+
- retval = protect_dir(polyptr->dir, 0, 0, idata);
959
+
-
960
+
- if (retval < 0 && errno != ENOENT) {
961
+
- pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
962
+
- polyptr->dir);
963
+
- return PAM_SESSION_ERR;
964
+
- }
965
+
+ dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata);
966
+
967
+
- if (retval < 0) {
968
+
- if ((polyptr->flags & POLYDIR_CREATE) &&
969
+
- create_polydir(polyptr, idata) != PAM_SUCCESS)
970
+
- return PAM_SESSION_ERR;
971
+
- } else {
972
+
- close(retval);
973
+
+ if (dfd_pptrdir < 0) {
974
+
+ if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) {
975
+
+ pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
976
+
+ polyptr->dir);
977
+
+ return PAM_SESSION_ERR;
978
+
+ }
979
+
+ dfd_pptrdir = create_polydir(polyptr, idata);
980
+
+ if (dfd_pptrdir < 0)
981
+
+ return PAM_SESSION_ERR;
982
+
}
983
+
984
+
if (polyptr->method == TMPFS) {
985
+
- if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
986
+
- pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m",
987
+
- polyptr->dir);
988
+
- return PAM_SESSION_ERR;
989
+
- }
990
+
+ /*
991
+
+ * There is no function mount() that operate on a fd, so instead, we
992
+
+ * get the magic link corresponding to the fd and give it to mount().
993
+
+ * This protects against potential races exploitable by an unpriv user.
994
+
+ */
995
+
+ if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) {
996
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir");
997
+
+ goto error_out;
998
+
+ }
999
+
+
1000
+
+ if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
1001
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m",
1002
+
+ polyptr->dir);
1003
+
+ goto error_out;
1004
+
+ }
1005
+
1006
+
- if (polyptr->flags & POLYDIR_NOINIT)
1007
+
- return PAM_SUCCESS;
1008
+
+ if (polyptr->flags & POLYDIR_NOINIT) {
1009
+
+ retval = PAM_SUCCESS;
1010
+
+ goto cleanup;
1011
+
+ }
1012
+
1013
+
- return inst_init(polyptr, "tmpfs", idata, 1);
1014
+
+ retval = inst_init(polyptr, "tmpfs", idata, 1);
1015
+
+ goto cleanup;
1016
+
}
1017
+
1018
+
- if (stat(polyptr->dir, &statbuf) < 0) {
1019
+
- pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m",
1020
+
- polyptr->dir);
1021
+
- return PAM_SESSION_ERR;
1022
+
+ if (fstat(dfd_pptrdir, &statbuf) < 0) {
1023
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir);
1024
+
+ goto error_out;
1025
+
}
1026
+
1027
+
/*
1028
+
@@ -1687,16 +1868,17 @@ static int ns_setup(struct polydir_s *polyptr,
1029
+
* security policy.
1030
+
*/
1031
+
#ifdef WITH_SELINUX
1032
+
- retval = poly_name(polyptr, &instname, &instcontext,
1033
+
- &origcontext, idata);
1034
+
+ retval = poly_name(polyptr, &instcontext, &origcontext, idata);
1035
+
#else
1036
+
- retval = poly_name(polyptr, &instname, idata);
1037
+
+ retval = poly_name(polyptr, idata);
1038
+
#endif
1039
+
1040
+
if (retval != PAM_SUCCESS) {
1041
+
- if (retval != PAM_IGNORE)
1042
+
- pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name");
1043
+
- goto cleanup;
1044
+
+ if (retval != PAM_IGNORE) {
1045
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name");
1046
+
+ goto error_out;
1047
+
+ }
1048
+
+ goto cleanup;
1049
+
} else {
1050
+
#ifdef WITH_SELINUX
1051
+
if ((idata->flags & PAMNS_DEBUG) &&
1052
+
@@ -1706,22 +1888,33 @@ static int ns_setup(struct polydir_s *polyptr,
1053
+
#endif
1054
+
}
1055
+
1056
+
- if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0)
1057
+
- goto error_out;
1058
+
-
1059
+
- if (idata->flags & PAMNS_DEBUG)
1060
+
- pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s",
1061
+
- inst_dir);
1062
+
+ /*
1063
+
+ * Gets a fd in a secure manner (we may be operating on a path under
1064
+
+ * user control), and check it's compliant.
1065
+
+ * Then, we should *always* operate on *this* fd and a relative path
1066
+
+ * to be protected against race conditions.
1067
+
+ */
1068
+
+ dfd_iparent = secure_opendir(polyptr->instance_parent,
1069
+
+ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata);
1070
+
+ if (dfd_iparent == -1) {
1071
+
+ pam_syslog(idata->pamh, LOG_ERR,
1072
+
+ "polyptr->instance_parent %s access error",
1073
+
+ polyptr->instance_parent);
1074
+
+ goto error_out;
1075
+
+ }
1076
+
+ if (check_inst_parent(dfd_iparent, idata)) {
1077
+
+ goto error_out;
1078
+
+ }
1079
+
1080
+
/*
1081
+
* Create instance directory with appropriate security
1082
+
* contexts, owner, group and mode bits.
1083
+
*/
1084
+
#ifdef WITH_SELINUX
1085
+
- retval = create_instance(polyptr, inst_dir, &statbuf, instcontext,
1086
+
- origcontext, idata);
1087
+
+ retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext,
1088
+
+ origcontext, idata);
1089
+
#else
1090
+
- retval = create_instance(polyptr, inst_dir, &statbuf, idata);
1091
+
+ retval = create_instance(polyptr, dfd_iparent, &statbuf, idata);
1092
+
#endif
1093
+
1094
+
if (retval == PAM_IGNORE) {
1095
+
@@ -1733,19 +1926,48 @@ static int ns_setup(struct polydir_s *polyptr,
1096
+
goto error_out;
1097
+
}
1098
+
1099
+
+ /*
1100
+
+ * Instead of getting a new secure fd, we reuse the fd opened on directory
1101
+
+ * polyptr->instance_parent to ensure we are working on the same dir as
1102
+
+ * previously, and thus ensure that previous checks (e.g. check_inst_parent())
1103
+
+ * are still relevant.
1104
+
+ */
1105
+
+ dfd_ipath = openat(dfd_iparent, polyptr->instname,
1106
+
+ O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
1107
+
+ if (dfd_ipath == -1) {
1108
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m",
1109
+
+ polyptr->instname);
1110
+
+ goto error_out;
1111
+
+ }
1112
+
+
1113
+
+ if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) {
1114
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath");
1115
+
+ goto error_out;
1116
+
+ }
1117
+
+
1118
+
+ if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) {
1119
+
+ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir");
1120
+
+ goto error_out;
1121
+
+ }
1122
+
+
1123
+
/*
1124
+
* Bind mount instance directory on top of the polyinstantiated
1125
+
* directory to provide an instance of polyinstantiated directory
1126
+
* based on polyinstantiated method.
1127
+
+ *
1128
+
+ * Operates on magic links created from two fd obtained securely
1129
+
+ * to protect against race conditions and symlink attacks. Indeed,
1130
+
+ * the source and destination can be in a user controled path.
1131
+
*/
1132
+
- if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) {
1133
+
- pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m",
1134
+
- inst_dir, polyptr->dir);
1135
+
+ if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) {
1136
+
+ pam_syslog(idata->pamh, LOG_ERR,
1137
+
+ "Error mounting %s on %s (%s on %s), %m",
1138
+
+ s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir);
1139
+
goto error_out;
1140
+
}
1141
+
1142
+
if (!(polyptr->flags & POLYDIR_NOINIT))
1143
+
- retval = inst_init(polyptr, inst_dir, idata, newdir);
1144
+
+ retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir);
1145
+
1146
+
goto cleanup;
1147
+
1148
+
@@ -1757,8 +1979,12 @@ error_out:
1149
+
retval = PAM_SESSION_ERR;
1150
+
1151
+
cleanup:
1152
+
- free(inst_dir);
1153
+
- free(instname);
1154
+
+ if (dfd_iparent != -1)
1155
+
+ close(dfd_iparent);
1156
+
+ if (dfd_ipath != -1)
1157
+
+ close(dfd_ipath);
1158
+
+ if (dfd_pptrdir != -1)
1159
+
+ close(dfd_pptrdir);
1160
+
#ifdef WITH_SELINUX
1161
+
freecon(instcontext);
1162
+
freecon(origcontext);
1163
+
@@ -1797,6 +2023,7 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1164
+
{
1165
+
struct polydir_s *pptr;
1166
+
pid_t rc, pid;
1167
+
+ int dfd = -1;
1168
+
struct sigaction newsa, oldsa;
1169
+
int status;
1170
+
1171
+
@@ -1808,20 +2035,40 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1172
+
}
1173
+
1174
+
for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
1175
+
- if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) {
1176
+
+ if (pptr->method == TMPDIR) {
1177
+
+
1178
+
+ dfd = secure_opendir_stateless(pptr->instance_parent);
1179
+
+ if (dfd == -1)
1180
+
+ continue;
1181
+
+
1182
+
+ if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
1183
+
+ close(dfd);
1184
+
+ continue;
1185
+
+ }
1186
+
+
1187
+
pid = fork();
1188
+
if (pid == 0) {
1189
+
- static char *envp[] = { NULL };
1190
+
+ static char *envp[] = { NULL };
1191
+
#ifdef WITH_SELINUX
1192
+
- if (idata->flags & PAMNS_SELINUX_ENABLED) {
1193
+
+ if (idata->flags & PAMNS_SELINUX_ENABLED) {
1194
+
if (setexeccon(NULL) < 0)
1195
+
_exit(1);
1196
+
}
1197
+
#endif
1198
+
+ if (fchdir(dfd) == -1) {
1199
+
+ pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m",
1200
+
+ pptr->instance_absolute);
1201
+
+ _exit(1);
1202
+
+ }
1203
+
+
1204
+
close_fds_pre_exec(idata);
1205
+
- if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0)
1206
+
- _exit(1);
1207
+
- } else if (pid > 0) {
1208
+
+ execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp);
1209
+
+ _exit(1);
1210
+
+ } else if (pid > 0) {
1211
+
+
1212
+
+ if (dfd != -1)
1213
+
+ close(dfd);
1214
+
+
1215
+
while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
1216
+
(errno == EINTR));
1217
+
if (rc == (pid_t)-1) {
1218
+
@@ -1834,6 +2081,10 @@ static int cleanup_tmpdirs(struct instance_data *idata)
1219
+
"Error removing %s", pptr->instance_prefix);
1220
+
}
1221
+
} else if (pid < 0) {
1222
+
+
1223
+
+ if (dfd != -1)
1224
+
+ close(dfd);
1225
+
+
1226
+
pam_syslog(idata->pamh, LOG_ERR,
1227
+
"Cannot fork to cleanup temporary directory, %m");
1228
+
rc = PAM_SESSION_ERR;
1229
+
@@ -1857,6 +2108,7 @@ out:
1230
+
static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
1231
+
{
1232
+
int retval = 0, need_poly = 0, changing_dir = 0;
1233
+
+ int dfd = -1;
1234
+
char *cptr, *fptr, poly_parent[PATH_MAX];
1235
+
struct polydir_s *pptr;
1236
+
1237
+
@@ -1972,20 +2224,28 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
1238
+
strcpy(poly_parent, "/");
1239
+
else if (cptr)
1240
+
*cptr = '\0';
1241
+
- if (chdir(poly_parent) < 0) {
1242
+
+
1243
+
+ dfd = secure_opendir_stateless(poly_parent);
1244
+
+ if (dfd == -1) {
1245
+
pam_syslog(idata->pamh, LOG_ERR,
1246
+
- "Can't chdir to %s, %m", poly_parent);
1247
+
+ "Failed opening %s to fchdir: %m", poly_parent);
1248
+
}
1249
+
+ else if (fchdir(dfd) == -1) {
1250
+
+ pam_syslog(idata->pamh, LOG_ERR,
1251
+
+ "Failed fchdir to %s: %m", poly_parent);
1252
+
+ }
1253
+
+ if (dfd != -1)
1254
+
+ close(dfd);
1255
+
}
1256
+
1257
+
- if (umount(pptr->rdir) < 0) {
1258
+
- int saved_errno = errno;
1259
+
- pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1260
+
- pptr->rdir);
1261
+
- if (saved_errno != EINVAL) {
1262
+
- retval = PAM_SESSION_ERR;
1263
+
- goto out;
1264
+
- }
1265
+
+ if (secure_umount(pptr->rdir) < 0) {
1266
+
+ int saved_errno = errno;
1267
+
+ pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1268
+
+ pptr->rdir);
1269
+
+ if (saved_errno != EINVAL) {
1270
+
+ retval = PAM_SESSION_ERR;
1271
+
+ goto out;
1272
+
+ }
1273
+
} else if (idata->flags & PAMNS_DEBUG)
1274
+
pam_syslog(idata->pamh, LOG_DEBUG, "Umount succeeded %s",
1275
+
pptr->rdir);
1276
+
@@ -2048,7 +2308,7 @@ static int orig_namespace(struct instance_data *idata)
1277
+
"Unmounting instance dir for user %d & dir %s",
1278
+
idata->uid, pptr->dir);
1279
+
1280
+
- if (umount(pptr->dir) < 0) {
1281
+
+ if (secure_umount(pptr->dir) < 0) {
1282
+
pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1283
+
pptr->dir);
1284
+
return PAM_SESSION_ERR;
1285
+
diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h
1286
+
index fd393d17..b1c31e1f 100644
1287
+
--- a/modules/pam_namespace/pam_namespace.h
1288
+
+++ b/modules/pam_namespace/pam_namespace.h
1289
+
@@ -126,6 +126,13 @@
1290
+
#define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data"
1291
+
#define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data"
1292
+
1293
+
+/*
1294
+
+ * Operation mode for function secure_opendir()
1295
+
+ */
1296
+
+#define SECURE_OPENDIR_PROTECT 0x00000001
1297
+
+#define SECURE_OPENDIR_MKDIR 0x00000002
1298
+
+#define SECURE_OPENDIR_FULL_FD 0x00000004
1299
+
+
1300
+
/*
1301
+
* Polyinstantiation method options, based on user, security context
1302
+
* or both
1303
+
@@ -163,6 +170,9 @@ struct polydir_s {
1304
+
char dir[PATH_MAX]; /* directory to polyinstantiate */
1305
+
char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */
1306
+
char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */
1307
+
+ char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */
1308
+
+ char instance_parent[PATH_MAX]; /* parent dir of the instance dir */
1309
+
+ char *instname; /* last segment of the path to the instance dir */
1310
+
enum polymethod method; /* method used to polyinstantiate */
1311
+
unsigned int num_uids; /* number of override uids */
1312
+
uid_t *uid; /* list of override uids */
1313
+
--
1314
+
2.49.0
1315
+
+8
pkgs/by-name/li/linux-pam/package.nix
···
3
stdenv,
4
buildPackages,
5
fetchurl,
0
6
flex,
7
db4,
8
gettext,
···
24
25
patches = [
26
./suid-wrapper-path.patch
0
0
0
0
0
0
0
27
];
28
29
# Case-insensitivity workaround for https://github.com/linux-pam/linux-pam/issues/569
···
3
stdenv,
4
buildPackages,
5
fetchurl,
6
+
fetchpatch,
7
flex,
8
db4,
9
gettext,
···
25
26
patches = [
27
./suid-wrapper-path.patch
28
+
# required for fixing CVE-2025-6020
29
+
(fetchpatch {
30
+
url = "https://github.com/linux-pam/linux-pam/commit/10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc.patch";
31
+
hash = "sha256-VS3D3wUbDxDXRriIuEvvgeZixzDA58EfiLygfFeisGg=";
32
+
})
33
+
# Manually cherry-picked from 475bd60c552b98c7eddb3270b0b4196847c0072e
34
+
./CVE-2025-6020.patch
35
];
36
37
# Case-insensitivity workaround for https://github.com/linux-pam/linux-pam/issues/569