public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Iker Pedrosa <ipedrosa@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/pam] rawhide: pam_userdb: fix password comparison timing leak
Date: Thu, 02 Jul 2026 10:58:46 GMT	[thread overview]
Message-ID: <178298992674.1.1403165462693301381.rpms-pam-ee0d2834f984@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/pam
            Branch : rawhide
            Commit : ee0d2834f9842deba7f0c043bf07aa98e35a4ffc
            Author : Iker Pedrosa <ipedrosa@redhat.com>
            Date   : 2026-07-02T12:58:15+02:00
            Stats  : +169/-1 in 2 file(s)
            URL    : https://src.fedoraproject.org/rpms/pam/c/ee0d2834f9842deba7f0c043bf07aa98e35a4ffc?branch=rawhide

            Log:
            pam_userdb: fix password comparison timing leak

Resolves: #2496416
Resolves: CVE-2026-54411
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>

---
diff --git a/pam-1.7.2-pam-userdb-fix-password-leak.patch b/pam-1.7.2-pam-userdb-fix-password-leak.patch
new file mode 100644
index 0000000..587920c
--- /dev/null
+++ b/pam-1.7.2-pam-userdb-fix-password-leak.patch
@@ -0,0 +1,160 @@
+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 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.54.0
+

diff --git a/pam.spec b/pam.spec
index d382f10..428cb37 100644
--- a/pam.spec
+++ b/pam.spec
@@ -14,7 +14,7 @@
 Summary: An extensible library which provides authentication for applications
 Name: pam
 Version: 1.7.2
-Release: 2%{?dist}
+Release: 3%{?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+.
@@ -34,6 +34,8 @@ Source17: postlogin.5
 Source18: https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 Patch1:  pam-1.7.0-redhat-modules.patch
 Patch2:  pam-1.5.3-unix-nomsg.patch
+# https://github.com/linux-pam/linux-pam/commit/30708d973b63891bf700299ce3ae0f1086398284
+Patch3:  pam-1.7.2-pam-userdb-fix-password-leak.patch
 
 %{load:%{SOURCE3}}
 
@@ -130,6 +132,7 @@ cp %{SOURCE18} .
 
 %patch -P 1 -p1 -b .redhat-modules
 %patch -P 2 -p1 -b .nomsg
+%patch -P 3 -p1 -b .pam-userdb-fix-password-leak
 
 %build
 %meson \
@@ -363,6 +366,11 @@ done
 %{_pam_libdir}/libpam_misc.so.%{so_ver}*
 
 %changelog
+* Thu Jul  2 2026 Iker Pedrosa <ipedrosa@redhat.com> - 1.7.2-3
+- pam_userdb: fix password comparison timing leak
+  Resolves: #2496416
+  Resolves: CVE-2026-54411
+
 * Fri Jun 12 2026 Yaakov Selkowitz <yselkowi@redhat.com>
 - Rebuilt for openssl 4.0
 

                 reply	other threads:[~2026-07-02 10:58 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178298992674.1.1403165462693301381.rpms-pam-ee0d2834f984@fedoraproject.org \
    --to=ipedrosa@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox