Fix CVE-2026-54411: pam_userdb password comparison timing leak
Backport fix for CVE-2026-54411 to pam 1.3.1 on RHEL 8.
The pam_userdb module used strncmp/strncasecmp for password
comparison, which is vulnerable to timing side-channel attacks
that can leak password prefix bytes and length (CWE-208).
A new patch (pam-1.3.1-CVE-2026-54411.patch) adds constant-time
string comparison helpers (pam_consttime_streq and
pam_consttime_strcaseeq) to pam_inline.h and rewrites
pam_userdb.c to use them. The patch combines two upstream
commits (c11ccdfa and 30708d97) adapted for the RHEL 8 codebase.
CVE: CVE-2026-54411
Upstream patches:
- c11ccdfad1.patch
- https://src.fedoraproject.org/rpms/pam/raw/main/f/pam-1.7.2-pam-userdb-fix-password-leak.patch
Resolves: RHEL-191699
This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.
Assisted-by: Ymir
This commit is contained in:
parent
8fb633cfad
commit
32839ce5e4
204
pam-1.3.1-CVE-2026-54411.patch
Normal file
204
pam-1.3.1-CVE-2026-54411.patch
Normal file
@ -0,0 +1,204 @@
|
||||
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);
|
||||
10
pam.spec
10
pam.spec
@ -3,7 +3,7 @@
|
||||
Summary: An extensible library which provides authentication for applications
|
||||
Name: pam
|
||||
Version: 1.3.1
|
||||
Release: 39%{?dist}
|
||||
Release: 40%{?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+.
|
||||
@ -131,6 +131,9 @@ Patch77: pam-1.3.1-pam-namespace-rebase.patch
|
||||
# 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
|
||||
@ -257,6 +260,7 @@ cp %{SOURCE18} .
|
||||
%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
|
||||
|
||||
@ -510,6 +514,10 @@ 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user