Compare commits

...

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

10 changed files with 1 additions and 3592 deletions

View File

@ -1,204 +0,0 @@
From 2cde1639a00d9c46ed5677a65aa3ef4e7ed4fede Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgzones@googlemail.com>
Date: Sat, 20 Jan 2024 14:03:51 +0100
Subject: [PATCH 1/2] libpam: add helper to compare strings in constant time
Add a helper function to compare two strings for equality, that performs
the same amount of operations based on the first argument, regardless of
the length of the second argument, or the position of the first
difference.
This can be used as defense-in-depth mitigation against timing attacks
of password comparisons.
---
libpam/include/pam_inline.h | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h
index dd93394..2450d50 100644
--- a/libpam/include/pam_inline.h
+++ b/libpam/include/pam_inline.h
@@ -100,4 +100,18 @@ pam_snprintf(char *str, size_t size, const char *fmt, ...)
pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \
##__VA_ARGS__)
+static inline int
+pam_consttime_streq(const char *userinput, const char *secret) {
+ volatile const char *u = userinput, *s = secret;
+ volatile int ret = 0;
+
+ do {
+ ret |= *u ^ *s;
+
+ s += !!*s;
+ } while (*u++ != '\0');
+
+ return ret == 0;
+}
+
#endif /* PAM_INLINE_H */
From f22f663d1fdf3ab53a9f569190a0070a130dd7d5 Mon Sep 17 00:00:00 2001
From: vlefebvre <valentin.lefebvre@suse.com>
Date: Tue, 16 Jun 2026 16:31:29 +0200
Subject: [PATCH 2/2] pam_userdb: fix password comparison timing leak
* libpam/include/pam_inline.h: Include <ctype.h>.
(pam_consttime_strcaseeq): New function that implements a constant-time,
case-insensitive string equality check.
* modules/pam_userdb/pam_userdb.c (user_lookup): Use it along with
pam_consttime_streq instead of strncmp and strncasecmp to fix
timing side-channel that leaks password prefix bytes and length
(CWE-208).
Resolves: https://github.com/linux-pam/linux-pam/issues/992
Co-authored-by: Dmitry V. Levin <ldv@strace.io>
---
libpam/include/pam_inline.h | 20 +++++++++
modules/pam_userdb/pam_userdb.c | 76 +++++++++++++++++++--------------
2 files changed, 65 insertions(+), 31 deletions(-)
diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h
index 2450d50..b6aa0cc 100644
--- a/libpam/include/pam_inline.h
+++ b/libpam/include/pam_inline.h
@@ -9,6 +9,7 @@
#define PAM_INLINE_H
#include "pam_cc_compat.h"
+#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -114,4 +115,23 @@ pam_consttime_streq(const char *userinput, const char *secret) {
return ret == 0;
}
+/*
+ * Constant-time, case-insensitive string equality check.
+ * Same contract as pam_consttime_streq but uses tolower() on each byte.
+ * Runs for exactly strlen(userinput)+1 iterations regardless of secret.
+ */
+static inline int
+pam_consttime_strcaseeq(const char *userinput, const char *secret) {
+ volatile const char *u = userinput, *s = secret;
+ volatile int ret = 0;
+
+ do {
+ ret |= tolower((unsigned char)*u) ^ tolower((unsigned char)*s);
+
+ s += !!*s;
+ } while (*u++ != '\0');
+
+ return ret == 0;
+}
+
#endif /* PAM_INLINE_H */
diff --git a/modules/pam_userdb/pam_userdb.c b/modules/pam_userdb/pam_userdb.c
index 8794cf6..7d425c3 100644
--- a/modules/pam_userdb/pam_userdb.c
+++ b/modules/pam_userdb/pam_userdb.c
@@ -48,6 +48,7 @@
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <security/_pam_macros.h>
+#include "pam_inline.h"
/*
* Conversation function to obtain the user's password
@@ -259,15 +260,24 @@ user_lookup (pam_handle_t *pamh, const char *database, const char *cryptmode,
} else {
/* Unknown password encryption method -
- * default to plaintext password storage
+ * default to plaintext password storage.
+ * Use constant-time comparison: strncmp/strncasecmp leak prefix bytes
+ * and the length pre-check leaks the password length (CWE-208).
*/
- if (strlen(pass) != (size_t)data.dsize) {
- compare = 1; /* wrong password len -> wrong password */
- } else if (ctrl & PAM_ICASE_ARG) {
- compare = strncasecmp(data.dptr, pass, data.dsize);
+ /* libdb is not guaranteed to produce null-terminated strings */
+ char *stored = strndup(data.dptr, data.dsize);
+ if (stored == NULL) {
+ pam_syslog(pamh, LOG_CRIT, "strndup failed: data.dptr");
+ compare = -2;
} else {
- compare = strncmp(data.dptr, pass, data.dsize);
+ if (ctrl & PAM_ICASE_ARG) {
+ compare = pam_consttime_strcaseeq(pass, stored) ? 0 : 1;
+ } else {
+ compare = pam_consttime_streq(pass, stored) ? 0 : 1;
+ }
+ _pam_overwrite(stored);
+ free(stored);
}
if (cryptmode && strncasecmp(cryptmode, "none", 4)
@@ -299,36 +309,40 @@ user_lookup (pam_handle_t *pamh, const char *database, const char *cryptmode,
}
/* now handle the key_only case */
+ size_t ulen = strlen(user);
for (key = dbm_firstkey(dbm);
key.dptr != NULL;
key = dbm_nextkey(dbm)) {
- int compare;
- /* first compare the user portion (case sensitive) */
- compare = strncmp(key.dptr, user, strlen(user));
- if (compare == 0) {
- /* assume failure */
- compare = -1;
- /* if we have the divider where we expect it to be... */
- if (key.dptr[strlen(user)] == '-') {
- saw_user = 1;
- if ((size_t)key.dsize == strlen(user) + 1 + strlen(pass)) {
- if (ctrl & PAM_ICASE_ARG) {
- /* compare the password portion (case insensitive)*/
- compare = strncasecmp(key.dptr + strlen(user) + 1,
- pass,
- strlen(pass));
- } else {
- /* compare the password portion (case sensitive) */
- compare = strncmp(key.dptr + strlen(user) + 1,
- pass,
- strlen(pass));
- }
- }
- }
- if (compare == 0) {
+ /* assume failure */
+ int compare = -1;
+
+ /*
+ * First compare the user portion (case sensitive);
+ * user is caller-supplied, so this memcmp leaks nothing secret.
+ */
+ if ((size_t)key.dsize > ulen &&
+ key.dptr[ulen] == '-' &&
+ memcmp(key.dptr, user, ulen) == 0) {
+ saw_user = 1;
+ char *stored_pass = strndup(key.dptr + ulen + 1,
+ key.dsize - ulen - 1);
+ if (stored_pass == NULL) {
dbm_close(dbm);
- return 0; /* match */
+ return -2;
}
+ /* compare the password portion (case (in)sensitive) */
+ if (ctrl & PAM_ICASE_ARG) {
+ compare = pam_consttime_strcaseeq(pass, stored_pass) ? 0 : 1;
+ } else {
+ compare = pam_consttime_streq(pass, stored_pass) ? 0 : 1;
+ }
+ _pam_overwrite(stored_pass);
+ free(stored_pass);
+ }
+
+ if (compare == 0) {
+ dbm_close(dbm);
+ return 0; /* match */
}
}
dbm_close(dbm);

View File

@ -1,33 +0,0 @@
diff -up Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c.audit-messages-formatting Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c
--- Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c.audit-messages-formatting 2024-03-11 16:21:51.928946604 +0100
+++ Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c 2024-03-11 16:21:51.934946609 +0100
@@ -334,7 +334,7 @@ check_tally(pam_handle_t *pamh, struct o
(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);
}
@@ -450,7 +450,7 @@ write_tally(pam_handle_t *pamh, struct o
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);
diff -up Linux-PAM-1.3.1/modules/pam_selinux/pam_selinux.c.audit-messages-formatting Linux-PAM-1.3.1/modules/pam_selinux/pam_selinux.c
--- Linux-PAM-1.3.1/modules/pam_selinux/pam_selinux.c.audit-messages-formatting 2024-03-11 16:21:51.934946609 +0100
+++ Linux-PAM-1.3.1/modules/pam_selinux/pam_selinux.c 2024-03-11 16:23:24.521025061 +0100
@@ -106,7 +106,7 @@ int send_audit_message(pam_handle_t *pam
pam_syslog(pamh, LOG_ERR, "Error translating 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) {
pam_syslog(pamh, LOG_ERR, "Error allocating memory.");

View File

@ -1,595 +0,0 @@
diff -up Linux-PAM-1.3.1/libpam/pam_handlers.c.libpam-support-long-lines Linux-PAM-1.3.1/libpam/pam_handlers.c
--- Linux-PAM-1.3.1/libpam/pam_handlers.c.libpam-support-long-lines 2024-11-04 13:17:27.395991844 +0100
+++ Linux-PAM-1.3.1/libpam/pam_handlers.c 2024-11-04 14:45:22.583441849 +0100
@@ -16,21 +16,30 @@
#include <fcntl.h>
#include <unistd.h>
-#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 */
@@ -58,12 +67,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;
@@ -73,7 +85,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) {
@@ -232,10 +244,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;
}
@@ -548,88 +561,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';
+}
+
/*
- * This is where we read a line of the PAM config file. The line may be
- * preceeded by lines of comments and also extended with "\\\n"
+ * 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';
-static int _pam_assemble_line(FILE *f, char *buffer, int buf_len)
+ 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)
{
- char *p = buffer;
- char *endp = buffer + buf_len;
- char *s, *os;
- int used = 0;
+ int ret = 1;
+ char *p = *end;
- /* loop broken with a 'break' when a non-'\\n' ended line is read */
+ /*
+ * 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;
+ }
- D(("called."));
- for (;;) {
- if (p >= endp) {
- /* Overflow */
- D(("_pam_assemble_line: overflow"));
- return -1;
- }
- if (fgets(p, endp - p, f) == NULL) {
- if (used) {
- /* Incomplete read */
- return -1;
- } else {
- /* EOF */
- return 0;
- }
- }
+ return ret;
+}
- /* skip leading spaces --- line may be blank */
+/*
+ * 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;
- s = p + strspn(p, " \n\t");
- if (*s && (*s != '#')) {
- os = s;
+ *start = line;
+ *end = line + len;
- /*
- * we are only interested in characters before the first '#'
- * character
- */
+ ret = _pam_str_unescnl(*start, end) || strchr(*start, '#') != NULL;
- while (*s && *s != '#')
- ++s;
- if (*s == '#') {
- *s = '\0';
- used += strlen(os);
- break; /* the line has been read */
- }
+ *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, struct line_buffer *buffer)
+{
+ int ret = 0;
+
+ /* loop broken with a 'break' when a non-'\\n' ended line is read */
- s = os;
+ D(("called."));
- /*
- * 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!
- */
+ _pam_buffer_shift(buffer);
- s += strlen(s);
- while (s > os && ((*--s == ' ') || (*s == '\t')
- || (*s == '\n')));
+ for (;;) {
+ char *start, *end;
+ ssize_t n;
+ int eol;
- /* 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 ... */
+ if ((n = getline(&buffer->chunk, &buffer->chunk_size, f)) == -1) {
+ if (ret) {
+ /* Incomplete read */
+ ret = -1;
} else {
- /* End of the line! */
- used += strlen(os);
- break; /* this is the complete line */
+ /* EOF */
+ ret = 0;
}
+ break;
+ }
+
+ 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 *
@@ -761,7 +929,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.3.1/libpam/pam_misc.c.libpam-support-long-lines Linux-PAM-1.3.1/libpam/pam_misc.c
--- Linux-PAM-1.3.1/libpam/pam_misc.c.libpam-support-long-lines 2017-02-10 11:10:15.000000000 +0100
+++ Linux-PAM-1.3.1/libpam/pam_misc.c 2024-11-04 14:47:31.147672755 +0100
@@ -39,6 +39,8 @@
#include <stdarg.h>
#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
@@ -163,60 +165,55 @@ char *_pam_memdup(const char *x, int len
/* Generate argv, argc from s */
/* caller must free(argv) */
-int _pam_mkargv(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.3.1/libpam/pam_private.h.libpam-support-long-lines Linux-PAM-1.3.1/libpam/pam_private.h
--- Linux-PAM-1.3.1/libpam/pam_private.h.libpam-support-long-lines 2024-11-04 13:17:27.404991868 +0100
+++ Linux-PAM-1.3.1/libpam/pam_private.h 2024-11-04 14:51:31.654635328 +0100
@@ -16,6 +16,7 @@
#include "config.h"
+#include <stddef.h>
#include <syslog.h>
#include <security/pam_appl.h>
@@ -266,7 +267,7 @@ char *_pam_strdup(const char *s);
char *_pam_memdup(const char *s, int len);
-int _pam_mkargv(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.3.1/modules/pam_exec/pam_exec.c.libpam-support-long-lines Linux-PAM-1.3.1/modules/pam_exec/pam_exec.c
--- Linux-PAM-1.3.1/modules/pam_exec/pam_exec.c.libpam-support-long-lines 2017-02-10 11:10:15.000000000 +0100
+++ Linux-PAM-1.3.1/modules/pam_exec/pam_exec.c 2024-11-04 13:17:27.434991946 +0100
@@ -408,7 +408,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.3.1/modules/pam_filter/pam_filter.c.libpam-support-long-lines Linux-PAM-1.3.1/modules/pam_filter/pam_filter.c
--- Linux-PAM-1.3.1/modules/pam_filter/pam_filter.c.libpam-support-long-lines 2017-02-10 11:10:15.000000000 +0100
+++ Linux-PAM-1.3.1/modules/pam_filter/pam_filter.c 2024-11-04 13:17:27.434991946 +0100
@@ -101,7 +101,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.3.1/modules/pam_motd/pam_motd.c.libpam-support-long-lines Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c
--- Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c.libpam-support-long-lines 2024-11-04 13:17:27.423991918 +0100
+++ Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c 2024-11-04 13:17:27.434991946 +0100
@@ -83,14 +83,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;
@@ -184,13 +184,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) {
@@ -302,11 +302,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;

View File

@ -1,215 +0,0 @@
From 08992030c56c940c0707ccbc442b1c325aa01e6d Mon Sep 17 00:00:00 2001
From: Tomas Mraz <tmraz@fedoraproject.org>
Date: Tue, 6 Apr 2021 12:27:38 +0200
Subject: [PATCH] pam_access: clean up the remote host matching code
* modules/pam_access/pam_access.c (from_match): Split out remote_match()
function and avoid calling it when matching against LOCAL keyword.
There is also no point in doing domain match against TTY or SERVICE.
---
modules/pam_access/pam_access.c | 44 +++++++++++++++++++++------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/modules/pam_access/pam_access.c b/modules/pam_access/pam_access.c
index 98848c54..277192b9 100644
--- a/modules/pam_access/pam_access.c
+++ b/modules/pam_access/pam_access.c
@@ -160,6 +160,7 @@ static int list_match (pam_handle_t *, char *, char *, struct login_info *,
static int user_match (pam_handle_t *, char *, struct login_info *);
static int group_match (pam_handle_t *, const char *, const char *, int);
static int from_match (pam_handle_t *, char *, struct login_info *);
+static int remote_match (pam_handle_t *, char *, struct login_info *);
static int string_match (pam_handle_t *, const char *, const char *, int);
static int network_netmask_match (pam_handle_t *, const char *, const char *, struct login_info *);
@@ -589,11 +590,9 @@ group_match (pam_handle_t *pamh, const char *tok, const char* usr,
/* from_match - match a host or tty against a list of tokens */
static int
-from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item)
+from_match (pam_handle_t *pamh, char *tok, struct login_info *item)
{
const char *string = item->from;
- int tok_len;
- int str_len;
int rv;
if (item->debug)
@@ -616,14 +615,29 @@ from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item)
} else if ((rv = string_match(pamh, tok, string, item->debug)) != NO) {
/* ALL or exact match */
return rv;
- } else if (tok[0] == '.') { /* domain: match last fields */
- if ((str_len = strlen(string)) > (tok_len = strlen(tok))
- && strcasecmp(tok, string + str_len - tok_len) == 0)
- return (YES);
- } else if (item->from_remote_host == 0) { /* local: no PAM_RHOSTS */
- if (strcasecmp(tok, "LOCAL") == 0)
- return (YES);
- } else if (tok[(tok_len = strlen(tok)) - 1] == '.') {
+ } else if (strcasecmp(tok, "LOCAL") == 0) {
+ /* LOCAL matches only local accesses */
+ if (!item->from_remote_host)
+ return YES;
+ return NO;
+ } else if (item->from_remote_host) {
+ return remote_match(pamh, tok, item);
+ }
+ return NO;
+}
+
+static int
+remote_match (pam_handle_t *pamh, char *tok, struct login_info *item)
+{
+ const char *string = item->from;
+ size_t tok_len = strlen(tok);
+ size_t str_len;
+
+ if (tok[0] == '.') { /* domain: match last fields */
+ if ((str_len = strlen(string)) > tok_len
+ && strcasecmp(tok, string + str_len - tok_len) == 0)
+ return YES;
+ } else if (tok[tok_len - 1] == '.') {
struct addrinfo hint;
memset (&hint, '\0', sizeof (hint));
@@ -661,13 +675,11 @@ from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item)
runp = runp->ai_next;
}
}
- } else {
- /* Assume network/netmask with a IP of a host. */
- if (network_netmask_match(pamh, tok, string, item))
- return YES;
+ return NO;
}
- return NO;
+ /* Assume network/netmask with an IP of a host. */
+ return network_netmask_match(pamh, tok, string, item);
}
/* string_match - match a string against one token */
--
2.47.0
From ecaaf4456e5aeacae1acdb1775bb5aadd3b19e13 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 16 Oct 2024 12:41:09 +0200
Subject: [PATCH 1/2] pam_access: always match local address
* modules/pam_access/pam_access.c: match the local address regardless of
the IP version in use.
In some circumstances the `localhost` may be translated to IPv4 or IPv6,
but the configuration file only indicated the address for one of the two
versions. Since the originating value is set in `PAM_RHOST` and PAM has
no control over it, let's match the local addresses regardless of the IP
version in use.
Resolves: https://issues.redhat.com/browse/RHEL-23018
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
modules/pam_access/pam_access.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/modules/pam_access/pam_access.c b/modules/pam_access/pam_access.c
index bfbc6d57..48e7c7e9 100644
--- a/modules/pam_access/pam_access.c
+++ b/modules/pam_access/pam_access.c
@@ -306,6 +306,23 @@ isipaddr (const char *string, int *addr_type,
return is_ip;
}
+/* is_local_addr - checks if the IP address is local */
+static int
+is_local_addr (const char *string, int addr_type)
+{
+ if (addr_type == AF_INET) {
+ if (strcmp(string, "127.0.0.1") == 0) {
+ return YES;
+ }
+ } else if (addr_type == AF_INET6) {
+ if (strcmp(string, "::1") == 0) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
/* are_addresses_equal - translate IP address strings to real IP
* addresses and compare them to find out if they are equal.
@@ -327,9 +344,18 @@ are_addresses_equal (const char *ipaddr0, const char *ipaddr1,
if (isipaddr (ipaddr1, &addr_type1, &addr1) == NO)
return NO;
- if (addr_type0 != addr_type1)
- /* different address types */
+ if (addr_type0 != addr_type1) {
+ /* different address types, but there is still a possibility that they are
+ * both local addresses
+ */
+ int local1 = is_local_addr(ipaddr0, addr_type0);
+ int local2 = is_local_addr(ipaddr1, addr_type1);
+
+ if (local1 == YES && local2 == YES)
+ return YES;
+
return NO;
+ }
if (netmask != NULL) {
/* Got a netmask, so normalize addresses? */
--
2.47.0
From 641dfd1084508c63f3590e93a35b80ffc50774e5 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Fri, 18 Oct 2024 10:27:07 +0200
Subject: [PATCH 2/2] pam_access: clarify `LOCAL` keyword behaviour
* modules/pam_access/access.conf.5.xml: `LOCAL` keyword behaviour
explanation was focused on the development internals. Let's clarify it
by rephrasing it to something a sysadmin can understand.
Resolves: https://issues.redhat.com/browse/RHEL-39943
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
modules/pam_access/access.conf.5.xml | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/modules/pam_access/access.conf.5.xml b/modules/pam_access/access.conf.5.xml
index 35a1a8fe..0b93db00 100644
--- a/modules/pam_access/access.conf.5.xml
+++ b/modules/pam_access/access.conf.5.xml
@@ -79,17 +79,12 @@
with network mask (where network mask can be a decimal number or an
internet address also), <emphasis>ALL</emphasis> (which always matches)
or <emphasis>LOCAL</emphasis>. The <emphasis>LOCAL</emphasis>
- keyword matches if and only if
- <citerefentry><refentrytitle>pam_get_item</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
- when called with an <parameter>item_type</parameter> of
- <emphasis>PAM_RHOST</emphasis>, returns <code>NULL</code> or an
- empty string (and therefore the
- <replaceable>origins</replaceable> field is compared against the
- return value of
- <citerefentry><refentrytitle>pam_get_item</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- called with an <parameter>item_type</parameter> of
- <emphasis>PAM_TTY</emphasis> or, absent that,
- <emphasis>PAM_SERVICE</emphasis>).
+ keyword matches when the user connects without a network
+ connection (e.g., <emphasis>su</emphasis>,
+ <emphasis>login</emphasis>). A connection through the loopback
+ device (e.g., <command>ssh user@localhost</command>) is
+ considered a network connection, and thus, the
+ <emphasis>LOCAL</emphasis> keyword does not match.
</para>
<para>
--
2.47.0

View File

@ -1,211 +0,0 @@
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.
</para>
+ <para>
+ An IPv6 link local host address must contain the interface
+ identifier. IPv6 link local network/netmask is not supported.
+ </para>
+ <para>
+ Hostnames should be written as Fully-Qualified Host Name (FQHN) to avoid
+ confusion with device names or PAM service names.
+ </para>
</refsect1>
<refsect1 id="access.conf-see_also">
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 @@
<arg choice="opt">
debug
</arg>
+ <arg choice="opt" rep="norepeat">
+ noaudit
+ </arg>
<arg choice="opt">
nodefgroup
</arg>
- <arg choice="opt">
- noaudit
+ <arg choice="opt" rep="norepeat">
+ nodns
</arg>
<arg choice="opt">
accessfile=<replaceable>file</replaceable>
@@ -114,7 +117,46 @@
<varlistentry>
<term>
- <option>fieldsep=<replaceable>separators</replaceable></option>
+ nodefgroup
+ </term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ nodns
+ </term>
+ <listitem>
+ <para>
+ 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 <filename>access.conf</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ quiet_log
+ </term>
+ <listitem>
+ <para>
+ Do not log denials with
+ <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ fieldsep=separators
</term>
<listitem>
<para>
@@ -152,20 +194,6 @@
</para>
</listitem>
</varlistentry>
-
- <varlistentry>
- <term>
- <option>nodefgroup</option>
- </term>
- <listitem>
- <para>
- 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.
- </para>
- </listitem>
- </varlistentry>
</variablelist>
</refsect1>
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)

View File

@ -1,67 +0,0 @@
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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
/*
@@ -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 */

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
diff -up Linux-PAM-1.5.1/modules/pam_unix/passverify.c.pam-unix-blank-expiration Linux-PAM-1.5.1/modules/pam_unix/passverify.c
--- Linux-PAM-1.5.1/modules/pam_unix/passverify.c.pam-unix-blank-expiration 2025-04-14 14:40:34.181165272 +0200
+++ Linux-PAM-1.5.1/modules/pam_unix/passverify.c 2025-04-14 14:42:11.740385381 +0200
@@ -5,6 +5,7 @@
#include <security/_pam_macros.h>
#include <security/pam_modules.h>
#include "support.h"
+#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
@@ -286,14 +286,29 @@ PAMH_ARG_DECL(int get_pwd_hash,
return PAM_SUCCESS;
}
+/*
+ * invariant: 0 <= num1
+ * invariant: 0 <= num2
+ */
+static int
+subtract(long num1, long num2)
+{
+ long value = num1 - num2;
+ if (value < INT_MIN)
+ return INT_MIN;
+ if (value > INT_MAX)
+ return INT_MAX;
+ return (int)value;
+}
+
PAMH_ARG_DECL(int check_shadow_expiry,
struct spwd *spent, int *daysleft)
{
- long int curdays;
+ long int curdays, passed;
*daysleft = -1;
curdays = (long int)(time(NULL) / (60 * 60 * 24));
- D(("today is %d, last change %d", curdays, spent->sp_lstchg));
- if ((curdays >= spent->sp_expire) && (spent->sp_expire != -1)) {
+ D(("today is %ld, last change %ld", curdays, spent->sp_lstchg));
+ if (spent->sp_expire >= 0 && curdays >= spent->sp_expire) {
D(("account expired"));
return PAM_ACCT_EXPIRED;
}
@@ -308,31 +323,41 @@ PAMH_ARG_DECL(int check_shadow_expiry,
*daysleft = 0;
return PAM_NEW_AUTHTOK_REQD;
}
+ if (spent->sp_lstchg < 0) {
+ D(("password aging disabled"));
+ return PAM_SUCCESS;
+ }
if (curdays < spent->sp_lstchg) {
pam_syslog(pamh, LOG_DEBUG,
"account %s has password changed in future",
spent->sp_namp);
return PAM_SUCCESS;
}
- if ((curdays - spent->sp_lstchg > spent->sp_max)
- && (curdays - spent->sp_lstchg > spent->sp_inact)
- && (curdays - spent->sp_lstchg > spent->sp_max + spent->sp_inact)
- && (spent->sp_max != -1) && (spent->sp_inact != -1)) {
- *daysleft = (int)((spent->sp_lstchg + spent->sp_max) - curdays);
- D(("authtok expired"));
- return PAM_AUTHTOK_EXPIRED;
- }
- if ((curdays - spent->sp_lstchg > spent->sp_max) && (spent->sp_max != -1)) {
- D(("need a new password 2"));
- return PAM_NEW_AUTHTOK_REQD;
- }
- if ((curdays - spent->sp_lstchg > spent->sp_max - spent->sp_warn)
- && (spent->sp_max != -1) && (spent->sp_warn != -1)) {
- *daysleft = (int)((spent->sp_lstchg + spent->sp_max) - curdays);
- D(("warn before expiry"));
+ passed = curdays - spent->sp_lstchg;
+ if (spent->sp_max >= 0) {
+ if (spent->sp_inact >= 0) {
+ long inact = spent->sp_max < LONG_MAX - spent->sp_inact ?
+ spent->sp_max + spent->sp_inact : LONG_MAX;
+ if (passed >= inact) {
+ *daysleft = subtract(inact, passed);
+ D(("authtok expired"));
+ return PAM_AUTHTOK_EXPIRED;
+ }
+ }
+ if (passed >= spent->sp_max) {
+ D(("need a new password 2"));
+ return PAM_NEW_AUTHTOK_REQD;
+ }
+ if (spent->sp_warn > 0) {
+ long warn = spent->sp_warn > spent->sp_max ? -1 :
+ spent->sp_max - spent->sp_warn;
+ if (passed >= warn) {
+ *daysleft = subtract(spent->sp_max, passed);
+ D(("warn before expiry"));
+ }
+ }
}
- if ((curdays - spent->sp_lstchg < spent->sp_min)
- && (spent->sp_min != -1)) {
+ if (spent->sp_min > 0 && passed < spent->sp_min) {
/*
* The last password change was too recent. This error will be ignored
* if no password change is attempted.

View File

@ -1,47 +0,0 @@
diff -up Linux-PAM-1.3.1/modules/pam_unix/passverify.c.pam-unix-shadow-password Linux-PAM-1.3.1/modules/pam_unix/passverify.c
--- Linux-PAM-1.3.1/modules/pam_unix/passverify.c.pam-unix-shadow-password 2024-11-05 13:02:14.637785962 +0100
+++ Linux-PAM-1.3.1/modules/pam_unix/passverify.c 2024-11-05 13:02:14.643785975 +0100
@@ -73,9 +73,13 @@ verify_pwd_hash(const char *p, char *has
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 {
@@ -192,17 +196,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;

View File

@ -3,7 +3,7 @@
Summary: An extensible library which provides authentication for applications
Name: pam
Version: 1.3.1
Release: 40%{?dist}
Release: 33%{?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+.
@ -110,30 +110,6 @@ Patch68: pam-1.3.1-faillock-create-tallydir.patch
Patch69: pam-1.3.1-access-handle-hostnames.patch
# https://github.com/linux-pam/linux-pam/commit/031bb5a5d0d950253b68138b498dc93be69a64cb
Patch70: pam-1.3.1-namespace-protect-dir.patch
# https://github.com/linux-pam/linux-pam/commit/c85513220c1bd3150e39c6277422d29cfa44acc7
# https://github.com/linux-pam/linux-pam/commit/1648734a69c31e9ce834da70144ac9a453296807
Patch71: pam-1.3.1-audit-messages-formatting.patch
# https://github.com/linux-pam/linux-pam/commit/b3020da7da384d769f27a8713257fbe1001878be
# https://github.com/linux-pam/linux-pam/commit/8d0c575336ad301cd14e16ad2fdec6fe621764b8
Patch72: pam-1.3.1-pam-unix-shadow-password.patch
# https://github.com/linux-pam/linux-pam/commit/08992030c56c940c0707ccbc442b1c325aa01e6d
# https://github.com/linux-pam/linux-pam/commit/641dfd1084508c63f3590e93a35b80ffc50774e5
Patch73: pam-1.3.1-pam-access-local.patch
# https://github.com/linux-pam/linux-pam/commit/ec1fb9ddc6c252d8c61379e9385ca19c036fcb96
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
# https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e
Patch77: pam-1.3.1-pam-namespace-rebase.patch
# https://github.com/linux-pam/linux-pam/commit/2c711ce57ced9f97c2cf4c8d59c1730447a7bd7f
# https://github.com/linux-pam/linux-pam/commit/51a06bc8cc2278c6e81c9c08a9381c9eb0d2de96
# https://github.com/linux-pam/linux-pam/commit/470b5bdd8fd29d6b35e3a80f9a57bdd4b2438200
Patch78: pam-1.3.1-pam-unix-blank-expiration.patch
# https://github.com/linux-pam/linux-pam/commit/c11ccdfad1596199713f75a61f34672f7529ab73
# https://github.com/linux-pam/linux-pam/commit/30708d973b63891bf700299ce3ae0f1086398284
Patch79: pam-1.3.1-CVE-2026-54411.patch
%define _pamlibdir %{_libdir}
%define _moduledir %{_libdir}/security
@ -252,15 +228,6 @@ cp %{SOURCE18} .
%patch68 -p1 -b .faillock-create-tallydir
%patch69 -p1 -b .access-handle-hostnames
%patch70 -p1 -b .namespace-protect-dir
%patch71 -p1 -b .audit-messages-formatting
%patch72 -p1 -b .pam-unix-shadow-password
%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
%patch78 -p1 -b .pam-unix-blank-expiration
%patch79 -p1 -b .CVE-2026-54411
autoreconf -i
@ -514,32 +481,6 @@ done
%doc doc/specs/rfc86.0.txt
%changelog
* Sun Jul 12 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1.3.1-40
- pam_userdb: fix password comparison timing leak.
Resolves: CVE-2026-54411 and RHEL-191699
* Fri Nov 14 2025 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-39
- pam_unix: sync expiry checks with shadow.
Resolves: RHEL-70476
* Wed Aug 6 2025 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-38
- pam_namespace: fix potential privilege escalation.
Resolves: CVE-2025-6020 and RHEL-96724
* Mon Nov 25 2024 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-36
- pam_access: rework resolving of tokens as hostname.
Resolves: CVE-2024-10963 and RHEL-66242
* Mon Nov 4 2024 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-35
- pam_unix: always run the helper to obtain shadow password file entries.
CVE-2024-10041. Resolves: RHEL-62877
- pam_access: always match local address and clarify LOCAL keyword behaviour.
Resolves: RHEL-23018
- libpam: support long lines in service files. Resolves: RHEL-5051
* Thu Apr 4 2024 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-34
- fix formatting of audit messages. Resolves: RHEL-28620
* Mon Feb 12 2024 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-33
- pam_namespace: protect_dir(): use O_DIRECTORY to prevent local DoS
situations. CVE-2024-22365. Resolves: RHEL-21242