diff --git a/SOURCES/pam-1-5-1-libpam-getlogin.patch b/SOURCES/pam-1-5-1-libpam-getlogin.patch new file mode 100644 index 0000000..a644b94 --- /dev/null +++ b/SOURCES/pam-1-5-1-libpam-getlogin.patch @@ -0,0 +1,144 @@ +From 244b46908df930626535c0cd7c2867407fe8714a Mon Sep 17 00:00:00 2001 +From: Thorsten Kukuk +Date: Tue, 14 Feb 2023 14:57:40 +0100 +Subject: [PATCH] libpam: use getlogin() from libc and not utmp + + utmp uses 32bit time_t for compatibility with 32bit userland on some + 64bit systems and is thus not Y2038 safe. Use getlogin() from libc + which avoids using utmp and is more safe than the old utmp-based + implementation by using /proc/self/loginuid. + + * libpam/pam_modutil_getlogin.c: Use getlogin() instead of parsing utmp +--- + libpam/pam_modutil_getlogin.c | 52 ++++++++--------------------------- + 1 file changed, 11 insertions(+), 41 deletions(-) + +diff --git a/libpam/pam_modutil_getlogin.c b/libpam/pam_modutil_getlogin.c +index 04a20fd8..633dd676 100644 +--- a/libpam/pam_modutil_getlogin.c ++++ b/libpam/pam_modutil_getlogin.c +@@ -10,7 +10,6 @@ + + #include + #include +-#include + + #define _PAMMODUTIL_GETLOGIN "_pammodutil_getlogin" + +@@ -19,62 +18,33 @@ pam_modutil_getlogin(pam_handle_t *pamh) + { + int status; + const void *logname; +- const void *void_curr_tty; +- const char *curr_tty; + char *curr_user; +- struct utmp *ut, line; ++ size_t curr_user_len; + + status = pam_get_data(pamh, _PAMMODUTIL_GETLOGIN, &logname); + if (status == PAM_SUCCESS) { + return logname; + } + +- status = pam_get_item(pamh, PAM_TTY, &void_curr_tty); +- if ((status != PAM_SUCCESS) || (void_curr_tty == NULL)) +- curr_tty = ttyname(0); +- else +- curr_tty = (const char*)void_curr_tty; +- +- if (curr_tty == NULL) { +- return NULL; +- } +- +- if (curr_tty[0] == '/') { /* full path */ +- const char *t; +- curr_tty++; +- if ((t = strchr(curr_tty, '/')) != NULL) { +- curr_tty = t + 1; +- } ++ logname = getlogin(); ++ if (logname == NULL) { ++ return NULL; + } +- logname = NULL; + +- setutent(); +- strncpy(line.ut_line, curr_tty, sizeof(line.ut_line)); +- +- if ((ut = getutline(&line)) == NULL) { +- goto clean_up_and_go_home; +- } +- +- curr_user = calloc(sizeof(line.ut_user)+1, 1); ++ curr_user_len = strlen(logname)+1; ++ curr_user = calloc(curr_user_len, 1); + if (curr_user == NULL) { +- goto clean_up_and_go_home; ++ return NULL; + } + +- strncpy(curr_user, ut->ut_user, sizeof(ut->ut_user)); +- /* calloc already zeroed the memory */ ++ memcpy(curr_user, logname, curr_user_len); + + status = pam_set_data(pamh, _PAMMODUTIL_GETLOGIN, curr_user, + pam_modutil_cleanup); + if (status != PAM_SUCCESS) { +- free(curr_user); +- goto clean_up_and_go_home; ++ free(curr_user); ++ return NULL; + } + +- logname = curr_user; +- +-clean_up_and_go_home: +- +- endutent(); +- +- return logname; ++ return curr_user; + } +-- +2.43.0 + +From f26d873435be9f35fa7953493cc07a9bc4e31876 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Sat, 18 Feb 2023 14:37:04 +0100 +Subject: [PATCH] libpam: simplify string copying using strdup + +--- + libpam/pam_modutil_getlogin.c | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/libpam/pam_modutil_getlogin.c b/libpam/pam_modutil_getlogin.c +index 633dd676..2e7a0116 100644 +--- a/libpam/pam_modutil_getlogin.c ++++ b/libpam/pam_modutil_getlogin.c +@@ -19,7 +19,6 @@ pam_modutil_getlogin(pam_handle_t *pamh) + int status; + const void *logname; + char *curr_user; +- size_t curr_user_len; + + status = pam_get_data(pamh, _PAMMODUTIL_GETLOGIN, &logname); + if (status == PAM_SUCCESS) { +@@ -31,14 +30,11 @@ pam_modutil_getlogin(pam_handle_t *pamh) + return NULL; + } + +- curr_user_len = strlen(logname)+1; +- curr_user = calloc(curr_user_len, 1); ++ curr_user = strdup(logname); + if (curr_user == NULL) { + return NULL; + } + +- memcpy(curr_user, logname, curr_user_len); +- + status = pam_set_data(pamh, _PAMMODUTIL_GETLOGIN, curr_user, + pam_modutil_cleanup); + if (status != PAM_SUCCESS) { +-- +2.43.0 + diff --git a/SOURCES/pam-1.5.1-access-handle-hostnames.patch b/SOURCES/pam-1.5.1-access-handle-hostnames.patch new file mode 100644 index 0000000..8cd9e8e --- /dev/null +++ b/SOURCES/pam-1.5.1-access-handle-hostnames.patch @@ -0,0 +1,168 @@ +diff -up Linux-PAM-1.5.1/modules/pam_access/pam_access.c.access-handle-hostnames Linux-PAM-1.5.1/modules/pam_access/pam_access.c +--- Linux-PAM-1.5.1/modules/pam_access/pam_access.c.access-handle-hostnames 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_access/pam_access.c 2024-01-22 15:56:09.977868880 +0100 +@@ -662,7 +662,7 @@ from_match (pam_handle_t *pamh UNUSED, c + } + } + } else { +- /* Assume network/netmask with a IP of a host. */ ++ /* Assume network/netmask, IP address or hostname. */ + if (network_netmask_match(pamh, tok, string, item)) + return YES; + } +@@ -684,7 +684,7 @@ string_match (pam_handle_t *pamh, const + /* + * If the token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the string. +- * "NONE" token matches NULL string. ++ * "NONE" token matches NULL string. + */ + + if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ +@@ -702,7 +702,8 @@ string_match (pam_handle_t *pamh, const + + /* network_netmask_match - match a string against one token + * where string is a hostname or ip (v4,v6) address and tok +- * represents either a single ip (v4,v6) address or a network/netmask ++ * represents either a hostname, a single ip (v4,v6) address ++ * or a network/netmask + */ + static int + network_netmask_match (pam_handle_t *pamh, +@@ -711,10 +712,12 @@ network_netmask_match (pam_handle_t *pam + char *netmask_ptr; + char netmask_string[MAXHOSTNAMELEN + 1]; + int addr_type; ++ struct addrinfo *ai = NULL; + + if (item->debug) +- pam_syslog (pamh, LOG_DEBUG, ++ pam_syslog (pamh, LOG_DEBUG, + "network_netmask_match: tok=%s, item=%s", tok, string); ++ + /* OK, check if tok is of type addr/mask */ + if ((netmask_ptr = strchr(tok, '/')) != NULL) + { +@@ -748,54 +751,108 @@ network_netmask_match (pam_handle_t *pam + netmask_ptr = number_to_netmask(netmask, addr_type, + netmask_string, MAXHOSTNAMELEN); + } +- } ++ ++ /* ++ * Construct an addrinfo list from the IP address. ++ * This should not fail as the input is a correct IP address... ++ */ ++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0) ++ { ++ return NO; ++ } ++ } + else +- /* NO, then check if it is only an addr */ +- if (isipaddr(tok, NULL, NULL) != YES) ++ { ++ /* ++ * It is either an IP address or a hostname. ++ * Let getaddrinfo sort everything out ++ */ ++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0) + { ++ pam_syslog(pamh, LOG_ERR, "cannot resolve hostname \"%s\"", tok); ++ + return NO; + } ++ netmask_ptr = NULL; ++ } + + if (isipaddr(string, NULL, NULL) != YES) + { +- /* Assume network/netmask with a name of a host. */ + struct addrinfo hint; + ++ /* Assume network/netmask with a name of a host. */ + memset (&hint, '\0', sizeof (hint)); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_UNSPEC; + + if (item->gai_rv != 0) ++ { ++ freeaddrinfo(ai); + return NO; ++ } + else if (!item->res && + (item->gai_rv = getaddrinfo (string, NULL, &hint, &item->res)) != 0) ++ { ++ freeaddrinfo(ai); + return NO; ++ } + else + { + struct addrinfo *runp = item->res; ++ struct addrinfo *runp1; + + while (runp != NULL) + { + char buf[INET6_ADDRSTRLEN]; + +- DIAG_PUSH_IGNORE_CAST_ALIGN; +- inet_ntop (runp->ai_family, +- runp->ai_family == AF_INET +- ? (void *) &((struct sockaddr_in *) runp->ai_addr)->sin_addr +- : (void *) &((struct sockaddr_in6 *) runp->ai_addr)->sin6_addr, +- buf, sizeof (buf)); +- DIAG_POP_IGNORE_CAST_ALIGN; ++ if (getnameinfo (runp->ai_addr, runp->ai_addrlen, buf, sizeof (buf), NULL, 0, NI_NUMERICHOST) != 0) ++ { ++ freeaddrinfo(ai); ++ return NO; ++ } + +- if (are_addresses_equal(buf, tok, netmask_ptr)) ++ for (runp1 = ai; runp1 != NULL; runp1 = runp1->ai_next) + { +- return YES; ++ char buf1[INET6_ADDRSTRLEN]; ++ ++ if (runp->ai_family != runp1->ai_family) ++ continue; ++ ++ if (getnameinfo (runp1->ai_addr, runp1->ai_addrlen, buf1, sizeof (buf1), NULL, 0, NI_NUMERICHOST) != 0) ++ { ++ freeaddrinfo(ai); ++ return NO; ++ } ++ ++ if (are_addresses_equal (buf, buf1, netmask_ptr)) ++ { ++ freeaddrinfo(ai); ++ return YES; ++ } + } + runp = runp->ai_next; + } + } + } + else +- return (are_addresses_equal(string, tok, netmask_ptr)); ++ { ++ struct addrinfo *runp1; ++ ++ for (runp1 = ai; runp1 != NULL; runp1 = runp1->ai_next) ++ { ++ char buf1[INET6_ADDRSTRLEN]; ++ ++ (void) getnameinfo (runp1->ai_addr, runp1->ai_addrlen, buf1, sizeof (buf1), NULL, 0, NI_NUMERICHOST); ++ ++ if (are_addresses_equal(string, buf1, netmask_ptr)) ++ { ++ freeaddrinfo(ai); ++ return YES; ++ } ++ } ++ } ++ ++ freeaddrinfo(ai); + + return NO; + } diff --git a/SOURCES/pam-1.5.1-audit-messages-formatting.patch b/SOURCES/pam-1.5.1-audit-messages-formatting.patch new file mode 100644 index 0000000..aa9c626 --- /dev/null +++ b/SOURCES/pam-1.5.1-audit-messages-formatting.patch @@ -0,0 +1,72 @@ +From c85513220c1bd3150e39c6277422d29cfa44acc7 Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Thu, 27 Jul 2023 13:14:42 -0400 +Subject: [PATCH 1/2] pam_faillock: fix formatting of audit messages + +pam_faillock uses audit_log_user_message to write to the audit system. +It does not take an op argument, so you have to add one yourself. Otherwise +the pam_faillock part of the message is lost because it's not in key=value +format. + +Also, we can't use uid in that event because the kernel already adds that +field. What we normally do is use 'suid' (meaning sender uid) as the +field name. +--- + modules/pam_faillock/pam_faillock.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/modules/pam_faillock/pam_faillock.c b/modules/pam_faillock/pam_faillock.c +index ca1c7035..a89909ab 100644 +--- a/modules/pam_faillock/pam_faillock.c ++++ b/modules/pam_faillock/pam_faillock.c +@@ -248,7 +248,7 @@ check_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies + + (void)pam_get_item(pamh, PAM_TTY, &tty); + (void)pam_get_item(pamh, PAM_RHOST, &rhost); +- snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid); ++ snprintf(buf, sizeof(buf), "op=pam_faillock suid=%u ", opts->uid); + audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf, + rhost, NULL, tty, 1); + } +@@ -364,7 +364,7 @@ write_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies + errno == EAFNOSUPPORT)) + return PAM_SYSTEM_ERR; + +- snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid); ++ snprintf(buf, sizeof(buf), "op=pam_faillock suid=%u ", opts->uid); + audit_log_user_message(audit_fd, AUDIT_ANOM_LOGIN_FAILURES, buf, + NULL, NULL, NULL, 1); + +-- +2.41.0 + + +From 1648734a69c31e9ce834da70144ac9a453296807 Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Fri, 4 Aug 2023 17:45:45 -0400 +Subject: [PATCH 2/2] pam_selinux: fix formatting of audit messages + +pam_selinux uses audit_log_user_message to write to the audit system. +It does not take an op argument, so you have to add one yourself. Otherwise +the pam_selinux part of the message is lost because it's not in key=value +format. +--- + modules/pam_selinux/pam_selinux.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/pam_selinux/pam_selinux.c b/modules/pam_selinux/pam_selinux.c +index e52e0fc4..713b3f73 100644 +--- a/modules/pam_selinux/pam_selinux.c ++++ b/modules/pam_selinux/pam_selinux.c +@@ -97,7 +97,7 @@ send_audit_message(const pam_handle_t *pamh, int success, const char *default_co + pam_syslog(pamh, LOG_ERR, "Error translating selected context '%s'.", selected_context); + selected_raw = NULL; + } +- if (asprintf(&msg, "pam: default-context=%s selected-context=%s", ++ if (asprintf(&msg, "op=pam_selinux default-context=%s selected-context=%s", + default_raw ? default_raw : (default_context ? default_context : "?"), + selected_raw ? selected_raw : (selected_context ? selected_context : "?")) < 0) { + msg = NULL; /* asprintf leaves msg in undefined state on failure */ +-- +2.41.0 + diff --git a/SOURCES/pam-1.5.1-faillock-create-tallydir.patch b/SOURCES/pam-1.5.1-faillock-create-tallydir.patch new file mode 100644 index 0000000..72f879a --- /dev/null +++ b/SOURCES/pam-1.5.1-faillock-create-tallydir.patch @@ -0,0 +1,36 @@ +From d54870f993e97fe75e2cd0470a3701d5af22877c Mon Sep 17 00:00:00 2001 +From: Changqing Li +Date: Tue, 12 Jan 2021 14:45:34 +0800 +Subject: [PATCH] faillock: create tallydir before creating tallyfile + +The default tallydir is "/var/run/faillock", and this default +tallydir may not exist. + +Function open may fail as tallydir does not exist when creating +the tallyfile. Therefore, faillock will not work well. + +Fix this problem by creating tallydir before creating tallyfile +when the tallydir does not exist. + +Signed-off-by: Changqing Li +--- + modules/pam_faillock/faillock.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/modules/pam_faillock/faillock.c b/modules/pam_faillock/faillock.c +index 4ea94cbe..091f253a 100644 +--- a/modules/pam_faillock/faillock.c ++++ b/modules/pam_faillock/faillock.c +@@ -74,6 +74,9 @@ open_tally (const char *dir, const char *user, uid_t uid, int create) + + if (create) { + flags |= O_CREAT; ++ if (access(dir, F_OK) != 0) { ++ mkdir(dir, 0755); ++ } + } + + fd = open(path, flags, 0660); +-- +2.43.0 + diff --git a/SOURCES/pam-1.5.1-libpam-close-range.patch b/SOURCES/pam-1.5.1-libpam-close-range.patch new file mode 100644 index 0000000..70d61b3 --- /dev/null +++ b/SOURCES/pam-1.5.1-libpam-close-range.patch @@ -0,0 +1,55 @@ +diff -up Linux-PAM-1.5.1/configure.ac.libpam-close-range Linux-PAM-1.5.1/configure.ac +--- Linux-PAM-1.5.1/configure.ac.libpam-close-range 2023-11-10 10:35:00.142833269 +0100 ++++ Linux-PAM-1.5.1/configure.ac 2023-11-10 10:36:29.158987392 +0100 +@@ -552,6 +552,7 @@ AC_CHECK_FUNCS(inet_ntop inet_pton innet + AC_CHECK_FUNCS(quotactl) + AC_CHECK_FUNCS(unshare) + AC_CHECK_FUNCS([ruserok_af ruserok], [break]) ++AC_CHECK_FUNCS(close_range) + BACKUP_LIBS=$LIBS + LIBS="$LIBS -lutil" + AC_CHECK_FUNCS([logwtmp]) +diff -up Linux-PAM-1.5.1/libpam/pam_modutil_sanitize.c.libpam-close-range Linux-PAM-1.5.1/libpam/pam_modutil_sanitize.c +--- Linux-PAM-1.5.1/libpam/pam_modutil_sanitize.c.libpam-close-range 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/libpam/pam_modutil_sanitize.c 2023-11-10 10:35:00.142833269 +0100 +@@ -11,6 +11,10 @@ + #include + #include + ++#ifndef CLOSE_RANGE_UNSHARE ++#define CLOSE_RANGE_UNSHARE (1U << 1) ++#endif /* CLOSE_RANGE_UNSHARE */ ++ + /* + * Creates a pipe, closes its write end, redirects fd to its read end. + * Returns fd on success, -1 otherwise. +@@ -84,9 +88,8 @@ redirect_out(pam_handle_t *pamh, enum pa + return fd; + } + +-/* Closes all descriptors after stderr. */ + static void +-close_fds(void) ++close_fds_iteratively(void) + { + /* + * An arbitrary upper limit for the maximum file descriptor number +@@ -111,6 +114,18 @@ close_fds(void) + close(fd); + } + ++/* Closes all descriptors after stderr. */ ++static void ++close_fds(void) ++{ ++#ifdef HAVE_CLOSE_RANGE ++ if (close_range(STDERR_FILENO+1, -1U, CLOSE_RANGE_UNSHARE) == 0) ++ return; ++#endif /* HAVE_CLOSE_RANGE */ ++ ++ close_fds_iteratively(); ++} ++ + int + pam_modutil_sanitize_helper_fds(pam_handle_t *pamh, + enum pam_modutil_redirect_fd stdin_mode, diff --git a/SOURCES/pam-1.5.1-libpam-support-long-lines.patch b/SOURCES/pam-1.5.1-libpam-support-long-lines.patch new file mode 100644 index 0000000..3bf538b --- /dev/null +++ b/SOURCES/pam-1.5.1-libpam-support-long-lines.patch @@ -0,0 +1,654 @@ +diff -up Linux-PAM-1.5.1/libpam/pam_handlers.c.libpam-support-long-lines Linux-PAM-1.5.1/libpam/pam_handlers.c +--- Linux-PAM-1.5.1/libpam/pam_handlers.c.libpam-support-long-lines 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/libpam/pam_handlers.c 2024-06-18 10:07:12.434785557 +0200 +@@ -17,21 +17,30 @@ + #include + #include + +-#define BUF_SIZE 1024 + #define MODULE_CHUNK 4 + #define UNKNOWN_MODULE "<*unknown module*>" + #ifndef _PAM_ISA + #define _PAM_ISA "." + #endif + +-static int _pam_assemble_line(FILE *f, char *buf, int buf_len); ++struct line_buffer { ++ char *assembled; ++ char *chunk; ++ size_t chunk_size; ++ size_t len; ++ size_t size; ++}; ++ ++static void _pam_buffer_init(struct line_buffer *buffer); ++ ++static int _pam_assemble_line(FILE *f, struct line_buffer *buf); + + static void _pam_free_handlers_aux(struct handler **hp); + + static int _pam_add_handler(pam_handle_t *pamh + , int must_fail, int other, int stack_level, int type + , int *actions, const char *mod_path +- , int argc, char **argv, int argvlen); ++ , int argc, char **argv, size_t argvlen); + + /* Values for module type */ + +@@ -59,12 +68,15 @@ static int _pam_parse_conf_file(pam_hand + #endif /* PAM_READ_BOTH_CONFS */ + ) + { +- char buf[BUF_SIZE]; ++ struct line_buffer buffer; + int x; /* read a line from the FILE *f ? */ ++ ++ _pam_buffer_init(&buffer); + /* + * read a line from the configuration (FILE *) f + */ +- while ((x = _pam_assemble_line(f, buf, BUF_SIZE)) > 0) { ++ while ((x = _pam_assemble_line(f, &buffer)) > 0) { ++ char *buf = buffer.assembled; + char *tok, *nexttok=NULL; + const char *this_service; + const char *mod_path; +@@ -74,7 +86,7 @@ static int _pam_parse_conf_file(pam_hand + int handler_type = PAM_HT_MODULE; /* regular handler from a module */ + int argc; + char **argv; +- int argvlen; ++ size_t argvlen; + + D(("_pam_init_handler: LINE: %s", buf)); + if (known_service != NULL) { +@@ -233,10 +245,11 @@ static int _pam_parse_conf_file(pam_hand + if (nexttok != NULL) { + D(("list: %s",nexttok)); + argvlen = _pam_mkargv(nexttok, &argv, &argc); +- D(("argvlen = %d",argvlen)); ++ D(("argvlen = %zu",argvlen)); + } else { /* there are no arguments so fix by hand */ + D(("_pam_init_handlers: empty argument list")); +- argvlen = argc = 0; ++ argvlen = 0; ++ argc = 0; + argv = NULL; + } + +@@ -557,88 +570,243 @@ int _pam_init_handlers(pam_handle_t *pam + return PAM_SUCCESS; + } + ++static int _pam_buffer_add(struct line_buffer *buffer, char *start, char *end) ++{ ++ size_t len = end - start; ++ ++ D(("assembled: [%zu/%zu] '%s', adding [%zu] '%s'", ++ buffer->len, buffer->size, ++ buffer->assembled == NULL ? "" : buffer->assembled, len, start)); ++ ++ if (start == end) ++ return 0; ++ ++ if (buffer->assembled == NULL && buffer->chunk == start) { ++ /* no extra allocation needed, just move chunk to assembled */ ++ buffer->assembled = buffer->chunk; ++ buffer->len = len; ++ buffer->size = buffer->chunk_size; ++ ++ buffer->chunk = NULL; ++ buffer->chunk_size = 0; ++ ++ D(("exiting with quick exchange")); ++ return 0; ++ } ++ ++ if (buffer->len + len + 1 > buffer->size) { ++ size_t size; ++ char *p; ++ ++ size = buffer->len + len + 1; ++ if ((p = realloc(buffer->assembled, size)) == NULL) ++ return -1; ++ ++ buffer->assembled = p; ++ buffer->size = size; ++ } ++ ++ memcpy(buffer->assembled + buffer->len, start, len); ++ buffer->len += len; ++ buffer->assembled[buffer->len] = '\0'; ++ ++ D(("exiting")); ++ return 0; ++} ++ ++static inline int _pam_buffer_add_eol(struct line_buffer *buffer, ++ char *start, char *end) ++{ ++ if (buffer->assembled != NULL || (*start != '\0' && *start != '\n')) ++ return _pam_buffer_add(buffer, start, end); ++ return 0; ++} ++ ++static void _pam_buffer_clear(struct line_buffer *buffer) ++{ ++ _pam_drop(buffer->assembled); ++ _pam_drop(buffer->chunk); ++ buffer->chunk_size = 0; ++ buffer->len = 0; ++ buffer->size = 0; ++} ++ ++static void _pam_buffer_init(struct line_buffer *buffer) ++{ ++ buffer->assembled = NULL; ++ buffer->chunk = NULL; ++ _pam_buffer_clear(buffer); ++} ++ ++static void _pam_buffer_purge(struct line_buffer *buffer) ++{ ++ _pam_drop(buffer->chunk); ++ buffer->chunk_size = 0; ++} ++ ++static void _pam_buffer_shift(struct line_buffer *buffer) ++{ ++ if (buffer->assembled == NULL) ++ return; ++ ++ _pam_buffer_purge(buffer); ++ buffer->chunk = buffer->assembled; ++ buffer->chunk_size = buffer->size; ++ ++ buffer->assembled = NULL; ++ buffer->size = 0; ++ buffer->len = 0; ++} ++ ++static inline int _pam_buffer_valid(struct line_buffer *buffer) ++{ ++ return buffer->assembled != NULL && *buffer->assembled != '\0'; ++} ++ ++/* ++ * Trim string to relevant parts of a configuration line. ++ * ++ * Preceding whitespaces are skipped and comment (#) marks the end of ++ * configuration line. ++ * ++ * Returns start of configuration line. ++ */ ++static inline char *_pam_str_trim(char *str) ++{ ++ /* skip leading spaces */ ++ str += strspn(str, " \t"); ++ /* ++ * we are only interested in characters before the first '#' ++ * character ++ */ ++ str[strcspn(str, "#")] = '\0'; ++ ++ return str; ++} ++ ++/* ++ * Remove escaped newline from end of string. ++ * ++ * Configuration lines may span across multiple lines in a file ++ * by ending a line with a backslash (\). ++ * ++ * If an escaped newline is encountered, the backslash will be ++ * replaced with a blank ' ' and the newline itself removed. ++ * Then the variable "end" will point to the new end of line. ++ * ++ * Returns 0 if escaped newline was found and replaced, 1 otherwise. ++ */ ++static inline int _pam_str_unescnl(char *start, char **end) ++{ ++ int ret = 1; ++ char *p = *end; ++ ++ /* ++ * Check for backslash by scanning back from the end of ++ * the entered line, the '\n' should be included since ++ * normally a line is terminated with this character. ++ */ ++ while (p > start && ((*--p == ' ') || (*p == '\t') || (*p == '\n'))) ++ ; ++ if (*p == '\\') { ++ *p++ = ' '; /* replace backslash with ' ' */ ++ *p = '\0'; /* truncate the line here */ ++ *end = p; ++ ret = 0; ++ } ++ ++ return ret; ++} ++ ++/* ++ * Prepare line from file for configuration line parsing. ++ * ++ * A configuration line may span across multiple lines in a file. ++ * Remove comments and skip preceding whitespaces. ++ * ++ * Returns 0 if line spans across multiple lines, 1 if ++ * end of line is encountered. ++ */ ++static inline int _pam_str_prepare(char *line, ssize_t len, ++ char **start, char **end) ++{ ++ int ret; ++ ++ *start = line; ++ *end = line + len; ++ ++ ret = _pam_str_unescnl(*start, end) || strchr(*start, '#') != NULL; ++ ++ *start = _pam_str_trim(*start); ++ ++ return ret; ++} ++ + /* + * This is where we read a line of the PAM config file. The line may be + * preceded by lines of comments and also extended with "\\\n" ++ * ++ * Returns 0 on EOF, 1 on successful line parsing, or -1 on error. + */ + +-static int _pam_assemble_line(FILE *f, char *buffer, int buf_len) ++static int _pam_assemble_line(FILE *f, struct line_buffer *buffer) + { +- char *p = buffer; +- char *endp = buffer + buf_len; +- char *s, *os; +- int used = 0; ++ int ret = 0; + + /* loop broken with a 'break' when a non-'\\n' ended line is read */ + + D(("called.")); ++ ++ _pam_buffer_shift(buffer); ++ + for (;;) { +- if (p >= endp) { +- /* Overflow */ +- D(("_pam_assemble_line: overflow")); +- return -1; +- } +- if (fgets(p, endp - p, f) == NULL) { +- if (used) { ++ char *start, *end; ++ ssize_t n; ++ int eol; ++ ++ if ((n = getline(&buffer->chunk, &buffer->chunk_size, f)) == -1) { ++ if (ret) { + /* Incomplete read */ +- return -1; ++ ret = -1; + } else { + /* EOF */ +- return 0; ++ ret = 0; + } ++ break; + } + +- /* skip leading spaces --- line may be blank */ +- +- s = p + strspn(p, " \n\t"); +- if (*s && (*s != '#')) { +- os = s; +- +- /* +- * we are only interested in characters before the first '#' +- * character +- */ +- +- while (*s && *s != '#') +- ++s; +- if (*s == '#') { +- *s = '\0'; +- used += strlen(os); +- break; /* the line has been read */ +- } +- +- s = os; +- +- /* +- * Check for backslash by scanning back from the end of +- * the entered line, the '\n' has been included since +- * normally a line is terminated with this +- * character. fgets() should only return one though! +- */ +- +- s += strlen(s); +- while (s > os && ((*--s == ' ') || (*s == '\t') +- || (*s == '\n'))); +- +- /* check if it ends with a backslash */ +- if (*s == '\\') { +- *s++ = ' '; /* replace backslash with ' ' */ +- *s = '\0'; /* truncate the line here */ +- used += strlen(os); +- p = s; /* there is more ... */ +- } else { +- /* End of the line! */ +- used += strlen(os); +- break; /* this is the complete line */ +- } ++ eol = _pam_str_prepare(buffer->chunk, n, &start, &end); + ++ if (eol) { ++ if (_pam_buffer_add_eol(buffer, start, end)) { ++ ret = -1; ++ break; ++ } ++ if (_pam_buffer_valid(buffer)) { ++ /* Successfully parsed a line */ ++ ret = 1; ++ break; ++ } ++ /* Start parsing next line */ ++ _pam_buffer_shift(buffer); ++ ret = 0; + } else { +- /* Nothing in this line */ +- /* Don't move p */ ++ /* Configuration line spans across multiple lines in file */ ++ if (_pam_buffer_add(buffer, start, end)) { ++ ret = -1; ++ break; ++ } ++ /* Keep parsing line */ ++ ret = 1; + } + } + +- return used; ++ if (ret == 1) ++ _pam_buffer_purge(buffer); ++ else ++ _pam_buffer_clear(buffer); ++ ++ return ret; + } + + static char * +@@ -777,7 +945,7 @@ _pam_load_module(pam_handle_t *pamh, con + int _pam_add_handler(pam_handle_t *pamh + , int handler_type, int other, int stack_level, int type + , int *actions, const char *mod_path +- , int argc, char **argv, int argvlen) ++ , int argc, char **argv, size_t argvlen) + { + struct loaded_module *mod = NULL; + struct handler **handler_p; +diff -up Linux-PAM-1.5.1/libpam/pam_misc.c.libpam-support-long-lines Linux-PAM-1.5.1/libpam/pam_misc.c +--- Linux-PAM-1.5.1/libpam/pam_misc.c.libpam-support-long-lines 2024-06-18 09:52:38.726482849 +0200 ++++ Linux-PAM-1.5.1/libpam/pam_misc.c 2024-06-18 10:02:13.132973447 +0200 +@@ -39,6 +39,8 @@ + + #include + #include ++#include ++#include + #include + #include + #include +@@ -163,60 +164,55 @@ char *_pam_memdup(const char *x, int len + /* Generate argv, argc from s */ + /* caller must free(argv) */ + +-int _pam_mkargv(const char *s, char ***argv, int *argc) ++size_t _pam_mkargv(const char *s, char ***argv, int *argc) + { +- int l; +- int argvlen = 0; +- char *sbuf, *sbuf_start; ++ size_t l; ++ size_t argvlen = 0; + char **our_argv = NULL; +- char **argvbuf; +- char *argvbufp; +-#ifdef PAM_DEBUG +- int count=0; +-#endif + +- D(("_pam_mkargv called: %s",s)); ++ D(("called: %s",s)); + + *argc = 0; + + l = strlen(s); +- if (l) { +- if ((sbuf = sbuf_start = _pam_strdup(s)) == NULL) { +- pam_syslog(NULL, LOG_CRIT, +- "pam_mkargv: null returned by _pam_strdup"); +- D(("arg NULL")); ++ if (l && l < SIZE_MAX / (sizeof(char) + sizeof(char *))) { ++ char **argvbuf; ++ /* Overkill on the malloc, but not large */ ++ argvlen = (l + 1) * (sizeof(char) + sizeof(char *)); ++ if ((our_argv = argvbuf = malloc(argvlen)) == NULL) { ++ pam_syslog(NULL, LOG_CRIT, "pam_mkargv: null returned by malloc"); ++ argvlen = 0; + } else { +- /* Overkill on the malloc, but not large */ +- argvlen = (l + 1) * ((sizeof(char)) + sizeof(char *)); +- if ((our_argv = argvbuf = malloc(argvlen)) == NULL) { +- pam_syslog(NULL, LOG_CRIT, +- "pam_mkargv: null returned by malloc"); +- } else { +- char *tmp=NULL; +- +- argvbufp = (char *) argvbuf + (l * sizeof(char *)); +- D(("[%s]",sbuf)); +- while ((sbuf = _pam_StrTok(sbuf, " \n\t", &tmp))) { +- D(("arg #%d",++count)); +- D(("->[%s]",sbuf)); +- strcpy(argvbufp, sbuf); +- D(("copied token")); +- *argvbuf = argvbufp; +- argvbufp += strlen(argvbufp) + 1; +- D(("stepped in argvbufp")); +- (*argc)++; +- argvbuf++; +- sbuf = NULL; +- D(("loop again?")); ++ char *argvbufp; ++ char *tmp=NULL; ++ char *tok; ++#ifdef PAM_DEBUG ++ unsigned count=0; ++#endif ++ argvbufp = (char *) argvbuf + (l * sizeof(char *)); ++ strcpy(argvbufp, s); ++ D(("[%s]",argvbufp)); ++ while ((tok = _pam_StrTok(argvbufp, " \n\t", &tmp))) { ++ D(("arg #%u",++count)); ++ D(("->[%s]",tok)); ++ *argvbuf++ = tok; ++ if (*argc == INT_MAX) { ++ pam_syslog(NULL, LOG_CRIT, ++ "pam_mkargv: too many arguments"); ++ argvlen = 0; ++ _pam_drop(our_argv); ++ break; + } ++ (*argc)++; ++ argvbufp = NULL; ++ D(("loop again?")); + } +- _pam_drop(sbuf_start); + } + } + + *argv = our_argv; + +- D(("_pam_mkargv returned")); ++ D(("exiting")); + + return(argvlen); + } +diff -up Linux-PAM-1.5.1/libpam/pam_private.h.libpam-support-long-lines Linux-PAM-1.5.1/libpam/pam_private.h +--- Linux-PAM-1.5.1/libpam/pam_private.h.libpam-support-long-lines 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/libpam/pam_private.h 2024-06-18 09:52:38.726482849 +0200 +@@ -16,6 +16,7 @@ + + #include "config.h" + ++#include + #include + + #include +@@ -272,7 +273,7 @@ char *_pam_strdup(const char *s); + + char *_pam_memdup(const char *s, int len); + +-int _pam_mkargv(const char *s, char ***argv, int *argc); ++size_t _pam_mkargv(const char *s, char ***argv, int *argc); + + void _pam_sanitize(pam_handle_t *pamh); + +diff -up Linux-PAM-1.5.1/modules/pam_exec/pam_exec.c.libpam-support-long-lines Linux-PAM-1.5.1/modules/pam_exec/pam_exec.c +--- Linux-PAM-1.5.1/modules/pam_exec/pam_exec.c.libpam-support-long-lines 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_exec/pam_exec.c 2024-06-18 09:52:38.725482846 +0200 +@@ -407,7 +407,7 @@ call_exec (const char *pam_type, pam_han + _exit (err); + } + +- arggv = calloc (argc + 4, sizeof (char *)); ++ arggv = calloc ((size_t) argc + 1, sizeof (char *)); + if (arggv == NULL) + _exit (ENOMEM); + +diff -up Linux-PAM-1.5.1/modules/pam_filter/pam_filter.c.libpam-support-long-lines Linux-PAM-1.5.1/modules/pam_filter/pam_filter.c +--- Linux-PAM-1.5.1/modules/pam_filter/pam_filter.c.libpam-support-long-lines 2024-06-18 09:52:38.725482846 +0200 ++++ Linux-PAM-1.5.1/modules/pam_filter/pam_filter.c 2024-06-18 09:54:17.102732759 +0200 +@@ -96,7 +96,8 @@ static int process_args(pam_handle_t *pa + char **levp; + const char *user = NULL; + const void *tmp; +- int i,size, retval; ++ int i, retval; ++ size_t size; + + *filtername = *++argv; + if (ctrl & FILTER_DEBUG) { +diff -up Linux-PAM-1.5.1/modules/pam_motd/pam_motd.c.libpam-support-long-lines Linux-PAM-1.5.1/modules/pam_motd/pam_motd.c +--- Linux-PAM-1.5.1/modules/pam_motd/pam_motd.c.libpam-support-long-lines 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_motd/pam_motd.c 2024-06-18 09:55:44.530954883 +0200 +@@ -71,14 +71,14 @@ static void try_to_display_fd(pam_handle + * Returns 0 in case of error, 1 in case of success. + */ + static int pam_split_string(const pam_handle_t *pamh, char *arg, char delim, +- char ***out_arg_split, unsigned int *out_num_strs) ++ char ***out_arg_split, size_t *out_num_strs) + { + char *arg_extracted = NULL; + const char *arg_ptr = arg; + char **arg_split = NULL; + char delim_str[2]; +- unsigned int i = 0; +- unsigned int num_strs = 0; ++ size_t i = 0; ++ size_t num_strs = 0; + int retval = 0; + + delim_str[0] = delim; +@@ -172,13 +172,13 @@ static int filter_dirents(const struct d + } + + static void try_to_display_directories_with_overrides(pam_handle_t *pamh, +- char **motd_dir_path_split, unsigned int num_motd_dirs, int report_missing) ++ char **motd_dir_path_split, size_t num_motd_dirs, int report_missing) + { + struct dirent ***dirscans = NULL; + unsigned int *dirscans_sizes = NULL; + unsigned int dirscans_size_total = 0; + char **dirnames_all = NULL; +- unsigned int i; ++ size_t i; + int i_dirnames = 0; + + if (pamh == NULL || motd_dir_path_split == NULL) { +@@ -304,9 +304,8 @@ static int drop_privileges(pam_handle_t + } + + static int try_to_display(pam_handle_t *pamh, char **motd_path_split, +- unsigned int num_motd_paths, +- char **motd_dir_path_split, +- unsigned int num_motd_dir_paths, int report_missing) ++ size_t num_motd_paths, char **motd_dir_path_split, ++ size_t num_motd_dir_paths, int report_missing) + { + PAM_MODUTIL_DEF_PRIVS(privs); + +@@ -316,7 +315,7 @@ static int try_to_display(pam_handle_t * + } + + if (motd_path_split != NULL) { +- unsigned int i; ++ size_t i; + + for (i = 0; i < num_motd_paths; i++) { + int fd = open(motd_path_split[i], O_RDONLY, 0); +@@ -354,11 +353,11 @@ int pam_sm_open_session(pam_handle_t *pa + int retval = PAM_IGNORE; + const char *motd_path = NULL; + char *motd_path_copy = NULL; +- unsigned int num_motd_paths = 0; ++ size_t num_motd_paths = 0; + char **motd_path_split = NULL; + const char *motd_dir_path = NULL; + char *motd_dir_path_copy = NULL; +- unsigned int num_motd_dir_paths = 0; ++ size_t num_motd_dir_paths = 0; + char **motd_dir_path_split = NULL; + int report_missing; + +diff -up Linux-PAM-1.5.1/modules/pam_permit/tst-pam_permit-retval.c.libpam-support-long-lines Linux-PAM-1.5.1/modules/pam_permit/tst-pam_permit-retval.c +--- Linux-PAM-1.5.1/modules/pam_permit/tst-pam_permit-retval.c.libpam-support-long-lines 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_permit/tst-pam_permit-retval.c 2024-06-18 09:52:38.726482849 +0200 +@@ -52,6 +52,35 @@ main(void) + ASSERT_EQ(PAM_SUCCESS, pam_end(pamh, 0)); + pamh = NULL; + ++ /* Perform a test dedicated to configuration file parsing. */ ++ ASSERT_NE(NULL, fp = fopen(service_file, "w")); ++ ASSERT_LT(0, fprintf(fp, "#%%PAM-1.0\n" ++ "# ignore escaped newlines in comments \\\n" ++ "auth required \\\n" ++ " %s/.libs/%s.so\n" ++ "# allow unneeded whitespaces\n" ++ " account required %s/.libs/%s.so%c\\\n" ++ "line after NUL byte continues up to here\n" ++ "password required %s/.libs/%s.so # eol comment\n" ++ "session required %s/.libs/%s.so", ++ cwd, MODULE_NAME, ++ cwd, MODULE_NAME, '\0', ++ cwd, MODULE_NAME, ++ cwd, MODULE_NAME)); ++ ASSERT_EQ(0, fclose(fp)); ++ ++ ASSERT_EQ(PAM_SUCCESS, ++ pam_start_confdir(service_file, user_name, &conv, ".", &pamh)); ++ ASSERT_NE(NULL, pamh); ++ ASSERT_EQ(PAM_SUCCESS, pam_authenticate(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_setcred(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_acct_mgmt(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_chauthtok(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_open_session(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_close_session(pamh, 0)); ++ ASSERT_EQ(PAM_SUCCESS, pam_end(pamh, 0)); ++ pamh = NULL; ++ + ASSERT_EQ(0, unlink(service_file)); + + return 0; diff --git a/SOURCES/pam-1.5.1-namespace-protect-dir.patch b/SOURCES/pam-1.5.1-namespace-protect-dir.patch new file mode 100644 index 0000000..39e2b5e --- /dev/null +++ b/SOURCES/pam-1.5.1-namespace-protect-dir.patch @@ -0,0 +1,58 @@ +From 031bb5a5d0d950253b68138b498dc93be69a64cb Mon Sep 17 00:00:00 2001 +From: Matthias Gerstner +Date: Wed, 27 Dec 2023 14:01:59 +0100 +Subject: [PATCH] pam_namespace: protect_dir(): use O_DIRECTORY to prevent + local DoS situations + +Without O_DIRECTORY the path crawling logic is subject to e.g. FIFOs +being placed in user controlled directories, causing the PAM module to +block indefinitely during `openat()`. + +Pass O_DIRECTORY to cause the `openat()` to fail if the path does not +refer to a directory. + +With this the check whether the final path element is a directory +becomes unnecessary, drop it. +--- + modules/pam_namespace/pam_namespace.c | 18 +----------------- + 1 file changed, 1 insertion(+), 17 deletions(-) + +diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c +index 2528cff8..f72d6718 100644 +--- a/modules/pam_namespace/pam_namespace.c ++++ b/modules/pam_namespace/pam_namespace.c +@@ -1201,7 +1201,7 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, + int dfd = AT_FDCWD; + int dfd_next; + int save_errno; +- int flags = O_RDONLY; ++ int flags = O_RDONLY | O_DIRECTORY; + int rv = -1; + struct stat st; + +@@ -1255,22 +1255,6 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, + rv = openat(dfd, dir, flags); + } + +- if (rv != -1) { +- if (fstat(rv, &st) != 0) { +- save_errno = errno; +- close(rv); +- rv = -1; +- errno = save_errno; +- goto error; +- } +- if (!S_ISDIR(st.st_mode)) { +- close(rv); +- errno = ENOTDIR; +- rv = -1; +- goto error; +- } +- } +- + if (flags & O_NOFOLLOW) { + /* we are inside user-owned dir - protect */ + if (protect_mount(rv, p, idata) == -1) { +-- +2.43.0 + diff --git a/SOURCES/pam-1.5.1-pam-access-resolve-ip.patch b/SOURCES/pam-1.5.1-pam-access-resolve-ip.patch new file mode 100644 index 0000000..27cbdf5 --- /dev/null +++ b/SOURCES/pam-1.5.1-pam-access-resolve-ip.patch @@ -0,0 +1,211 @@ +diff -up Linux-PAM-1.5.1/modules/pam_access/access.conf.5.xml.pam-access-resolve-ip Linux-PAM-1.5.1/modules/pam_access/access.conf.5.xml +--- Linux-PAM-1.5.1/modules/pam_access/access.conf.5.xml.pam-access-resolve-ip 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_access/access.conf.5.xml 2024-11-21 10:04:58.553127026 +0100 +@@ -226,6 +226,14 @@ + item and the line will be most probably ignored. For this reason, it is not + recommended to put spaces around the ':' characters. + ++ ++ An IPv6 link local host address must contain the interface ++ identifier. IPv6 link local network/netmask is not supported. ++ ++ ++ Hostnames should be written as Fully-Qualified Host Name (FQHN) to avoid ++ confusion with device names or PAM service names. ++ + + + +diff -up Linux-PAM-1.5.1/modules/pam_access/pam_access.8.xml.pam-access-resolve-ip Linux-PAM-1.5.1/modules/pam_access/pam_access.8.xml +--- Linux-PAM-1.5.1/modules/pam_access/pam_access.8.xml.pam-access-resolve-ip 2020-11-25 17:57:02.000000000 +0100 ++++ Linux-PAM-1.5.1/modules/pam_access/pam_access.8.xml 2024-11-21 10:04:58.553127026 +0100 +@@ -25,11 +25,14 @@ + + debug + ++ ++ noaudit ++ + + nodefgroup + +- +- noaudit ++ ++ nodns + + + accessfile=file +@@ -114,7 +117,46 @@ + + + +- ++ nodefgroup ++ ++ ++ ++ User tokens which are not enclosed in parentheses will not be ++ matched against the group database. The backwards compatible default is ++ to try the group database match even for tokens not enclosed ++ in parentheses. ++ ++ ++ ++ ++ ++ ++ nodns ++ ++ ++ ++ Do not try to resolve tokens as hostnames, only IPv4 and IPv6 ++ addresses will be resolved. Which means to allow login from a ++ remote host, the IP addresses need to be specified in access.conf. ++ ++ ++ ++ ++ ++ ++ quiet_log ++ ++ ++ ++ Do not log denials with ++ syslog3. ++ ++ ++ ++ ++ ++ ++ fieldsep=separators + + + +@@ -152,20 +194,6 @@ + + + +- +- +- +- +- +- +- +- User tokens which are not enclosed in parentheses will not be +- matched against the group database. The backwards compatible default is +- to try the group database match even for tokens not enclosed +- in parentheses. +- +- +- + + + +diff -up Linux-PAM-1.5.1/modules/pam_access/pam_access.c.pam-access-resolve-ip Linux-PAM-1.5.1/modules/pam_access/pam_access.c +--- Linux-PAM-1.5.1/modules/pam_access/pam_access.c.pam-access-resolve-ip 2024-11-21 10:04:58.547127010 +0100 ++++ Linux-PAM-1.5.1/modules/pam_access/pam_access.c 2024-11-21 10:04:58.553127026 +0100 +@@ -92,6 +92,7 @@ struct login_info { + int debug; /* Print debugging messages. */ + int only_new_group_syntax; /* Only allow group entries of the form "(xyz)" */ + int noaudit; /* Do not audit denials */ ++ int nodns; /* Do not try to resolve tokens as hostnames */ + const char *fs; /* field separator */ + const char *sep; /* list-element separator */ + int from_remote_host; /* If PAM_RHOST was used for from */ +@@ -143,6 +144,8 @@ parse_args(pam_handle_t *pamh, struct lo + loginfo->only_new_group_syntax = YES; + } else if (strcmp (argv[i], "noaudit") == 0) { + loginfo->noaudit = YES; ++ } else if (strcmp (argv[i], "nodns") == 0) { ++ loginfo->nodns = YES; + } else { + pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]); + } +@@ -700,6 +703,39 @@ string_match (pam_handle_t *pamh, const + } + + ++static int ++is_device (pam_handle_t *pamh, const char *tok) ++{ ++ struct stat st; ++ const char *dev = "/dev/"; ++ char *devname; ++ ++ devname = malloc (strlen(dev) + strlen (tok) + 1); ++ if (devname == NULL) { ++ pam_syslog(pamh, LOG_ERR, "Cannot allocate memory for device name: %m"); ++ /* ++ * We should return an error and abort, but pam_access has no good ++ * error handling. ++ */ ++ return NO; ++ } ++ ++ char *cp = stpcpy (devname, dev); ++ strcpy (cp, tok); ++ ++ if (lstat(devname, &st) != 0) ++ { ++ free (devname); ++ return NO; ++ } ++ free (devname); ++ ++ if (S_ISCHR(st.st_mode)) ++ return YES; ++ ++ return NO; ++} ++ + /* network_netmask_match - match a string against one token + * where string is a hostname or ip (v4,v6) address and tok + * represents either a hostname, a single ip (v4,v6) address +@@ -761,10 +797,42 @@ network_netmask_match (pam_handle_t *pam + return NO; + } + } ++ else if (isipaddr(tok, NULL, NULL) == YES) ++ { ++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0) ++ { ++ if (item->debug) ++ pam_syslog(pamh, LOG_DEBUG, "cannot resolve IP address \"%s\"", tok); ++ ++ return NO; ++ } ++ netmask_ptr = NULL; ++ } ++ else if (item->nodns) ++ { ++ /* Only hostnames are left, which we would need to resolve via DNS */ ++ return NO; ++ } + else + { ++ /* Bail out on X11 Display entries and ttys. */ ++ if (tok[0] == ':') ++ { ++ if (item->debug) ++ pam_syslog (pamh, LOG_DEBUG, ++ "network_netmask_match: tok=%s is X11 display", tok); ++ return NO; ++ } ++ if (is_device (pamh, tok)) ++ { ++ if (item->debug) ++ pam_syslog (pamh, LOG_DEBUG, ++ "network_netmask_match: tok=%s is a TTY", tok); ++ return NO; ++ } ++ + /* +- * It is either an IP address or a hostname. ++ * It is most likely a hostname. + * Let getaddrinfo sort everything out + */ + if (getaddrinfo (tok, NULL, NULL, &ai) != 0) diff --git a/SOURCES/pam-1.5.1-pam-unix-shadow-password.patch b/SOURCES/pam-1.5.1-pam-unix-shadow-password.patch new file mode 100644 index 0000000..ffdba93 --- /dev/null +++ b/SOURCES/pam-1.5.1-pam-unix-shadow-password.patch @@ -0,0 +1,114 @@ +diff -up Linux-PAM-1.5.1/modules/pam_unix/passverify.c.fail1 Linux-PAM-1.5.1/modules/pam_unix/passverify.c +--- Linux-PAM-1.5.1/modules/pam_unix/passverify.c.fail1 2024-11-04 11:42:51.962791265 +0100 ++++ Linux-PAM-1.5.1/modules/pam_unix/passverify.c 2024-11-04 11:45:18.246218579 +0100 +@@ -239,17 +239,21 @@ PAMH_ARG_DECL(int get_account_info, + return PAM_UNIX_RUN_HELPER; + #endif + } else if (is_pwd_shadowed(*pwd)) { ++#ifdef HELPER_COMPILE + /* +- * ...and shadow password file entry for this user, ++ * shadow password file entry for this user, + * if shadowing is enabled + */ +-#ifndef HELPER_COMPILE +- if (geteuid() || SELINUX_ENABLED) +- return PAM_UNIX_RUN_HELPER; +-#endif +- *spwdent = pam_modutil_getspnam(pamh, name); ++ *spwdent = getspnam(name); + if (*spwdent == NULL || (*spwdent)->sp_pwdp == NULL) + return PAM_AUTHINFO_UNAVAIL; ++#else ++ /* ++ * The helper has to be invoked to deal with ++ * the shadow password file entry. ++ */ ++ return PAM_UNIX_RUN_HELPER; ++#endif + } + } else { + return PAM_USER_UNKNOWN; + + +From 8d0c575336ad301cd14e16ad2fdec6fe621764b8 Mon Sep 17 00:00:00 2001 +From: Sergei Trofimovich +Date: Thu, 28 Mar 2024 21:58:35 +0000 +Subject: [PATCH] pam_unix: allow empty passwords with non-empty hashes + +Before the change pam_unix has different behaviours for a user with +empty password for these two `/etc/shadow` entries: + + nulloktest:$6$Yy4ty2jJ$bsVQWo8qlXC6UHq1/qTC3UR60ZJKmKApJ3Wj7DreAy8FxlVKtlDnplFQ7jMLVlDqordE7e4t49GvTb.aI59TP0:1:::::: + nulloktest::1:::::: + +The entry with a hash was rejected and the entry without was accepted. + +The rejection happened because 9e74e90147c "pam_unix: avoid determining +if user exists" introduced the following rejection check (slightly +simplified): + + ... + } else if (p[0] == '\0' && nullok) { + if (hash[0] != '\0') { + retval = PAM_AUTH_ERR; + } + +We should not reject the user with a hash assuming it's non-empty. +The change does that by pushing empty password check into +`verify_pwd_hash()`. + +`NixOS` generates such hashed entries for empty passwords as if they +were non-empty using the following perl code: + + sub hashPassword { + my ($password) = @_; + my $salt = ""; + my @chars = ('.', '/', 0..9, 'A'..'Z', 'a'..'z'); + $salt .= $chars[rand 64] for (1..8); + return crypt($password, '$6$' . $salt . '$'); + } + +Resolves: https://github.com/linux-pam/linux-pam/issues/758 +Fixes: 9e74e90147c "pam_unix: avoid determining if user exists" +Signed-off-by: Sergei Trofimovich +--- + modules/pam_unix/passverify.c | 14 ++++++-------- + 1 file changed, 6 insertions(+), 8 deletions(-) + +diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c +index 30045333..1c83f1aa 100644 +--- a/modules/pam_unix/passverify.c ++++ b/modules/pam_unix/passverify.c +@@ -76,9 +76,13 @@ PAMH_ARG_DECL(int verify_pwd_hash, + + strip_hpux_aging(hash); + hash_len = strlen(hash); +- if (!hash_len) { ++ ++ if (p && p[0] == '\0' && !nullok) { ++ /* The passed password is empty */ ++ retval = PAM_AUTH_ERR; ++ } else if (!hash_len) { + /* the stored password is NULL */ +- if (nullok) { /* this means we've succeeded */ ++ if (p && p[0] == '\0' && nullok) { /* this means we've succeeded */ + D(("user has empty password - access granted")); + retval = PAM_SUCCESS; + } else { +@@ -1109,12 +1113,6 @@ helper_verify_password(const char *name, const char *p, int nullok) + if (pwd == NULL || hash == NULL) { + helper_log_err(LOG_NOTICE, "check pass; user unknown"); + retval = PAM_USER_UNKNOWN; +- } else if (p[0] == '\0' && nullok) { +- if (hash[0] == '\0') { +- retval = PAM_SUCCESS; +- } else { +- retval = PAM_AUTH_ERR; +- } + } else { + retval = verify_pwd_hash(p, hash, nullok); + } +-- +2.47.0 + diff --git a/SPECS/pam.spec b/SPECS/pam.spec index 5b906b6..b8ef4b6 100644 --- a/SPECS/pam.spec +++ b/SPECS/pam.spec @@ -3,7 +3,7 @@ Summary: An extensible library which provides authentication for applications Name: pam Version: 1.5.1 -Release: 15%{?dist} +Release: 22%{?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+. @@ -51,6 +51,27 @@ Patch13: pam-1.5.1-pam-faillock-avoid-logging-erroneous.patch # https://github.com/linux-pam/linux-pam/commit/55f206447a1e4ee26e307e7a9c069236e823b1a5 # https://github.com/linux-pam/linux-pam/commit/80bfda5962e5be3daa70e0fc8c75fc97d1c55121 Patch14: pam-1.5.1-pam-misc-configurable.patch +# https://github.com/linux-pam/linux-pam/commit/d6103b30050554d7b6ca6d55cb5b4ed3c9516663 +Patch15: pam-1.5.1-libpam-close-range.patch +# https://github.com/linux-pam/linux-pam/commit/c85513220c1bd3150e39c6277422d29cfa44acc7 +# https://github.com/linux-pam/linux-pam/commit/1648734a69c31e9ce834da70144ac9a453296807 +Patch16: pam-1.5.1-audit-messages-formatting.patch +# https://github.com/linux-pam/linux-pam/commit/d54870f993e97fe75e2cd0470a3701d5af22877c +Patch17: pam-1.5.1-faillock-create-tallydir.patch +# https://github.com/linux-pam/linux-pam/commit/244b46908df930626535c0cd7c2867407fe8714a +# https://github.com/linux-pam/linux-pam/commit/f26d873435be9f35fa7953493cc07a9bc4e31876 +Patch18: pam-1-5-1-libpam-getlogin.patch +# https://github.com/linux-pam/linux-pam/commit/23393bef92c1e768eda329813d7af55481c6ca9f +Patch19: pam-1.5.1-access-handle-hostnames.patch +# https://github.com/linux-pam/linux-pam/commit/031bb5a5d0d950253b68138b498dc93be69a64cb +Patch20: pam-1.5.1-namespace-protect-dir.patch +# https://github.com/linux-pam/linux-pam/commit/ec1fb9ddc6c252d8c61379e9385ca19c036fcb96 +Patch21: pam-1.5.1-libpam-support-long-lines.patch +# https://github.com/linux-pam/linux-pam/commit/b3020da7da384d769f27a8713257fbe1001878be +# https://github.com/linux-pam/linux-pam/commit/8d0c575336ad301cd14e16ad2fdec6fe621764b8 +Patch22: pam-1.5.1-pam-unix-shadow-password.patch +# https://github.com/linux-pam/linux-pam/commit/940747f88c16e029b69a74e80a2e94f65cb3e628 +Patch23: pam-1.5.1-pam-access-resolve-ip.patch %global _pamlibdir %{_libdir} %global _moduledir %{_libdir}/security @@ -147,6 +168,15 @@ cp %{SOURCE18} . %patch12 -p1 -b .pam-faillock-clarify-missing-user %patch13 -p1 -b .pam-faillock-avoid-logging-erroneous %patch14 -p1 -b .pam-misc-configurable +%patch15 -p1 -b .libpam-close-range +%patch16 -p1 -b .audit-messages-formatting +%patch17 -p1 -b .faillock-create-tallydir +%patch18 -p1 -b .libpam-getlogin +%patch19 -p1 -b .access-handle-hostnames +%patch20 -p1 -b .namespace-protect-dir +%patch21 -p1 -b .libpam-support-long-lines +%patch22 -p1 -b .pam-unix-shadow-password +%patch23 -p1 -b .pam-access-resolve-ip autoreconf -i @@ -402,6 +432,32 @@ done %doc doc/sag/*.txt doc/sag/html %changelog +* Thu Nov 21 2024 Iker Pedrosa - 1.5.1-22 +- pam_access: rework resolving of tokens as hostname. + Resolves: CVE-2024-10963 and RHEL-66245 + +* Wed Nov 6 2024 Diaa Sami - 1.5.1-21 +- pam_unix: always run the helper to obtain shadow password file entries. + CVE-2024-10041. Resolves: RHEL-62880 + +* Tue Jun 18 2024 Iker Pedrosa - 1.5.1-20 +- libpam: support long lines in service files. Resolves: RHEL-40705 + +* Mon Feb 12 2024 Iker Pedrosa - 1.5.1-19 +- pam_namespace: protect_dir(): use O_DIRECTORY to prevent local DoS + situations. CVE-2024-22365. Resolves: RHEL-21244 + +* Fri Jan 26 2024 Iker Pedrosa - 1.5.1-18 +- libpam: use getlogin() from libc and not utmp. Resolves: RHEL-16727 +- pam_access: handle hostnames in access.conf. Resolves: RHEL-22300 + +* Mon Jan 8 2024 Iker Pedrosa - 1.5.1-17 +- pam_faillock: create tallydir before creating tallyfile. Resolves: RHEL-20943 + +* Fri Nov 10 2023 Iker Pedrosa - 1.5.1-16 +- libpam: use close_range() to close file descriptors. Resolves: RHEL-5099 +- fix formatting of audit messages. Resolves: RHEL-5100 + * Mon Jun 26 2023 Iker Pedrosa - 1.5.1-15 - pam_misc: make length of misc_conv() configurable and set to 4096. Resolves: #2215007