From bdaba80a54a96ff340cfa559d2d2d32868997a52 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Wed, 2 Jul 2025 09:48:31 +0800 Subject: [PATCH] [CVE]update to pam-1.3.1-37 to #ICJB56 update to pam-1.3.1-37 for CVE-2025-6020 Project: TC2024080204 Signed-off-by: Jacob Wang --- dist | 2 +- pam-1.3.1-pam-inline-pam-asprintf.patch | 67 ++ pam-1.3.1-pam-namespace-rebase.patch | 1156 +++++++++++++++++++++++ pam.spec | 12 +- 4 files changed, 1235 insertions(+), 2 deletions(-) create mode 100644 pam-1.3.1-pam-inline-pam-asprintf.patch create mode 100644 pam-1.3.1-pam-namespace-rebase.patch diff --git a/dist b/dist index 9c0e36e..1fe92cf 100644 --- a/dist +++ b/dist @@ -1 +1 @@ -an8 +an8_10 diff --git a/pam-1.3.1-pam-inline-pam-asprintf.patch b/pam-1.3.1-pam-inline-pam-asprintf.patch new file mode 100644 index 0000000..c92c345 --- /dev/null +++ b/pam-1.3.1-pam-inline-pam-asprintf.patch @@ -0,0 +1,67 @@ +diff -up Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.pam-inline-pam-asprintf Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h +--- Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.pam-inline-pam-asprintf 2025-06-17 10:12:31.039519165 +0200 ++++ Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h 2025-06-17 10:17:40.313892315 +0200 +@@ -15,6 +15,12 @@ + # define PAM_CLANG_PREREQ(maj, min) 0 + #endif + ++#if PAM_GNUC_PREREQ(3, 0) ++# define PAM_ATTRIBUTE_MALLOC __attribute__((__malloc__)) ++#else ++# define PAM_ATTRIBUTE_MALLOC /* empty */ ++#endif ++ + #if PAM_GNUC_PREREQ(4, 6) + # define DIAG_PUSH_IGNORE_CAST_QUAL \ + _Pragma("GCC diagnostic push"); \ +diff -up Linux-PAM-1.3.1/libpam/include/pam_inline.h.pam-inline-pam-asprintf Linux-PAM-1.3.1/libpam/include/pam_inline.h +--- Linux-PAM-1.3.1/libpam/include/pam_inline.h.pam-inline-pam-asprintf 2025-06-17 10:12:31.039639983 +0200 ++++ Linux-PAM-1.3.1/libpam/include/pam_inline.h 2025-06-17 10:19:03.453146173 +0200 +@@ -9,6 +9,9 @@ + #define PAM_INLINE_H + + #include "pam_cc_compat.h" ++#include ++#include ++#include + #include + + /* +@@ -64,4 +67,37 @@ pam_str_skip_icase_prefix_len(const char + #define pam_str_skip_icase_prefix(str_, prefix_) \ + pam_str_skip_icase_prefix_len((str_), (prefix_), sizeof(prefix_) - 1 + PAM_MUST_BE_ARRAY(prefix_)) + ++static inline char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC ++pam_asprintf(const char *fmt, ...) ++{ ++ int rc; ++ char *res; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ rc = vasprintf(&res, fmt, ap); ++ va_end(ap); ++ ++ return rc < 0 ? NULL : res; ++} ++ ++static inline int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3)) ++pam_snprintf(char *str, size_t size, const char *fmt, ...) ++{ ++ int rc; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ rc = vsnprintf(str, size, fmt, ap); ++ va_end(ap); ++ ++ if (rc < 0 || (unsigned int) rc >= size) ++ return -1; ++ return rc; ++} ++ ++#define pam_sprintf(str_, fmt_, ...) \ ++ pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \ ++ ##__VA_ARGS__) ++ + #endif /* PAM_INLINE_H */ diff --git a/pam-1.3.1-pam-namespace-rebase.patch b/pam-1.3.1-pam-namespace-rebase.patch new file mode 100644 index 0000000..356f54e --- /dev/null +++ b/pam-1.3.1-pam-namespace-rebase.patch @@ -0,0 +1,1156 @@ +diff -up Linux-PAM-1.3.1/modules/pam_namespace/namespace.init.pam-namespace-rebase Linux-PAM-1.3.1/modules/pam_namespace/namespace.init +--- Linux-PAM-1.3.1/modules/pam_namespace/namespace.init.pam-namespace-rebase 2017-02-10 11:10:15.000000000 +0100 ++++ Linux-PAM-1.3.1/modules/pam_namespace/namespace.init 2025-06-17 12:50:04.962542096 +0200 +@@ -15,8 +15,8 @@ if [ "$3" = 1 ]; then + gid=$(echo "$passwd" | cut -f4 -d":") + cp -rT /etc/skel "$homedir" + chown -R "$user":"$gid" "$homedir" +- mask=$(awk '/^UMASK/{gsub("#.*$", "", $2); print $2; exit}' /etc/login.defs) +- mode=$(printf "%o" $((0777 & ~$mask))) ++ mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs) ++ mode=$(printf "%o" $((0777 & ~mask))) + chmod ${mode:-700} "$homedir" + [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir" + fi +diff -up Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.c.pam-namespace-rebase Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.c +--- Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.c.pam-namespace-rebase 2025-06-17 12:50:04.954195973 +0200 ++++ Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.c 2025-06-17 12:51:08.445616548 +0200 +@@ -34,9 +34,250 @@ + + #define _ATFILE_SOURCE + ++#include "config.h" ++#include ++#include "pam_cc_compat.h" ++#include "pam_inline.h" + #include "pam_namespace.h" + #include "argv_parse.h" + ++/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */ ++static const char *base_name(const char *path) ++{ ++ const char *base = strrchr(path, '/'); ++ return base ? base+1 : path; ++} ++ ++static int ++compare_filename(const void *a, const void *b) ++{ ++ return strcmp(base_name(* (char * const *) a), ++ base_name(* (char * const *) b)); ++} ++ ++static void close_fds_pre_exec(struct instance_data *idata) ++{ ++ if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD, ++ PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) { ++ _exit(1); ++ } ++} ++ ++static void ++strip_trailing_slashes(char *str) ++{ ++ char *p = str + strlen(str); ++ ++ while (--p > str && *p == '/') ++ *p = '\0'; ++} ++ ++static int protect_mount(int dfd, const char *path, struct instance_data *idata) ++{ ++ struct protect_dir_s *dir = idata->protect_dirs; ++ char tmpbuf[64]; ++ ++ while (dir != NULL) { ++ if (strcmp(path, dir->dir) == 0) { ++ return 0; ++ } ++ dir = dir->next; ++ } ++ ++ if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0) ++ return -1; ++ ++ dir = calloc(1, sizeof(*dir)); ++ ++ if (dir == NULL) { ++ return -1; ++ } ++ ++ dir->dir = strdup(path); ++ ++ if (dir->dir == NULL) { ++ free(dir); ++ return -1; ++ } ++ ++ if (idata->flags & PAMNS_DEBUG) { ++ pam_syslog(idata->pamh, LOG_INFO, ++ "Protect mount of %s over itself", path); ++ } ++ ++ if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { ++ int save_errno = errno; ++ pam_syslog(idata->pamh, LOG_ERR, ++ "Protect mount of %s failed: %m", tmpbuf); ++ free(dir->dir); ++ free(dir); ++ errno = save_errno; ++ return -1; ++ } ++ ++ dir->next = idata->protect_dirs; ++ idata->protect_dirs = dir; ++ ++ return 0; ++} ++ ++static int protect_dir(const char *path, mode_t mode, int do_mkdir, ++ struct instance_data *idata) ++{ ++ char *p = strdup(path); ++ char *d; ++ char *dir = p; ++ int dfd = AT_FDCWD; ++ int dfd_next; ++ int save_errno; ++ int flags = O_RDONLY | O_DIRECTORY; ++ int rv = -1; ++ struct stat st; ++ ++ if (p == NULL) { ++ return -1; ++ } ++ ++ if (*dir == '/') { ++ dfd = open("/", flags); ++ if (dfd == -1) { ++ goto error; ++ } ++ dir++; /* assume / is safe */ ++ } ++ ++ while ((d=strchr(dir, '/')) != NULL) { ++ *d = '\0'; ++ dfd_next = openat(dfd, dir, flags); ++ if (dfd_next == -1) { ++ goto error; ++ } ++ ++ if (dfd != AT_FDCWD) ++ close(dfd); ++ dfd = dfd_next; ++ ++ if (fstat(dfd, &st) != 0) { ++ goto error; ++ } ++ ++ if (flags & O_NOFOLLOW) { ++ /* we are inside user-owned dir - protect */ ++ if (protect_mount(dfd, p, idata) == -1) ++ goto error; ++ } else if (st.st_uid != 0 || st.st_gid != 0 || ++ (st.st_mode & S_IWOTH)) { ++ /* do not follow symlinks on subdirectories */ ++ flags |= O_NOFOLLOW; ++ } ++ ++ *d = '/'; ++ dir = d + 1; ++ } ++ ++ rv = openat(dfd, dir, flags); ++ ++ if (rv == -1) { ++ if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { ++ goto error; ++ } ++ rv = openat(dfd, dir, flags); ++ } ++ ++ if (flags & O_NOFOLLOW) { ++ /* we are inside user-owned dir - protect */ ++ if (protect_mount(rv, p, idata) == -1) { ++ save_errno = errno; ++ close(rv); ++ rv = -1; ++ errno = save_errno; ++ } ++ } ++ ++error: ++ save_errno = errno; ++ free(p); ++ if (dfd != AT_FDCWD && dfd >= 0) ++ close(dfd); ++ errno = save_errno; ++ ++ return rv; ++} ++ ++/* Evaluating a list of files which have to be parsed in the right order: ++ * ++ * - If etc/security/namespace.d/@filename@.conf exists, then ++ * %vendordir%/security/namespace.d/@filename@.conf should not be used. ++ * - All files in both namespace.d directories are sorted by their @filename@.conf in ++ * lexicographic order regardless of which of the directories they reside in. */ ++static char **read_namespace_dir(struct instance_data *idata) ++{ ++ glob_t globbuf; ++ size_t i=0; ++ int glob_rv = glob(NAMESPACE_D_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); ++ char **file_list; ++ size_t file_list_size = glob_rv == 0 ? globbuf.gl_pathc : 0; ++ ++#ifdef VENDOR_NAMESPACE_D_GLOB ++ glob_t globbuf_vendor; ++ int glob_rv_vendor = glob(VENDOR_NAMESPACE_D_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf_vendor); ++ if (glob_rv_vendor == 0) ++ file_list_size += globbuf_vendor.gl_pathc; ++#endif ++ file_list = malloc((file_list_size + 1) * sizeof(char*)); ++ if (file_list == NULL) { ++ pam_syslog(idata->pamh, LOG_ERR, "Cannot allocate memory for file list: %m"); ++#ifdef VENDOR_NAMESPACE_D_GLOB ++ if (glob_rv_vendor == 0) ++ globfree(&globbuf_vendor); ++#endif ++ if (glob_rv == 0) ++ globfree(&globbuf); ++ return NULL; ++ } ++ ++ if (glob_rv == 0) { ++ for (i = 0; i < globbuf.gl_pathc; i++) { ++ file_list[i] = strdup(globbuf.gl_pathv[i]); ++ if (file_list[i] == NULL) { ++ pam_syslog(idata->pamh, LOG_ERR, "strdup failed: %m"); ++ break; ++ } ++ } ++ } ++#ifdef VENDOR_NAMESPACE_D_GLOB ++ if (glob_rv_vendor == 0) { ++ for (size_t j = 0; j < globbuf_vendor.gl_pathc; j++) { ++ if (glob_rv == 0 && globbuf.gl_pathc > 0) { ++ int double_found = 0; ++ for (size_t k = 0; k < globbuf.gl_pathc; k++) { ++ if (strcmp(base_name(globbuf.gl_pathv[k]), ++ base_name(globbuf_vendor.gl_pathv[j])) == 0) { ++ double_found = 1; ++ break; ++ } ++ } ++ if (double_found) ++ continue; ++ } ++ file_list[i] = strdup(globbuf_vendor.gl_pathv[j]); ++ if (file_list[i] == NULL) { ++ pam_syslog(idata->pamh, LOG_ERR, "strdup failed: %m"); ++ break; ++ } ++ i++; ++ } ++ globfree(&globbuf_vendor); ++ } ++#endif ++ file_list[i] = NULL; ++ qsort(file_list, i, sizeof(char *), compare_filename); ++ if (glob_rv == 0) ++ globfree(&globbuf); ++ ++ return file_list; ++} ++ + /* + * Adds an entry for a polyinstantiated directory to the linked list of + * polyinstantiated directories. It is called from process_line() while +@@ -106,7 +347,7 @@ static void cleanup_protect_data(pam_han + unprotect_dirs(data); + } + +-static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[]) ++static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[]) + { + const char *src = orig; + char *dst; +@@ -117,7 +358,7 @@ static char *expand_variables(const char + if (*src == '$') { + int i; + for (i = 0; var_names[i]; i++) { +- int namelen = strlen(var_names[i]); ++ size_t namelen = strlen(var_names[i]); + if (strncmp(var_names[i], src+1, namelen) == 0) { + dstlen += strlen(var_values[i]) - 1; /* $ */ + src += namelen; +@@ -135,7 +376,7 @@ static char *expand_variables(const char + if (c == '$') { + int i; + for (i = 0; var_names[i]; i++) { +- int namelen = strlen(var_names[i]); ++ size_t namelen = strlen(var_names[i]); + if (strncmp(var_names[i], src+1, namelen) == 0) { + dst = stpcpy(dst, var_values[i]); + --dst; +@@ -219,8 +460,7 @@ static int parse_iscript_params(char *pa + + if (*params != '\0') { + if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */ +- if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1) +- return -1; ++ poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params); + } else { + poly->init_script = strdup(params); + } +@@ -259,7 +499,7 @@ static int filter_mntopts(const char *op + + do { + size_t len; +- int i; ++ unsigned int i; + + end = strchr(opts, ','); + if (end == NULL) { +@@ -268,7 +508,7 @@ static int filter_mntopts(const char *op + len = end - opts; + } + +- for (i = 0; i < (int)(sizeof(mntflags)/sizeof(mntflags[0])); i++) { ++ for (i = 0; i < PAM_ARRAY_SIZE(mntflags); i++) { + if (mntflags[i].len != len) + continue; + if (memcmp(mntflags[i].name, opts, len) == 0) { +@@ -302,9 +542,9 @@ static int parse_method(char *method, st + { + enum polymethod pm; + char *sptr = NULL; +- static const char *method_names[] = { "user", "context", "level", "tmpdir", ++ static const char *const method_names[] = { "user", "context", "level", "tmpdir", + "tmpfs", NULL }; +- static const char *flag_names[] = { "create", "noinit", "iscript", ++ static const char *const flag_names[] = { "create", "noinit", "iscript", + "shared", "mntopts", NULL }; + static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT, + POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS }; +@@ -329,7 +569,7 @@ static int parse_method(char *method, st + + while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) { + for (i = 0; flag_names[i]; i++) { +- int namelen = strlen(flag_names[i]); ++ size_t namelen = strlen(flag_names[i]); + + if (strncmp(flag, flag_names[i], namelen) == 0) { + poly->flags |= flag_values[i]; +@@ -375,27 +615,27 @@ static int parse_method(char *method, st + * of the namespace configuration file. It skips over comments and incomplete + * or malformed lines. It processes a valid line with information on + * polyinstantiating a directory by populating appropriate fields of a +- * polyinstatiated directory structure and then calling add_polydir_entry to ++ * polyinstantiated directory structure and then calling add_polydir_entry to + * add that entry to the linked list of polyinstantiated directories. + */ + static int process_line(char *line, const char *home, const char *rhome, + struct instance_data *idata) + { + char *dir = NULL, *instance_prefix = NULL, *rdir = NULL; ++ const char *config_dir, *config_instance_prefix; + char *method, *uids; + char *tptr; + struct polydir_s *poly; + int retval = 0; + char **config_options = NULL; +- static const char *var_names[] = {"HOME", "USER", NULL}; ++ static const char *const var_names[] = {"HOME", "USER", NULL}; + const char *var_values[] = {home, idata->user}; + const char *rvar_values[] = {rhome, idata->ruser}; +- int len; + + /* + * skip the leading white space + */ +- while (*line && isspace(*line)) ++ while (*line && isspace((unsigned char)*line)) + line++; + + /* +@@ -431,22 +671,19 @@ static int process_line(char *line, cons + goto erralloc; + } + +- dir = config_options[0]; +- if (dir == NULL) { ++ config_dir = config_options[0]; ++ if (config_dir == NULL) { + pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); + goto skipping; + } +- instance_prefix = config_options[1]; +- if (instance_prefix == NULL) { ++ config_instance_prefix = config_options[1]; ++ if (config_instance_prefix == NULL) { + pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix"); +- instance_prefix = NULL; + goto skipping; + } + method = config_options[2]; + if (method == NULL) { + pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); +- instance_prefix = NULL; +- dir = NULL; + goto skipping; + } + +@@ -461,19 +698,16 @@ static int process_line(char *line, cons + /* + * Expand $HOME and $USER in poly dir and instance dir prefix + */ +- if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) { +- instance_prefix = NULL; +- dir = NULL; ++ if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) { + goto erralloc; + } + +- if ((dir=expand_variables(dir, var_names, var_values)) == NULL) { +- instance_prefix = NULL; ++ if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) { + goto erralloc; + } + +- if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values)) +- == NULL) { ++ if ((instance_prefix = expand_variables(config_instance_prefix, ++ var_names, var_values)) == NULL) { + goto erralloc; + } + +@@ -483,15 +717,8 @@ static int process_line(char *line, cons + pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix); + } + +- len = strlen(dir); +- if (len > 0 && dir[len-1] == '/') { +- dir[len-1] = '\0'; +- } +- +- len = strlen(rdir); +- if (len > 0 && rdir[len-1] == '/') { +- rdir[len-1] = '\0'; +- } ++ strip_trailing_slashes(dir); ++ strip_trailing_slashes(rdir); + + if (dir[0] == '\0' || rdir[0] == '\0') { + pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir"); +@@ -502,26 +729,19 @@ static int process_line(char *line, cons + * Populate polyinstantiated directory structure with appropriate + * pathnames and the method with which to polyinstantiate. + */ +- if (strlen(dir) >= sizeof(poly->dir) +- || strlen(rdir) >= sizeof(poly->rdir) +- || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) { +- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); +- goto skipping; +- } +- strcpy(poly->dir, dir); +- strcpy(poly->rdir, rdir); +- strcpy(poly->instance_prefix, instance_prefix); +- + if (parse_method(method, poly, idata) != 0) { + goto skipping; + } + +- if (poly->method == TMPDIR) { +- if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) { +- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); +- goto skipping; +- } +- strcat(poly->instance_prefix, "XXXXXX"); ++#define COPY_STR(dst, src, apd) \ ++ pam_sprintf((dst), "%s%s", (src), (apd)) ++ ++ if (COPY_STR(poly->dir, dir, "") < 0 ++ || COPY_STR(poly->rdir, rdir, "") < 0 ++ || COPY_STR(poly->instance_prefix, instance_prefix, ++ poly->method == TMPDIR ? "XXXXXX" : "") < 0) { ++ pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); ++ goto skipping; + } + + /* +@@ -545,7 +765,7 @@ static int process_line(char *line, cons + if (uids) { + uid_t *uidptr; + const char *ustr, *sstr; +- int count, i; ++ size_t count, i; + + if (*uids == '~') { + poly->flags |= POLYDIR_EXCLUSIVE; +@@ -554,8 +774,13 @@ static int process_line(char *line, cons + for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++) + sstr = strchr(ustr, ','); + ++ if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { ++ pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration"); ++ goto skipping; ++ } ++ + poly->num_uids = count; +- poly->uid = (uid_t *) malloc(count * sizeof (uid_t)); ++ poly->uid = malloc(count * sizeof (uid_t)); + uidptr = poly->uid; + if (uidptr == NULL) { + goto erralloc; +@@ -622,8 +847,6 @@ static int parse_config_file(struct inst + char *line; + int retval; + size_t len = 0; +- glob_t globbuf; +- const char *oldlocale; + size_t n; + + /* +@@ -662,13 +885,16 @@ static int parse_config_file(struct inst + * process_line to process each line. + */ + +- memset(&globbuf, '\0', sizeof(globbuf)); +- oldlocale = setlocale(LC_COLLATE, "C"); +- glob(NAMESPACE_D_GLOB, 0, NULL, &globbuf); +- if (oldlocale != NULL) +- setlocale(LC_COLLATE, oldlocale); +- + confname = PAM_NAMESPACE_CONFIG; ++#ifdef VENDOR_PAM_NAMESPACE_CONFIG ++ /* Check whether PAM_NAMESPACE_CONFIG file is available. ++ * If it does not exist, fall back to VENDOR_PAM_NAMESPACE_CONFIG file. */ ++ struct stat buffer; ++ if (stat(confname, &buffer) != 0 && errno == ENOENT) { ++ confname = VENDOR_PAM_NAMESPACE_CONFIG; ++ } ++#endif ++ char **filename_list = read_namespace_dir(idata); + n = 0; + for (;;) { + if (idata->flags & PAMNS_DEBUG) +@@ -678,7 +904,6 @@ static int parse_config_file(struct inst + if (fil == NULL) { + pam_syslog(idata->pamh, LOG_ERR, "Error opening config file %s", + confname); +- globfree(&globbuf); + free(rhome); + free(home); + return PAM_SERVICE_ERR; +@@ -696,7 +921,6 @@ static int parse_config_file(struct inst + "Error processing conf file %s line %s", confname, line); + fclose(fil); + free(line); +- globfree(&globbuf); + free(rhome); + free(home); + return PAM_SERVICE_ERR; +@@ -705,14 +929,18 @@ static int parse_config_file(struct inst + fclose(fil); + free(line); + +- if (n >= globbuf.gl_pathc) ++ if (filename_list == NULL || filename_list[n] == NULL) + break; + +- confname = globbuf.gl_pathv[n]; +- n++; ++ confname = filename_list[n++]; ++ } ++ ++ if (filename_list != NULL) { ++ for (size_t i = 0; filename_list[i] != NULL; i++) ++ free(filename_list[i]); ++ free(filename_list); + } + +- globfree(&globbuf); + free(rhome); + free(home); + +@@ -738,7 +966,7 @@ static int parse_config_file(struct inst + + + /* +- * This funtion returns true if a given uid is present in the polyinstantiated ++ * This function returns true if a given uid is present in the polyinstantiated + * directory's list of override uids. If the uid is one of the override + * uids for the polyinstantiated directory, polyinstantiation is not + * performed for that user for that directory. +@@ -795,11 +1023,11 @@ static char *md5hash(const char *instnam + + #ifdef WITH_SELINUX + static int form_context(const struct polydir_s *polyptr, +- security_context_t *i_context, security_context_t *origcon, ++ char **i_context, char **origcon, + struct instance_data *idata) + { + int rc = PAM_SUCCESS; +- security_context_t scon = NULL; ++ char *scon = NULL; + security_class_t tclass; + + /* +@@ -842,6 +1070,12 @@ static int form_context(const struct pol + + if (polyptr->method == CONTEXT) { + tclass = string_to_security_class("dir"); ++ if (tclass == 0) { ++ pam_syslog(idata->pamh, LOG_ERR, ++ "Error getting dir security class"); ++ freecon(scon); ++ return PAM_SESSION_ERR; ++ } + + if (security_compute_member(scon, *origcon, tclass, + i_context) < 0) { +@@ -878,7 +1112,7 @@ static int form_context(const struct pol + goto fail; + } + if (context_range_set(fcontext, context_range_get(scontext)) != 0) { +- pam_syslog(idata->pamh, LOG_ERR, "Unable to set MLS Componant of context"); ++ pam_syslog(idata->pamh, LOG_ERR, "Unable to set MLS Component of context"); + goto fail; + } + *i_context=strdup(context_str(fcontext)); +@@ -895,6 +1129,7 @@ static int form_context(const struct pol + return rc; + } + /* Should never get here */ ++ freecon(scon); + return PAM_SUCCESS; + } + #endif +@@ -908,7 +1143,7 @@ static int form_context(const struct pol + */ + #ifdef WITH_SELINUX + static int poly_name(const struct polydir_s *polyptr, char **i_name, +- security_context_t *i_context, security_context_t *origcon, ++ char **i_context, char **origcon, + struct instance_data *idata) + #else + static int poly_name(const struct polydir_s *polyptr, char **i_name, +@@ -919,7 +1154,7 @@ static int poly_name(const struct polydi + char *hash = NULL; + enum polymethod pm; + #ifdef WITH_SELINUX +- security_context_t rawcon = NULL; ++ char *rawcon = NULL; + #endif + + *i_name = NULL; +@@ -956,10 +1191,8 @@ static int poly_name(const struct polydi + + switch (pm) { + case USER: +- if (asprintf(i_name, "%s", idata->user) < 0) { +- *i_name = NULL; ++ if ((*i_name = strdup(idata->user)) == NULL) + goto fail; +- } + break; + + #ifdef WITH_SELINUX +@@ -969,17 +1202,12 @@ static int poly_name(const struct polydi + pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context"); + goto fail; + } +- if (polyptr->flags & POLYDIR_SHARED) { +- if (asprintf(i_name, "%s", rawcon) < 0) { +- *i_name = NULL; +- goto fail; +- } +- } else { +- if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { +- *i_name = NULL; +- goto fail; +- } +- } ++ if (polyptr->flags & POLYDIR_SHARED) ++ *i_name = strdup(rawcon); ++ else ++ *i_name = pam_asprintf("%s_%s", rawcon, idata->user); ++ if (*i_name == NULL) ++ goto fail; + break; + + #endif /* WITH_SELINUX */ +@@ -1009,11 +1237,12 @@ static int poly_name(const struct polydi + *i_name = hash; + hash = NULL; + } else { +- char *newname; +- if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash), +- *i_name, hash) < 0) { ++ char *newname = ++ pam_asprintf("%.*s_%s", ++ NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash), ++ *i_name, hash); ++ if (newname == NULL) + goto fail; +- } + free(*i_name); + *i_name = newname; + } +@@ -1038,137 +1267,6 @@ fail: + return rc; + } + +-static int protect_mount(int dfd, const char *path, struct instance_data *idata) +-{ +- struct protect_dir_s *dir = idata->protect_dirs; +- char tmpbuf[64]; +- +- while (dir != NULL) { +- if (strcmp(path, dir->dir) == 0) { +- return 0; +- } +- dir = dir->next; +- } +- +- dir = calloc(1, sizeof(*dir)); +- +- if (dir == NULL) { +- return -1; +- } +- +- dir->dir = strdup(path); +- +- if (dir->dir == NULL) { +- free(dir); +- return -1; +- } +- +- snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd); +- +- if (idata->flags & PAMNS_DEBUG) { +- pam_syslog(idata->pamh, LOG_INFO, +- "Protect mount of %s over itself", path); +- } +- +- if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { +- int save_errno = errno; +- pam_syslog(idata->pamh, LOG_ERR, +- "Protect mount of %s failed: %m", tmpbuf); +- free(dir->dir); +- free(dir); +- errno = save_errno; +- return -1; +- } +- +- dir->next = idata->protect_dirs; +- idata->protect_dirs = dir; +- +- return 0; +-} +- +-static int protect_dir(const char *path, mode_t mode, int do_mkdir, +- struct instance_data *idata) +-{ +- char *p = strdup(path); +- char *d; +- char *dir = p; +- int dfd = AT_FDCWD; +- int dfd_next; +- int save_errno; +- int flags = O_RDONLY | O_DIRECTORY; +- int rv = -1; +- struct stat st; +- +- if (p == NULL) { +- goto error; +- } +- +- if (*dir == '/') { +- dfd = open("/", flags); +- if (dfd == -1) { +- goto error; +- } +- dir++; /* assume / is safe */ +- } +- +- while ((d=strchr(dir, '/')) != NULL) { +- *d = '\0'; +- dfd_next = openat(dfd, dir, flags); +- if (dfd_next == -1) { +- goto error; +- } +- +- if (dfd != AT_FDCWD) +- close(dfd); +- dfd = dfd_next; +- +- if (fstat(dfd, &st) != 0) { +- goto error; +- } +- +- if (flags & O_NOFOLLOW) { +- /* we are inside user-owned dir - protect */ +- if (protect_mount(dfd, p, idata) == -1) +- goto error; +- } else if (st.st_uid != 0 || st.st_gid != 0 || +- (st.st_mode & S_IWOTH)) { +- /* do not follow symlinks on subdirectories */ +- flags |= O_NOFOLLOW; +- } +- +- *d = '/'; +- dir = d + 1; +- } +- +- rv = openat(dfd, dir, flags); +- +- if (rv == -1) { +- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { +- goto error; +- } +- rv = openat(dfd, dir, flags); +- } +- +- if (flags & O_NOFOLLOW) { +- /* we are inside user-owned dir - protect */ +- if (protect_mount(rv, p, idata) == -1) { +- save_errno = errno; +- close(rv); +- rv = -1; +- errno = save_errno; +- } +- } +- +-error: +- save_errno = errno; +- free(p); +- if (dfd != AT_FDCWD && dfd >= 0) +- close(dfd); +- errno = save_errno; +- +- return rv; +-} +- + static int check_inst_parent(char *ipath, struct instance_data *idata) + { + struct stat instpbuf; +@@ -1180,13 +1278,12 @@ static int check_inst_parent(char *ipath + * admin explicitly instructs to ignore the instance parent + * mode by the "ignore_instance_parent_mode" argument). + */ +- inst_parent = (char *) malloc(strlen(ipath)+1); ++ inst_parent = strdup(ipath); + if (!inst_parent) { + pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string"); + return PAM_SESSION_ERR; + } + +- strcpy(inst_parent, ipath); + trailing_slash = strrchr(inst_parent, '/'); + if (trailing_slash) + *trailing_slash = '\0'; +@@ -1226,72 +1323,83 @@ static int inst_init(const struct polydi + struct instance_data *idata, int newdir) + { + pid_t rc, pid; +- struct sigaction newsa, oldsa; + int status; + const char *init_script = NAMESPACE_INIT_SCRIPT; + ++#ifdef VENDOR_NAMESPACE_INIT_SCRIPT ++ /* Check whether NAMESPACE_INIT_SCRIPT file is available. ++ * If it does not exist, fall back to VENDOR_NAMESPACE_INIT_SCRIPT file. */ ++ struct stat buffer; ++ if (stat(init_script, &buffer) != 0 && errno == ENOENT) { ++ init_script = VENDOR_NAMESPACE_INIT_SCRIPT; ++ } ++#endif ++ ++ if ((polyptr->flags & POLYDIR_ISCRIPT) && polyptr->init_script) ++ init_script = polyptr->init_script; ++ ++ if (access(init_script, F_OK) != 0) ++ return PAM_SUCCESS; ++ ++ if (access(init_script, X_OK) < 0) { ++ if (idata->flags & PAMNS_DEBUG) ++ pam_syslog(idata->pamh, LOG_ERR, ++ "Namespace init script not executable"); ++ return PAM_SESSION_ERR; ++ } ++ ++ struct sigaction newsa, oldsa; ++ + memset(&newsa, '\0', sizeof(newsa)); +- newsa.sa_handler = SIG_DFL; ++ newsa.sa_handler = SIG_DFL; + if (sigaction(SIGCHLD, &newsa, &oldsa) == -1) { +- pam_syslog(idata->pamh, LOG_ERR, "Cannot set signal value"); ++ pam_syslog(idata->pamh, LOG_ERR, "failed to reset SIGCHLD handler"); + return PAM_SESSION_ERR; + } + +- if ((polyptr->flags & POLYDIR_ISCRIPT) && polyptr->init_script) +- init_script = polyptr->init_script; ++ pid = fork(); ++ if (pid == 0) { ++ static char *envp[] = { NULL }; ++#ifdef WITH_SELINUX ++ if (idata->flags & PAMNS_SELINUX_ENABLED) { ++ if (setexeccon(NULL) < 0) ++ _exit(1); ++ } ++#endif ++ /* Pass maximum privs when we exec() */ ++ if (setuid(geteuid()) < 0) { ++ /* ignore failures, they don't matter */ ++ } + +- if (access(init_script, F_OK) == 0) { +- if (access(init_script, X_OK) < 0) { +- if (idata->flags & PAMNS_DEBUG) +- pam_syslog(idata->pamh, LOG_ERR, +- "Namespace init script not executable"); ++ close_fds_pre_exec(idata); ++ ++ execle(init_script, init_script, ++ polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); ++ _exit(1); ++ } else if (pid > 0) { ++ while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && ++ (errno == EINTR)); ++ if (rc == (pid_t)-1) { ++ pam_syslog(idata->pamh, LOG_ERR, "waitpid failed- %m"); ++ rc = PAM_SESSION_ERR; ++ goto out; ++ } ++ if (!WIFEXITED(status) || WIFSIGNALED(status) > 0) { ++ pam_syslog(idata->pamh, LOG_ERR, ++ "Error initializing instance"); + rc = PAM_SESSION_ERR; + goto out; +- } else { +- pid = fork(); +- if (pid == 0) { +- static char *envp[] = { NULL }; +-#ifdef WITH_SELINUX +- if (idata->flags & PAMNS_SELINUX_ENABLED) { +- if (setexeccon(NULL) < 0) +- _exit(1); +- } +-#endif +- /* Pass maximum privs when we exec() */ +- if (setuid(geteuid()) < 0) { +- /* ignore failures, they don't matter */ +- } +- +- if (execle(init_script, init_script, +- polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0) +- _exit(1); +- } else if (pid > 0) { +- while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && +- (errno == EINTR)); +- if (rc == (pid_t)-1) { +- pam_syslog(idata->pamh, LOG_ERR, "waitpid failed- %m"); +- rc = PAM_SESSION_ERR; +- goto out; +- } +- if (!WIFEXITED(status) || WIFSIGNALED(status) > 0) { +- pam_syslog(idata->pamh, LOG_ERR, +- "Error initializing instance"); +- rc = PAM_SESSION_ERR; +- goto out; +- } +- } else if (pid < 0) { +- pam_syslog(idata->pamh, LOG_ERR, +- "Cannot fork to run namespace init script, %m"); +- rc = PAM_SESSION_ERR; +- goto out; +- } + } ++ } else if (pid < 0) { ++ pam_syslog(idata->pamh, LOG_ERR, ++ "Cannot fork to run namespace init script, %m"); ++ rc = PAM_SESSION_ERR; ++ goto out; + } + rc = PAM_SUCCESS; + out: +- (void) sigaction(SIGCHLD, &oldsa, NULL); +- +- return rc; ++ (void) sigaction(SIGCHLD, &oldsa, NULL); ++ return rc; + } + + static int create_polydir(struct polydir_s *polyptr, +@@ -1395,7 +1503,7 @@ static int create_polydir(struct polydir + */ + #ifdef WITH_SELINUX + static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, +- security_context_t icontext, security_context_t ocontext, ++ const char *icontext, const char *ocontext, + struct instance_data *idata) + #else + static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, +@@ -1513,7 +1621,7 @@ static int ns_setup(struct polydir_s *po + char *instname = NULL; + struct stat statbuf; + #ifdef WITH_SELINUX +- security_context_t instcontext = NULL, origcontext = NULL; ++ char *instcontext = NULL, *origcontext = NULL; + #endif + + if (idata->flags & PAMNS_DEBUG) +@@ -1522,16 +1630,14 @@ static int ns_setup(struct polydir_s *po + + retval = protect_dir(polyptr->dir, 0, 0, idata); + +- if (retval < 0 && errno != ENOENT) { +- pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", +- polyptr->dir); +- return PAM_SESSION_ERR; +- } +- + if (retval < 0) { +- if ((polyptr->flags & POLYDIR_CREATE) && +- create_polydir(polyptr, idata) != PAM_SUCCESS) +- return PAM_SESSION_ERR; ++ if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) { ++ pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", ++ polyptr->dir); ++ return PAM_SESSION_ERR; ++ } ++ if (create_polydir(polyptr, idata) != PAM_SUCCESS) ++ return PAM_SESSION_ERR; + } else { + close(retval); + } +@@ -1580,7 +1686,7 @@ static int ns_setup(struct polydir_s *po + #endif + } + +- if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0) ++ if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL) + goto error_out; + + if (idata->flags & PAMNS_DEBUG) +@@ -1692,8 +1798,9 @@ static int cleanup_tmpdirs(struct instan + _exit(1); + } + #endif +- if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0) +- _exit(1); ++ close_fds_pre_exec(idata); ++ execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp); ++ _exit(1); + } else if (pid > 0) { + while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && + (errno == EINTR)); +@@ -1708,7 +1815,7 @@ static int cleanup_tmpdirs(struct instan + } + } else if (pid < 0) { + pam_syslog(idata->pamh, LOG_ERR, +- "Cannot fork to run namespace init script, %m"); ++ "Cannot fork to cleanup temporary directory, %m"); + rc = PAM_SESSION_ERR; + goto out; + } +@@ -1948,7 +2055,7 @@ static int orig_namespace(struct instanc + */ + static int ctxt_based_inst_needed(void) + { +- security_context_t scon = NULL; ++ char *scon = NULL; + int rc = 0; + + rc = getexeccon(&scon); +@@ -1994,7 +2101,7 @@ static int root_shared(void) + break; + + if (i == 6) { +- if (strncmp(tok, "shared:", 7) == 0) ++ if (pam_str_skip_prefix(tok, "shared:") != NULL) + /* there might be more / mounts, the last one counts */ + rv = 1; + else +@@ -2162,7 +2269,7 @@ int pam_sm_close_session(pam_handle_t *p + { + int i, retval; + struct instance_data idata; +- void *polyptr; ++ const void *polyptr; + + /* init instance data */ + idata.flags = 0; +@@ -2202,7 +2309,7 @@ int pam_sm_close_session(pam_handle_t *p + pam_set_data(idata.pamh, NAMESPACE_PROTECT_DATA, NULL, NULL); + + if (idata.flags & PAMNS_DEBUG) +- pam_syslog(idata.pamh, LOG_DEBUG, "close_session - sucessful"); ++ pam_syslog(idata.pamh, LOG_DEBUG, "close_session - successful"); + return PAM_SUCCESS; + } + +@@ -2210,12 +2317,14 @@ int pam_sm_close_session(pam_handle_t *p + if (retval != PAM_SUCCESS) + return retval; + +- retval = pam_get_data(idata.pamh, NAMESPACE_POLYDIR_DATA, (const void **)&polyptr); ++ retval = pam_get_data(idata.pamh, NAMESPACE_POLYDIR_DATA, &polyptr); + if (retval != PAM_SUCCESS || polyptr == NULL) + /* nothing to reset */ + return PAM_SUCCESS; + +- idata.polydirs_ptr = polyptr; ++ DIAG_PUSH_IGNORE_CAST_QUAL; ++ idata.polydirs_ptr = (void *)polyptr; ++ DIAG_POP_IGNORE_CAST_QUAL; + + if (idata.flags & PAMNS_DEBUG) + pam_syslog(idata.pamh, LOG_DEBUG, "Resetting namespace for pid %d", +diff -up Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.h.pam-namespace-rebase Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.h +--- Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.h.pam-namespace-rebase 2025-06-17 12:50:04.921165460 +0200 ++++ Linux-PAM-1.3.1/modules/pam_namespace/pam_namespace.h 2025-06-17 12:50:04.963237346 +0200 +@@ -30,7 +30,7 @@ + * DEALINGS IN THE SOFTWARE. + */ + +-#if !(defined(linux)) ++#ifndef __linux__ + #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!! + #endif + +@@ -44,21 +44,16 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include + #include + #include +-#include + #include + #include +-#include + #include + #include + #include +-#include + #include "security/pam_modules.h" + #include "security/pam_modutil.h" + #include "security/pam_ext.h" +@@ -111,7 +106,7 @@ + #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */ + + /* polydir flags */ +-#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */ ++#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */ + #define POLYDIR_CREATE 0x00000002 /* create the polydir */ + #define POLYDIR_NOINIT 0x00000004 /* no init script */ + #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */ diff --git a/pam.spec b/pam.spec index ee87fa4..d38caa0 100644 --- a/pam.spec +++ b/pam.spec @@ -3,7 +3,7 @@ Summary: An extensible library which provides authentication for applications Name: pam Version: 1.3.1 -Release: 36%{?dist} +Release: 37%{?dist} # The library is BSD licensed with option to relicense as GPLv2+ # - this option is redundant as the BSD license allows that anyway. # pam_timestamp, pam_loginuid, and pam_console modules are GPLv2+. @@ -123,6 +123,10 @@ Patch73: pam-1.3.1-pam-access-local.patch Patch74: pam-1.3.1-libpam-support-long-lines.patch # https://github.com/linux-pam/linux-pam/commit/940747f88c16e029b69a74e80a2e94f65cb3e628 Patch75: pam-1.3.1-pam-access-resolve-ip.patch +# https://github.com/linux-pam/linux-pam/commit/10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc +Patch76: pam-1.3.1-pam-inline-pam-asprintf.patch +# Available upstream +Patch77: pam-1.3.1-pam-namespace-rebase.patch %define _pamlibdir %{_libdir} %define _moduledir %{_libdir}/security @@ -246,6 +250,8 @@ cp %{SOURCE18} . %patch73 -p1 -b .pam-access-local %patch74 -p1 -b .libpam-support-long-lines %patch75 -p1 -b .pam-access-resolve-ip +%patch76 -p1 -b .pam-inline-pam-asprintf +%patch77 -p1 -b .pam-namespace-rebase autoreconf -i @@ -499,6 +505,10 @@ done %doc doc/specs/rfc86.0.txt %changelog +* Mon Jun 16 2025 Iker Pedrosa - 1.3.1-37 +- pam_namespace: fix potential privilege escalation. + Resolves: CVE-2025-6020 and RHEL-96724 + * Mon Nov 25 2024 Iker Pedrosa - 1.3.1-36 - pam_access: rework resolving of tokens as hostname. Resolves: CVE-2024-10963 and RHEL-66242 -- Gitee