pam_pkcs11: Possible Authentication Bypass in Error Situations (CVE-2025-24531)
#local #PAM #CVETable of Contents
- 1) Introduction
- 2) Discovery of the Issue / Relation to GDM Smart Card Authentication
- 3) The
PAM_IGNORE
Issue in pam_pkcs11 - 4) Affected Distributions and Configurations
- 5) Possible Workaround
- 6) Bugfix
- 7) Lessons Learned
- 8) Timeline
- 9) References
1) Introduction
This report is about a regression in pam_pkcs11 version
0.6.12. In this release the implementation of
pam_sm_authenticate()
has been changed to return
PAM_IGNORE
in many exit paths, which can lead to a complete authentication
bypass in some scenarios. This report is based on upstream Git tag
“pam_pkcs11-0.6.12”. A bugfix is found in release
0.6.13.
Whether this issue can be exploited is a complex question that depends a lot on the system configuration. The following section gives some insight into how we discovered the issue and why its severity can be high in some circumstances. Section 3) looks in detail into the issue found in pam_pkcs11. The rest of the report explores which Linux distributions might be affected by the issue, a possible workaround and the upstream bugfix. Finally we will be taking a look at the lessons that can be learned from this finding.
2) Discovery of the Issue / Relation to GDM Smart Card Authentication
Fellow SUSE engineer Marcus Rückert uses a YubiKey for login at his openSUSE Tumbleweed desktop system. In October 2024 he noticed a change in behaviour in his GDM login setup. By digging a bit deeper he noticed that in some situations login was possible without entering a password or using his YubiKey at all.
While analysing the issue we found that there is a bug (or a feature?) in GDM 3 that causes YubiKeys to be treated as smart cards. This is one ingredient that comes into play here. A number of Linux distributions use a dedicated “gdm-smartcard” PAM stack configuration file for smart card login in GDM. On openSUSE we rely on pam_pkcs11 as the sole (proper) authentication module in this gdm-smartcard PAM stack. It took us a while to understand where exactly this gdm-smartcard PAM stack is used in GDM. The logic to select this PAM stack is found in a wholly different Gnome component, namely gnome-shell. There, some JavaScript is responsible for detecting smart cards via the D-Bus interface of the gnome-settings-daemon, and for changing the authentication mode of GDM.
We reproduced the situation by using smart card emulation in a QEMU Virtual machine, to be able to achieve proper smart card detection in both GDM and pam_pkcs11. As soon as the smart card is properly setup in the system, GDM switches into smart card authentication mode (support for this is enabled by default). A user list is no longer shown in the display manager, instead the username has to be entered manually. After entering the username, the “gdm-smartcard” PAM stack is executed, and with it pam_pkcs11. No password is asked for and login succeeds.
What happens in this test setup, as well as in the real life setup using a YubiKey, is that pam_pkcs11 stops execution after logging “Failed to initialize crypto” and, surprisingly, login succeeds. The reason for this lies in pam_pkcs11, as is described in the next section.
We did not investigate why exactly the “initialize crypto” error occurs, as we don’t believe it is relevant for the security issue. Even when errors occur, the outcome of the PAM stack execution shouldn’t allow authentication without providing credentials.
3) The PAM_IGNORE
Issue in pam_pkcs11
The successful login without proper authentication in pam_pkcs11 stems from a change that found its way into pam_pkcs11 version 0.6.12. The issue has been introduced with commit bac6cf8 (note that there seems to exist an artifact in the upstream Git repository: a seemingly identical commit 88a87d5 in the commit log on “master”).
With this change, many exit paths of the
pam_sm_authenticate()
function now return
PAM_IGNORE
instead of PAM_CRED_INSUFFICIENT
. In particular, the code found
at line 284 means that the default return value on
error conditions is PAM_IGNORE
, if there is no “login token name”:
if (!configuration->card_only || !login_token_name) {
/* Allow to pass to the next module if the auth isn't
restricted to card only. */
pkcs11_pam_fail = PAM_IGNORE;
} else {
pkcs11_pam_fail = PAM_CRED_INSUFFICIENT;
}
The card_only
flag refers to a module
parameter, whose meaning seems to have
changed over time and is no longer fully conforming to what is documented. It
is enabled in the “gdm-smartcard” stack, thus this part of the if
condition
will not trigger. The background of the login_token_name
is that the PAM
module contains special logic for unlocking the screen saver, but only if the
session login was performed using pam_pkcs11. This will always be false
during initial login, and thus this part of the if
condition applies in this
case.
When a PAM module returns PAM_IGNORE
, its outcome should not be used to
determine the result of the PAM stack execution. openSUSE uses the required
control setting for pam_pkcs11 in its “gdm-smartcard” configuration. In
extended PAM syntax required
is expressed like this:
required
[success=ok new_authtok_reqd=ok ignore=ignore default=bad]
When pam_pkcs11 returns PAM_IGNORE
then the “required” control setting no
longer results in what the average administrator will expect, namely that
authentication fails if no successful smart card authentication is possible.
What happens instead depends on the rest of the modules present in the “auth” section on the PAM stack. When no other PAM module at all is on the stack, then authentication fails, because the PAM library expects at least one decisive return value from any module on the stack. When there is another PAM module on the stack that actually authenticates, then that module will set a failed state if no credentials are provided, thereby preventing successful login.
To judge the situation with the “gdm-smartcard” PAM stack, let’s look more closely at its “auth” section:
auth requisite pam_faillock.so preauth
auth required pam_pkcs11.so wait_for_card card_only
auth required pam_shells.so
auth requisite pam_nologin.so
auth optional pam_permit.so
auth required pam_env.so
auth [success=ok default=1] pam_gdm.so
auth optional pam_gnome_keyring.so
There are a lot of other modules configured, alas, none of them is actually
authenticating. These are what we like to call “utility modules” in this
discussion: they provide support functions. Examples are the pam_faillock
module which checks whether excess authentication errors occurred, or
pam_gnome_keyring which attempts to intercept the cleartext password used
for login to transparently unlock the user keyring. Commonly, such modules
return PAM_SUCCESS
in most situations. As a result, when pam_pkcs11 returns
PAM_IGNORE
, the overall outcome of the PAM authentication will become
PAM_SUCCESS
, supplied by non-authenticating modules in the “gdm-smartcard”
PAM stack.
Below the code location in pam_sm_authenticate()
function shown above, only
two code paths return something other than PAM_IGNORE
:
Both return paths will reset pkcs11_pam_fail
to a safe PAM_AUTH_ERR
value.
The following is a list of all the other return paths which will return
PAM_IGNORE
:
- line 305: if a user is logging in from remote, or can control the DISPLAY environment variable (e.g. in sudo context).
- line 316: if the
crypto_init()
call fails. - line 328: if a screen saver context is detected and no login token is
recorded, then an explicit jump to a
PAM_IGNORE
return is performed. - lines 343, 357: if loading or initializing the PKCS#11 module fails.
- line 374: if the configured token is not found and
card_only
is not set. This might be okay in light of the semantics ofcard_only
, but it is still strange. If system administrators want to make pam_pkcs11 authentication optional then they can do so by using the PAM stack configuration already, by using theoptional
control setting. Changing the module result semantics this drastically through a seemingly harmless module option is unusual. - line 416: if no smart card is found even after potentially waiting for it.
If a smart card is found, but one of various PKCS#11 library functions or certificate
checks fail, then further
PAM_IGNORE
returns can happen if any of the following operations fail:open_pkcs11_session()
(line 432)get_slot_login_required()
(line 443)- when reading in a password fails (line 471)
- empty password was read without
nullok
set (line 486) get_certificate_list()
(line 522)pam_set_item(..., PAM_USER, ...)
(line 597)match_user()
(line 613)(no matching certificate found)
(line 634)get_random_value()
(line 663)sign_value()
(line 677)close_pkcs11_session()
(line 776)
As this long list demonstrates, it is likely that a local attacker will be able
to provoke a PAM_IGNORE
return value in pam_pkcs11. For a physical attacker
the simplest way is to insert an arbitrary smart card into an existing reader,
or attach a peripheral smart card device to the system. The pam_pkcs11
module, if configured, will attempt to access the smart card: if the access
fails, then the module returns PAM_IGNORE
, resulting in a possible
authentication bypass.
4) Affected Distributions and Configurations
The issue was introduced in pam_pkcs11 version 0.6.12, released in July 2021. Any PAM stack that relies on pam_pkcs11 as the only authentication factor will be affected by the issue.
On openSUSE Tumbleweed the issue became apparent only due to the mentioned changes in GDM, which cause YubiKeys to be treated as smart cards in some situations. We believe plugging in any kind of mismatching smart card (or YubiKey) on openSUSE Tumbleweed with GDM as a display manager will allow to bypass login.
Similar situations could occur on other Linux distributions if GDM smart card login is enabled and smart cards are autodetected. Even then, an affected “gdm-smartcard” PAM stack still needs to be in place for the issue to trigger. gdm-smartcard PAM stacks relying on pam_pkcs11 are found in the GDM repository for:
We tried reproducing the issue on Arch Linux. There the gdm-smartcard PAM stack is installed along with GDM, but there is no pam_pkcs11 package in the standard repositories. It can be installed from the AUR, however. When doing so and also installing the gdm and ccid packages, then the issue becomes basically exploitable as well. We only tested this with a crafted sudo PAM stack, though, since we did not manage to get gdm into smart card authentication mode on Arch Linux. It seems some ingredient was still missing to trigger that.
On Arch Linux we also noticed that the AUR pam_pkcs11 package does not
place any default “pam_pkcs11.conf” file into /etc
. This also avoids the
security problem, because when the slot_num
setting
is left unconfigured to its built-in default value of -1, then
pam_sm_authenticate()
will return early with PAM_AUTHINFO_UNAVAIL
. On
openSUSE we do ship a default configuration of slot_num = 0
, however.
Current Fedora Linux does not use pam_pkcs11 for smart card authentication anymore (pam_sss is used instead). Older versions of Fedora might still be affected.
5) Possible Workaround
A quick workaround to prevent login bypass is to use the following PAM stack configuration line instead of what is found e.g. in the gdm-smartcard PAM stacks:
auth [success=ok default=bad] pam_pkcs11.so wait_for_card card_only
Instead of using ignore=ignore
as seen in the required
control setting
shown in section 3), the PAM library will consider ignore
(actually any other
outcome than success) a bad result for the authentication stack. This will
cause authentication to fail even if pam_pkcs11 returns PAM_IGNORE
.
6) Bugfix
After extensive discussions about the nature of the problem and potential
compatibility issues, upstream arrived at a rather straightforward bugfix
which is found in commit 2ecba68d40. Basically the
PAM_IGNORE
return values have been changed into PAM_CRED_INSUFFICIENT
again.
This bugfix is part of upstream release 0.6.13, which also fixes another vulnerability in the PAM module, which has been discovered independently.
7) Lessons Learned
We could not find any clear advice in PAM admin or developer documentation
regarding the proper use of PAM_IGNORE
. Therefore we try to give an overview
of the current situation and suggested best practices in this section.
On the use of PAM_IGNORE
As there have been doubts if pam_pkcs11 is to blame for its use of
PAM_IGNORE
, we made a survey of other PAM modules packaged in openSUSE. We
found one PAM module, pam_u2f, that also had problematic uses of PAM_IGNORE
in error situations and we published the issue already in a previous
report. This report already resulted in a discussion
on the oss-security mailing list about possible
structural problems when implementing PAM modules.
Apart from this we found the following uses of PAM_IGNORE
:
Core PAM Modules
- pam_wheel: this is only kind of a filter module, such that non-
root
will be denied, while forroot
it returnsPAM_IGNORE
; the actual authentication decision is made by other modules. - pam_sepermit: returns
PAM_IGNORE
if users are not listed in the configuration file. - pam_lastlog: uses
PAM_IGNORE
if the lastlog file (in a privileged location) cannot be read. - pam_userdb: returns
PAM_IGNORE
if no database is configured. - pam_listfile: returns
PAM_IGNORE
if the user about to login does not match the configured criteria.
Third Party PAM Modules
- pam_google_authenticator: returns
PAM_IGNORE
if there is no state file and thenullok
option is passed the module. - nss-pam-ldapd: returns
PAM_IGNORE
if the user is unknown or no auth info is available, but only if explicitly configured to do so (cfg->ignore_authinfo_unavail
,cfg->ignore_unknown_user
) - pam_krb5:
- returns
PAM_IGNORE
if the user it not known, but only ifoptions->ignore_unknown_principals
is set. - returns
PAM_IGNORE
if aminimum_uid
is configured and the user doesn’t match that.
- returns
- pam_radius: returns
PAM_IGNORE
if the network is unavailable and ignore has been explicitly configured via thelocalifdown
option. - pam_yubico: returns
PAM_IGNORE
if there are no tokens for the user and thenullok
option is passed to the module.
As can be seen from this list, most PAM modules only return PAM_IGNORE
if
there is an explicit opt-in either through a configuration option or a setting
in a privileged configuration file. Most of the time the meaning of the return
value is that the authentication mechanism is not configured at all, or not
configured for the user that is authenticated. Such configurations can only be
used in a safe way if the module in question is an optional authentication
mechanism, and a fallback PAM module for authentication is present on the
stack.
From the issues seen in pam_pkcs11 and pam_u2f we believe it is especially
important for PAM module implementations to take care not to use PAM_IGNORE
in unclear error situations, since local or physically present attackers might
be able to trigger them.
On the use of PAM_SUCCESS
PAM modules that only serve utility functions but do not actually authenticate
could consider not returning PAM_SUCCESS
but PAM_IGNORE
instead. This
would avoid unintended successful authentication in a situation like described
in this report. It seems natural to PAM module authors to return PAM_SUCCESS
if nothing in their module failed, however. A lot of modules work this way and
changing them all would be a big effort.
Conservative PAM Stack Configuration
Sadly PAM can be difficult to understand for non-developers and sometimes even for PAM module authors. Even more so admins and integrators should be careful when writing PAM stacks, especially when less common PAM modules are used as the only authentication requirement. Extended PAM syntax like used in our suggested workaround could be used in such situations for hardening purposes, to make sure no unexpected authentication outcomes can occur.
8) Timeline
2024-11-06 | There was no maintainer, security contact or disclosure process documented in pam_pkcs11 or the OpenSC project. In an attempt to find a suitable upstream contact we approached Ludovic Rousseau, who was a contributor to pam_pkcs11 and a member of the OpenSC organization on GitHub. |
2024-11-06 | Ludovic replied that he is no longer active in the project and pointed to public means of reporting the issue, which we would rather not use at this point. |
2024-11-07 | We approached Paul Wolneykien, another recent pam_pkcs11 contributor, and asked for guidance. |
2024-11-07 | Paul replied that Ludovic would be the proper maintainer, with Frank Morgner as a fallback. He also pointed to the (public) opensc developer mailing list. |
2024-11-08 | Still without a conclusive contact we publicly asked for a security contact on the opensc developer mailing list. |
2024-11-08 | In response to our question, Frank Morgner of the OpenSC project enabled private security reporting in the pam_pkcs11 GitHub repository. |
2024-11-11 | We shared our report using the now available GitHub private issue reporting, offering coordinated disclosure and an embargo period of up to 90 days. |
2024-11-12 | A couple of upstream developers joined the private GitHub issue and various discussions started. |
2024-11-13 | Due to uncertainty on the proper use of PAM_IGNORE and what the proper fix in pam_pkcs11 could be, we suggested an early publication of the issue to allow a public discussion of the issue. |
2024-11-17 | Different opinions were expressed with regards to publishing the issue, so no agreement could be found at this point. No planned release date could be established. |
2024-11-20 | While looking into other PAM modules and their use of PAM_IGNORE , we found that the pam-u2f module suffered from a similar problem. We reported the issue to Yubico upstream, see our earlier report. |
2024-11-26 | linux-pam developer Dmitry V. Levin got pulled into the discussion to judge whether the use of PAM_IGNORE in pam_pkcs11 is problematic or not. He stated that the switch to PAM_IGNORE is problematic when end users are not aware of the behavioural change. |
2024-12-05 | With no clear path forward we suggested to share the report with the linux-distros mailing list soon to achieve some progress. No agreement regarding publication could be found, though. |
2025-01-07 | Upstream developers discussed a patch to fix the issue, but communication died down since December 12. We asked once more about a path forward to publish the report and bugfix. |
2025-01-13 | Upstream asked us to request a CVE for the issue. We requested it from Mitre, but the request got stuck for nearly two weeks. |
2025-01-14 | The spin-off pam-u2f issue was published. It was unfortunate that this got published first, since we could not publicly discus the bigger picture involving pam_pkcs11 at this time. |
2025-01-20 | An upstream developer stated that a private branch containing a bugfix is available, and asked whether this should be published. We asked not to publish anything without an agreement on the date and procedure. |
2025-01-23 | The issue with the Mitre CVE request got resolved and CVE-2025-24531 was assigned for it. We shared this CVE in the private upstream issue. |
2025-01-23 | We asked once more for a coordinated release date and suggested to share the issue with the linux-distros mailing list on Jan 30 and to perform general publication on Feb 6. |
2025-01-24 | General agreement was achieved for the suggested publication dates. |
2025-01-30 | We shared the report and bugfix with the linux-distros mailing list, communicating an embargo period until publication on Feb 6. |
2025-02-06 | Upstream published bugfix release 0.6.13 as planned. |