Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package pam for openSUSE:Factory checked in at 2026-07-07 20:59:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/pam (Old) and /work/SRC/openSUSE:Factory/.pam.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "pam" Tue Jul 7 20:59:28 2026 rev:154 rq:1364026 version:1.7.2+git12 Changes: -------- --- /work/SRC/openSUSE:Factory/pam/pam.changes 2026-05-27 16:14:05.449846099 +0200 +++ /work/SRC/openSUSE:Factory/.pam.new.1982/pam.changes 2026-07-07 20:59:28.554634200 +0200 @@ -1,0 +2,7 @@ +Tue Jun 16 15:15:09 UTC 2026 - Valentin Lefebvre <[email protected]> + +- Fix password comparison timing leak from pam_userdb + [CVE-2026-54411], [bsc#1268290] + * pam_userdb-fix-password-comparison-timing-leak.patch + +------------------------------------------------------------------- New: ---- pam_userdb-fix-password-comparison-timing-leak.patch ----------(New B)---------- New: [CVE-2026-54411], [bsc#1268290] * pam_userdb-fix-password-comparison-timing-leak.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ pam.spec ++++++ --- /var/tmp/diff_new_pack.rldkYp/_old 2026-07-07 20:59:30.610703694 +0200 +++ /var/tmp/diff_new_pack.rldkYp/_new 2026-07-07 20:59:30.614703830 +0200 @@ -92,6 +92,8 @@ Source23: postlogin-password.pamd Source24: postlogin-session.pamd Patch1: pam-limit-nproc.patch +# PATCH-FIX-UPSTREAM +Patch2: pam_userdb-fix-password-comparison-timing-leak.patch BuildRequires: audit-devel BuildRequires: bison BuildRequires: flex ++++++ pam_userdb-fix-password-comparison-timing-leak.patch ++++++ >From 30708d973b63891bf700299ce3ae0f1086398284 Mon Sep 17 00:00:00 2001 From: vlefebvre <[email protected]> Date: Tue, 16 Jun 2026 16:31:29 +0200 Subject: [PATCH] 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 <[email protected]> --- libpam/include/pam_inline.h | 20 +++++++++ modules/pam_userdb/pam_userdb.c | 75 +++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h index d79d6fdf..86d131cf 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> @@ -244,4 +245,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 bdb8553c..b37f096c 100644 --- a/modules/pam_userdb/pam_userdb.c +++ b/modules/pam_userdb/pam_userdb.c @@ -321,15 +321,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_string(stored); + free(stored); } if (cryptmode && pam_str_skip_icase_prefix(cryptmode, "none") == NULL @@ -361,36 +370,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 = db_firstkey(dbm); key.dptr != NULL; key = db_nextkey(dbm, key)) { - 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) { db_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_string(stored_pass); + free(stored_pass); + } + + if (compare == 0) { + db_close(dbm); + return 0; /* match */ } } db_close(dbm); -- 2.51.0
