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