pam_userdb: fix password comparison timing leak
Resolves: CVE-2026-54411 and RHEL-191705 Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
This commit is contained in:
parent
8538bf24a7
commit
afa97f427c
172
pam-1.6.1-pam-userdb-password-timing-leak.patch
Normal file
172
pam-1.6.1-pam-userdb-password-timing-leak.patch
Normal file
@ -0,0 +1,172 @@
|
||||
From 30708d973b63891bf700299ce3ae0f1086398284 Mon Sep 17 00:00:00 2001
|
||||
From: vlefebvre <valentin.lefebvre@suse.com>
|
||||
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 <ldv@strace.io>
|
||||
---
|
||||
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 d79d6fdf2..86d131cf4 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>
|
||||
@@ -210,4 +211,36 @@ pam_read_passwords(int fd, int npass, char **passwords)
|
||||
return i;
|
||||
}
|
||||
|
||||
+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;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * 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 bdb8553cc..b37f096cd 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);
|
||||
|
||||
|
||||
9
pam.spec
9
pam.spec
@ -4,7 +4,7 @@
|
||||
Summary: An extensible library which provides authentication for applications
|
||||
Name: pam
|
||||
Version: 1.6.1
|
||||
Release: 10%{?dist}
|
||||
Release: 11%{?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 and pam_loginuid modules are GPLv2+.
|
||||
@ -43,6 +43,8 @@ Patch10: pam-1.6.1-pam-faillock-skip.patch
|
||||
Patch11: pam-1.6.1-pam-access-uid-gid-access-conf.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/fc927d8f1a6d81e5bcf58096871684b35b793fe2
|
||||
Patch12: pam-1.6.1-pam-access-fix-group-name-match.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/30708d973b63891bf700299ce3ae0f1086398284
|
||||
Patch13: pam-1.6.1-pam-userdb-password-timing-leak.patch
|
||||
|
||||
%{load:%{SOURCE3}}
|
||||
|
||||
@ -145,6 +147,7 @@ cp %{SOURCE18} .
|
||||
%patch -P 10 -p1 -b .pam-faillock-skip
|
||||
%patch -P 11 -p1 -b .pam-access-uid-gid-access-conf
|
||||
%patch -P 12 -p1 -b .pam-access-fix-group-name-match
|
||||
%patch -P 13 -p1 -b .pam-userdb-password-timing-leak
|
||||
|
||||
autoreconf -i
|
||||
|
||||
@ -383,6 +386,10 @@ done
|
||||
%{_pam_libdir}/libpam_misc.so.%{so_ver}*
|
||||
|
||||
%changelog
|
||||
* Mon Jul 20 2026 Iker Pedrosa <ipedrosa@redhat.com> - 1.6.1-11
|
||||
- pam_userdb: fix password comparison timing leak.
|
||||
Resolves: CVE-2026-54411 and RHEL-191705
|
||||
|
||||
* Tue Apr 7 2026 Iker Pedrosa <ipedrosa@redhat.com> - 1.6.1-10
|
||||
- pam_access: support UID and GID in access.conf
|
||||
Resolves: RHEL-119867
|
||||
|
||||
Loading…
Reference in New Issue
Block a user