linux-pam: apply patch for CVE-2025-6020

+1323
+1315
pkgs/by-name/li/linux-pam/CVE-2025-6020.patch
··· 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 3 stdenv, 4 4 buildPackages, 5 5 fetchurl, 6 + fetchpatch, 6 7 flex, 7 8 db4, 8 9 gettext, ··· 24 25 25 26 patches = [ 26 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 27 35 ]; 28 36 29 37 # Case-insensitivity workaround for https://github.com/linux-pam/linux-pam/issues/569