See how linux-pam compares to other vendors in security performance
On Tue, 17 Jun 2025 at 20:00:59 +0000, Qualys Security Advisory wrote: On Debian 12 and Ubuntu 24.04, when an unprivileged user logs in via sshd, PAM's pamenv module (from Linux-PAM 1.5.x) also reads this user's ~/.pamenvironment file, because pamenv's "userreadenv" is explicitly set to 1 in /etc/pam.d/sshd (it is 0 by default, since Linux-PAM 1.4.0).
The conflation of the "PAM environment" (ambiguously trusted) with smcv
Hello,
This is a report about a potential privilege escalation in the pamnamespace.so PAM module. This module is one of the core PAM modules from the linux-pam project [1].
The vulnerability has been fixed in linux-pam v1.7.1 [2] and is tracked as CVE-2025-6020 and GHSA-f9p8-gjr4-j9gx [3].
In addition to upgrading to the latest version, users of pamnamespace may want to update their namespace.init script if they do not use the one provided by their distribution. See section "Patches and mitigation" for more details.
======================================================================== The vulnerability ========================================================================
When setting up the polyinstantiation of directories, the module pamnamespace may access files and directories owned by unprivileged users. This is for instance the case when configuring polyinstantiated home directories for the users in the following way, inspired by the example in man namespace.conf(5):
$HOME $HOME/$USER.inst/ user root
When accessing a directory owned by a user or writable by a user, pamnamespace does it with great care. It resorts to two techniques:
1. Using file descriptors and relative paths instead of absolute paths.
2. Bind mounting on themselves all the directories that can be modified by an unprivileged user and are part of the given path. The goal is to forbid changes on these directories while operating on the path. This is what functions protectdir() and protectmount() do.
The first technique is used in some parts of the code, but some operations are also made on user-controlled paths using absolute paths. Thus, these operations are safe only if the second technique is reliable.
At the time the pamnamespace module was written, the second technique was effective against a malicious user operating in any mount namespace. However, since Linux 3.18, it's only effective if the malicious user is operating in the same mount namespace as the one the mount was made in. Indeed, as man mountnamespaces(7) states:
A file or directory that is a mount point in one namespace that is not a mount point in another namespace, may be renamed, unlinked, or removed (rmdir(2)) in the mount namespace in which it is not a mount point [...]. Consequently, the mount point is removed in the mount namespace where it was a mount point.
Previously (before Linux 3.18), attempting to unlink, rename, or remove a file or directory that was a mount point in another mount namespace would result in the error EBUSY.
Therefore, if a user can launch a process outside of the mount namespace created by pamnamespace (i.e. outside of their PAM session), they are able to alter the paths on which pamnamespace is operating as root. Doing so, they can launch symlink attacks, race the other security checks in the code, and elevate their privileges to root.
It may be complicated to launch a process outside of a mount namespace as an unprivileged user, but the context can help an attacker to do so. For instance:
If two users are colluding or are compromised on the same system, the first one can mess with the paths used to polyinstantiate directories for the second user. Indeed, they will not be restricted by pamnamespace, as they will be in their own PAM session and therefore in their own mount namespace with their own mounts.
A PAM service is misconfigured and do not call pamnamespace when creating a new PAM session. This can be found in the wild, especially because some packages come with PAM service files that do not include the common PAM service files of their distribution (common-session, system-auth, ...). This easily leads to misconfigurations, and in this case, can be turned into a local privilege escalation to root.
======================================================================== Patches and mitigation ========================================================================
In order to fix the issue, the following changes were made:
All the functions that may operate on a user-controlled path were converted to operate on file descriptors instead of absolute path.
The existing bind-mount protection provided by protectmount() is kept as a defense in depth measure.
Protecting the shell script namespace.init against symlink attacks and race conditions was deemed too complicated, especially because its content can be modified by vendors and users. Instead, it now receives two additional flags as arguments to indicate if the polydir path and the instance path are safe to operate on as root.
If they provide their own script in place of the default one, vendors and users are expected to update their script to check for these flags and not operate on user-controlled paths.
======================================================================== Timeline ========================================================================
2025-01-29: The vulnerability is reported to the maintainers. A PoC demonstrating the LPE to root and a draft of a patch are provided as well.
2025-02-17: An upstream maintainer acknowledge the issue and provides the first comments on the patch.
2025-06-05: After successive iterations and reviews, the patch is ready.
2025-06-11: Issue shared with the linux-distro mailing list. CVE-2025-6020 is assigned.
2025-06-17: Upstream linux-pam releases v1.7.1 containing the fix.
======================================================================== References ========================================================================
[1]: https://github.com/linux-pam/linux-pam [2]: https://github.com/linux-pam/linux-pam/releases/tag/v1.7.1 [3]: https://github.com/linux-pam/linux-pam/security/advisories/GHSA-f9p8-gjr4-j9gx
--
Best regards,
Olivier BAL-PETRE ANSSI - French Cybersecurity Agency https://cyber.gouv.fr/
The module pamnamespace in linux-pam <= 1.7.0 may access user-controlled paths without proper protections, which allows a local user to elevate their privileges to root via multiple symlink attacks and race conditions.
A flaw was found in linux-pam. The module pamnamespace may use access user-controlled paths without proper protection, allowing local users to elevate their privileges to root via multiple symlink attacks and race conditions.
On 2/6/25 8:55 AM, Matthias Gerstner wrote: 4) Affected Distributions and Configurations ============================================
The issue was introduced in pampkcs11 version 0.6.12, released in July 2021. Any PAM stack that relies on pampkcs11 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 [7], 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 pampkcs11 are found in the GDM repository for:
- Arch Linux [22] - Exherbo Linux [23] - Linux from Scratch [24] Hello Matthias! Douglas Reno Linux From Scratch
Hello list,
this report was previously sent to the linux-distros mailing list with an embargo period of 7 days, which ended today. The following report contains minor changes compared with the (draft) version shared on linux-distros.
We also offer a rendered version of this report on our blog [1].
1) Introduction ===============
This report is about a regression in pampkcs11 [2] version 0.6.12 [3]. In this release the implementation of pamsmauthenticate() [4] has been changed to return PAMIGNORE in many exit paths, which can lead to a complete authentication bypass in some scenarios. This report is based on upstream Git tag "pampkcs11-0.6.12". A bugfix is found in release 0.6.13 [5].
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 pampkcs11. 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 [6] 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 [7] (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 pampkcs11 as the sole (proper) authentication module in this gdm-smartcard PAM stack [8]. 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 [9]. 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 pampkcs11. 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 pampkcs11. 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 pampkcs11 stops execution after logging "Failed to initialize crypto" and, surprisingly, login succeeds. The reason for this lies in pampkcs11, 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 PAMIGNORE Issue in pampkcs11 ========================================
The successful login without proper authentication in pampkcs11 stems from a change that found its way into pampkcs11 version 0.6.12. The issue has been introduced with commit bac6cf8 [10] (note that there seems to exist an artifact in the upstream Git repository: a seemingly identical commit 88a87d5 [11] in the commit log on "master").
With this change, many exit paths of the pamsmauthenticate() [4] function now return PAMIGNORE instead of PAMCREDINSUFFICIENT. In particular, the code found at line 284 [12] means that the default return value on error conditions is PAMIGNORE, if there is no "login token name":
if (!configuration->cardonly || !logintokenname) { / Allow to pass to the next module if the auth isn't restricted to card only. / pkcs11pamfail = PAMIGNORE; } else { pkcs11pamfail = PAMCREDINSUFFICIENT; }
The cardonly flag refers to a module parameter [13], 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 logintokenname is that the PAM module contains special logic for unlocking the screen saver, but only if the session login was performed using pampkcs11. 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 PAMIGNORE, its outcome should not be used to determine the result of the PAM stack execution. openSUSE uses the required control setting for pampkcs11 in its "gdm-smartcard" configuration. In extended PAM syntax required is expressed like this:
required [success=ok newauthtokreqd=ok ignore=ignore default=bad]
When pampkcs11 returns PAMIGNORE 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 pamfaillock.so preauth auth required pampkcs11.so waitforcard cardonly auth required pamshells.so auth requisite pamnologin.so auth optional pampermit.so auth required pamenv.so auth [success=ok default=1] pamgdm.so auth optional pamgnomekeyring.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 pamfaillock module which checks whether excess authentication errors occurred, or pamgnomekeyring which attempts to intercept the cleartext password used for login to transparently unlock the user keyring. Commonly, such modules return PAMSUCCESS in most situations. As a result, when pampkcs11 returns PAMIGNORE, the overall outcome of the PAM authentication will become PAMSUCCESS, supplied by non-authenticating modules in the "gdm-smartcard" PAM stack.
Below the code location in pamsmauthenticate() function shown above, only two code paths return something other than PAMIGNORE:
- in line 510 if pkcs11login() fails [14] - in line 695 f verifysignature() fails [15]
Both return paths will reset pkcs11pamfail to a safe PAMAUTHERR value. The following is a list of all the other return paths which will return PAMIGNORE:
- line 305: if a user is logging in from remote, or can control the DISPLAY environment variable (e.g. in sudo context). [16] - line 316: if the cryptoinit() call fails. [17] - line 328: if a screen saver context is detected and no login token is recorded, then an explicit jump to a PAMIGNORE return is performed. [18] - lines 343, 357: if loading or initializing the PKCS#11 module fails. [19] - line 374: if the configured token is not found and cardonly is not set [20]. This might be okay in light of the semantics of cardonly, but it is still strange. If system administrators want to make pampkcs11 authentication optional then they can do so by using the PAM stack configuration already, by using the optional 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 [21]. If a smart card is found, but one of various PKCS#11 library functions or certificate checks fail, then further PAMIGNORE returns can happen if any of the following operations fail: - openpkcs11session() (line 432) - getslotloginrequired() (line 443) - when reading in a password fails (line 471) - empty password was read without nullok set (line 486) - getcertificatelist() (line 522) - pamsetitem(..., PAMUSER, ...) (line 597) - matchuser() (line 613) - (no matching certificate found) (line 634) - getrandomvalue() (line 663) - signvalue() (line 677) - closepkcs11session() (line 776)
As this long list demonstrates, it is likely that a local attacker will be able to provoke a PAMIGNORE return value in pampkcs11. 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 pampkcs11 module, if configured, will attempt to access the smart card: if the access fails, then the module returns PAMIGNORE, resulting in a possible authentication bypass.
4) Affected Distributions and Configurations ============================================
The issue was introduced in pampkcs11 version 0.6.12, released in July 2021. Any PAM stack that relies on pampkcs11 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 [7], 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 pampkcs11 are found in the GDM repository for:
- Arch Linux [22] - Exherbo Linux [23] - Linux from Scratch [24]
We tried reproducing the issue on Arch Linux. There the gdm-smartcard PAM stack is installed along with GDM, but there is no pampkcs11 package in the standard repositories. It can be installed from the AUR (https://aur.archlinux.org/), 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 pampkcs11 package does not place any default "pampkcs11.conf" file into /etc. This also avoids the security problem, because when the slotnum [25] setting is left unconfigured to its built-in default value of -1, then pamsmauthenticate() will return early with PAMAUTHINFOUNAVAIL. On openSUSE we do ship a default configuration of slotnum = 0, however.
Current Fedora Linux does not use pampkcs11 for smart card authentication anymore (pamsss 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] pampkcs11.so waitforcard cardonly
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 pampkcs11 returns PAMIGNORE.
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 [26]. Basically the PAMIGNORE return values have been changed into PAMCREDINSUFFICIENT again.
This bugfix is part of upstream release 0.6.13 [5], 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 PAMIGNORE. Therefore we try to give an overview of the current situation and suggested best practices in this section.
On the use of PAMIGNORE --------------------------
As there have been doubts if pampkcs11 is to blame for its use of PAMIGNORE, we made a survey of other PAM modules packaged in openSUSE. We found one PAM module, pamu2f, that also had problematic uses of PAMIGNORE in error situations and we published the issue already in a previous report [27]. This report already resulted in a discussion on this mailing list [28] about possible structural problems when implementing PAM modules.
Apart from this we found the following uses of PAMIGNORE:
Core PAM Modules
- pamwheel: this is only kind of a filter module, such that non-root will be denied, while for root it returns PAMIGNORE; the actual authentication decision is made by other modules. - pamsepermit: returns PAMIGNORE if users are not listed in the configuration file. - pamlastlog: uses PAMIGNORE if the lastlog file (in a privileged location) cannot be read. - pamuserdb: returns PAMIGNORE if no database is configured. - pamlistfile: returns PAMIGNORE if the user about to login does not match the configured criteria.
Third Party PAM Modules
- pamgoogleauthenticator: returns PAMIGNORE if there is no state file and the nullok option is passed the module. - nss-pam-ldapd: returns PAMIGNORE if the user is unknown or no auth info is available, but only if explicitly configured to do so (cfg->ignoreauthinfounavail, cfg->ignoreunknownuser) - pamkrb5: - returns PAMIGNORE if the user it not known, but only if options->ignoreunknownprincipals is set. - returns PAMIGNORE if a minimumuid is configured and the user doesn't match that. - pamradius: returns PAMIGNORE if the network is unavailable and ignore has been explicitly configured via the localifdown option. - pamyubico: returns PAMIGNORE if there are no tokens for the user and the nullok option is passed to the module.
As can be seen from this list, most PAM modules only return PAMIGNORE 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 pampkcs11 and pamu2f we believe it is especially important for PAM module implementations to take care not to use PAMIGNORE in unclear error situations, since local or physically present attackers might be able to trigger them.
On the use of PAMSUCCESS ---------------------------
PAM modules that only serve utility functions but do not actually authenticate could consider not returning PAMSUCCESS but PAMIGNORE instead. This would avoid unintended successful authentication in a situation like described in this report. It seems natural to PAM module authors to return PAMSUCCESS 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 pampkcs11 or the OpenSC project. In an attempt to find a suitable upstream contact we approached Ludovic Rousseau, who was a contributor to pampkcs11 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 pampkcs11 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 [31] 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 pampkcs11 GitHub repository [2]. 2024-11-11: We shared our report [29] 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 PAMIGNORE and what the proper fix in pampkcs11 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 PAMIGNORE, we found that the pam-u2f module suffered from a similar problem. We reported the issue to Yubico upstream, see our earlier report [27]. 2024-11-26: linux-pam [32] developer Dmitry V. Levin got pulled into the discussion to judge whether the use of PAMIGNORE in pampkcs11 is problematic or not. He stated that the switch to PAMIGNORE 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 [30] for nearly two weeks. 2025-01-14: The spin-off pam-u2f [27] issue was published. It was unfortunate that this got published first, since we could not publicly discus the bigger picture involving pampkcs11 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 [33], communicating an embargo period until publication on Feb 6. 2025-02-06: Upstream published bugfix release 0.6.13 [5] as planned.
9) References =============
[1]: https://security.opensuse.org/2025/02/06/pam-pkcs11-pam-ignore-auth-bypass.html [2]: https://github.com/OpenSC/pampkcs11 [3]: https://github.com/OpenSC/pampkcs11/releases/tag/pampkcs11-0.6.12 [4]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L207 [5]: https://github.com/OpenSC/pampkcs11/releases/tag/pampkcs11-0.6.13 [6]: https://bugzilla.suse.com/showbug.cgi?id=1231843 [7]: https://gitlab.gnome.org/GNOME/gdm/-/issues/877 [8]: https://build.opensuse.org/projects/GNOME:Factory/packages/gdm/files/gdm-smartcard.pamd?expand=1&rev=50feb25477832ba767b0c6702d80bc04 [9]: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/17ce108a35d35447c82899bfe5011b4860862a53/js/gdm/util.js#L28 [10]: https://github.com/OpenSC/pampkcs11/commit/bac6cf8e0b242e508e8b715e7f78d52f1227840a [11]: https://github.com/OpenSC/pampkcs11/commit/88a87d54ff0a9f1c425906bb1fe260e40bd7751c [12]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L284 [13]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/README#L105 [14]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L510 [15]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L695 [16]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L305 [17]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L316 [18]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L328 [19]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L343 [20]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L374 [21]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L416 [22]: https://gitlab.gnome.org/GNOME/gdm/-/blob/be3a3de7130b212b3ba84c5644e0e057e41556d8/data/pam-arch/gdm-smartcard.pam [23]: https://gitlab.gnome.org/GNOME/gdm/-/blob/be3a3de7130b212b3ba84c5644e0e057e41556d8/data/pam-exherbo/gdm-smartcard.pam [24]: https://gitlab.gnome.org/GNOME/gdm/-/blob/be3a3de7130b212b3ba84c5644e0e057e41556d8/data/pam-exherbo/gdm-smartcard.pam [25]: https://github.com/OpenSC/pampkcs11/blob/pampkcs11-0.6.12/src/pampkcs11/pampkcs11.c#L249 [26]: https://github.com/OpenSC/pampkcs11/commit/2ecba68d404c3112546a9e802e3776b9f6c50a6a [27]: https://security.opensuse.org/2025/01/14/pam-u2f-ignore-returns.html [28]: https://www.openwall.com/lists/oss-security/2025/01/15/1 [29]: https://github.com/OpenSC/pampkcs11/security/advisories/GHSA-7mf6-rg36-qgch [30]: https://www.openwall.com/lists/oss-security/2025/01/22/2 [31]: https://sourceforge.net/p/opensc/mailman/message/58838740/ [32]: https://github.com/linux-pam/linux-pam [33]: https://oss-security.openwall.org/wiki/mailing-lists/distros
Best Regards
Matthias
-- Matthias Gerstner <matthias.gerstner () suse de> Security Engineer https://www.suse.com/security GPG Key ID: 0x14C405C971923553 SUSE Software Solutions Germany GmbH HRB 36809, AG Nürnberg Geschäftsführer: Ivo Totev, Andrew McDonald, Werner Knoblich
Matthias Gerstner <mgerstner () suse de> writes: I could not find anything conclusive about this topic in the PAM documentation and development guides. Actually not even about the exact behavior and purpose of PAMIGNORE. In one of my old modules (now orphaned), I see I have this piece of terrifying code:
int pamsmauthenticate(pamhandlet pamh UNUSED, int flags UNUSED, int argc UNUSED, const char argv[] UNUSED) { / We want to return PAMIGNORE here, but Linux PAM 0.99.7.1 (at least) has a bug that causes PAMIGNORE to result in authentication failure when the module is marked [default=done]. So we return PAMSUCCESS, which is dangerous but works in that case. / return PAMSUCCESS; }
This has been a long-standing problem, and it would be great to get it sorted out somehow and clearly documented and to try to signal to all PAM modules that they can drop bug workarounds like this.
The situation with expected return statuses and behavior of pamsetcred is if anything even worse than pamauthenticate. The above problem was in a module that did not want to implement authentication at all, but since it had to provide a pamsetcred implementation, was required to also implement pamauthenticate because pamsetcred otherwise wasn't called. In this case, this was specifically called out in the documentation:
Note that this is not an authentication module and will always return PAMSUCCESS to any authentication attempt, so never make this module sufficient in your authentication stack. It's only listed as an auth module because it provides a pamsetcred implementation and some programs need to call pamsetcred rather than pamopensession (screen savers, for instance, to refresh credentials).
But of course who knows if anyone reads the documentation.
My experience as a PAM module maintainer is that I kept making educated guesses then iterating if I got a bug report, which for a security-sensitive piece of software is not a comfortable place to be. It's a recipe for erring on the side of failing open, which is the opposite of what should be happening, but one tends to only get bug reports when logins fail.
-- Russ Allbery (eagle () eyrie org) <https://www.eyrie.org/~eagle/>
Matthias Gerstner wrote in <Z4jejSMgNUpzFI6T () kasco suse de>: |On Wed, Jan 15, 2025 at 11:58:00PM -0600, Jacob Bachmeyer wrote: |> On 1/15/25 06:03, Matthias Gerstner wrote: |>> There exist utility modules that don't ... |> This looks to me like a logic error in PAM. Why are utility modules ... |I suppose libpam has no way of differentiating the "importance" or |purpose of the modules it runs. It could be argued that such utility
PAM is also massively underdocumented regarding things like id handling, environmental status in which modules run, that sessions can be escaped via simple daemonization (not that it matters to pam at all that mechanisms exist to overcome this, ie, on whatever result / status code / xy side), and similar things.
For example that PAMUSER can return NULL or an empty string in the PAMSUCCESS status code case is more than just astonishing to the occasional programmer who would expect that "sane behaviour" also depends on some context, here not making sense on POSIX?
Also system(3) should be a pretty much safe system call if a session opener program uses it, in that environmental attack surface could only come from system side aka first level administrator configuration, unless i am very much mistaken. Ie RFC 86 writes
Authentication software deserves special attention because authentication forms a very critical component of any secure computer system.
and for session-in-session, for example su(1), we read
The current environment is passed to the new shell. The value of $PATH is reset to /bin:/usr/bin for normal users, or /sbin:/bin:/usr/sbin:/usr/bin for the superuser. This may be changed with the ENVPATH and ENVSUPATH definitions in /etc/login.defs.
There is no notion of signal handling, and i blindly assume that PAM takes care for closing sessions with that "set right". (not ducking.)
Unfortunately my pamxdg (not what became the same in FreeBSD) that used it never was brought up in public. I finally unrolled the system(3) with fork(2)/execve(2), but there is possibility left since i still do not care for signals therein.
... |the often already pretty complex PAM stacks we see on Linux |distributions. ...
Really, in my opinion someone with money (some summer of code maybe) should iterate PAM, so that certain conditions are in a defined state for running module code, and that mode should be addressible so that mode<>module is locked (ie module code only runs if right mode). Maybe via some dlopen(3) availability check, and when run it returns a flag mask of states is preassumes, or something.
--End of <Z4jejSMgNUpzFI6T () kasco suse de>
Just my one cent.
--steffen | |Der Kragenbaer, The moon bear, |der holt sich munter he cheerfully and one by one |einen nach dem anderen runter wa.ks himself off |(By Robert Gernhardt) | |In Fall and Winter, feel "The Dropbear Bard"s pint(er). | |The banded bear |without a care, |Banged on himself for e'er and e'er | |Farewell, dear collar bear
Hi Jacob,
On Wed, Jan 15, 2025 at 11:58:00PM -0600, Jacob Bachmeyer wrote: On 1/15/25 06:03, Matthias Gerstner wrote: There exist utility modules that don't actually authenticate but perform helper functions or enforce policy. An example is the pamfaillock [8] module, which can be added to the auth management group to record failed authentication attempts and lock the account for a certain time if too many failed attempts occur. This module will return PAMSUCCESS when running in "preauth" mode and if the maximum number of failed attempts has not been reached yet. In such a case PAMSUCCESS would become the overall authentication result when pam-u2f returns PAMIGNORE. This looks to me like a logic error in PAM. Why are utility modules that do not actually perform authentication returning PAMSUCCESS (indicating successful authentication(!)) instead of PAMIGNORE or some other "neutral" code?
Is this a widespread misconfiguration? I suppose libpam has no way of differentiating the "importance" or purpose of the modules it runs. It could be argued that such utility modules that don't actually authenticate should indeed not return PAMSUCCESS. From my experience PAM module implementations generally return PAMSUCCESS, though, if they deem their task successfully completed. This makes sense from a single module's point of view, naturally.
I could not find anything conclusive about this topic in the PAM documentation and development guides. Actually not even about the exact behavior and purpose of PAMIGNORE.
For hardening purposes it could be a good idea to return something like PAMIGNORE in utility modules that implement pamsmauthenticate(). I'm not sure if I'm overlooking something here that might spoil the idea, however. This is a discussion that could be continued on a PAM development mailing list (but I couldn't find one after a quick search). Is there a keyword that causes PAM to treat failure as failure but ignore PAMSUCCESS that should be used with those utility modules? It looks like there is no keyword that feels natural for this purpose. Technically it could be done using the extended PAM syntax in a way like this:
auth [success=ignore ignore=ignore default=bad] pammyutility.so [...]
So it could be handled on configuration level, but it would complicate the often already pretty complex PAM stacks we see on Linux distributions.
Cheers
Matthias
A vulnerability was found in PAM. The secret information is stored in memory, where the attacker can trigger the victim program to execute by sending characters to its standard input (stdin). As this occurs, the attacker can train the branch predictor to execute an ROP chain speculatively. This flaw could result in leaked passwords, such as those found in /etc/shadow while performing authentications.
A vulnerability was found in PAM. The secret is in memory, while the attacker can trigger the victim program to execute by sending characters to its stdin. In-between, they then train the branch predictor to speculatively execute a ROP chain.
Moreover, when stdin is a pipe or file, the FILE IO buffer malloc'd will receive the just-freed IO buffer that was used to read /etc/shadow, so it is also possible to have the secret conveniently available in the uninitialized memory of the stdin's FILE buf.=20
This makes several registers reference the /etc/shadow contents during the read-loop of the fgets call that the polkit agent uses in the pam conversation.=20
The attack is difficult to pull of: - Attacker needs to find a gadget chain in the mapped-in executable memory of the victim - Attacker needs to trigger TLB entries to be prefetched to win the race and fit the transient operations in the misspeculation window. - Attacker needs to tweak the attack to break ASLR. This they also can do using Spectre as well.
On Tue, Oct 08, 2024 at 08:10:17AM +0200, Simon Josefsson wrote: I noticed that that there are Linux-PAM helpers to drop privileges:
https://github.com/linux-pam/linux-pam/blob/master/libpam/pammodutilpriv.c#L52 This currently switches fsuid/fsgid (so for the current thread only), but uses initgroups() and setgroups() libc functions (so affects all threads). Code last modified 4 years ago, which pre-dates our recognition of the supplementary groups vs. threads problem and its fix in tcb. Maybe Linux-PAM should implement a similar change now. I have found another implementation of this in yubico-pam:
https://github.com/Yubico/yubico-pam/blob/master/dropprivs.c This currently switches euid/egid/groups, so doesn't try to be MT-safe.
(On Linux, it is also possible to switch euid/egid via direct syscalls for the current thread only, but there's no point because switching of fsuid/fsgid is more appropriate for this purpose anyway.)
A couple of nitpicks on your upstream oath-toolkit code:
1. You restore egid/euid in this same order as you set them. This unnecessarily introduces an extra temporary state. I suggest that you restore them in reverse order, so set egid/euid ... restore euid/egid. (The SUSE patch doesn't have this "problem".)
2. After uses of asprintf() you first check the pointer for NULL and only then check the return value. Strictly speaking, this is undefined behavior because the pointer may be uninitialized if the return value indicates an error. Of course, this shouldn't matter in practice unless a compiler is aware and deliberately exploits the UB (e.g. optimize everything out because this is UB anyway) or the environment (hardware or a software "sanitizer") is such that uninitialized reads are trapped, but to make it correct I suggest that you simply drop the NULL checks. Btw, do you have any thoughts on WHICH user to drop privileges to? The SUSE patch drops privs to the credential file owner. My patch drops privs to the PAM user that is being authenticated. I think there are reasonable arguments for both choices, and for all reasonable configurations that I'm aware of, I don't think the choice matters. Thank you for bringing this up.
I think that for reasonable configurations in absence of attacks this difference is unimportant, however there are different risks involved.
In particular, I worry that the SUSE approach could be susceptible to hard link attacks (when the fs.protectedhardlinks sysctl is not set). Would this allow to overwrite someone else's file (the original issue) if the user can hard link that file? I currently don't see why not, so it's probably a vulnerability.
Would it allow to have the log in process temporarily switch to another user (of the attacker's choosing)? This sounds relatively minor.
In general, switching to a user not only drops privileges for file access, but also potentially exposes the process as that user's. fsuid/fsgid switching is the safest in this respect (these were meant just for file access purposes), but with other IDs (depending on which) there may be extra exposure of the partially privileged log in process to the user via /proc, kill(), setpriority(), etc. ... but thankfully and hopefully not also via ptrace() on modern systems anymore.
Alexander
linux-pam (aka Linux PAM) before 1.6.0 allows attackers to cause a denial of service (blocked login process) via mkfifo because the openat call (for protectdir) lacks ODIRECTORY.
The Linux-PAM package before 1.5.2-6.1 for openSUSE Tumbleweed allows authentication bypass for SSH logins. The pamaccess.so module doesn't correctly restrict login if a user tries to connect from an IP address that is not resolvable via DNS. In such conditions, a user with denied access to a machine can still get access. NOTE: the relevance of this issue is largely limited to openSUSE Tumbleweed and openSUSE Factory; it does not affect Linux-PAM upstream.
A flaw was found in Linux-Pam in the way it handle empty passwords for non-existing users. When the user doesn't exist PAM try to authenticate with root and in the case of an empty password it successfully authenticate.
Reference: https://github.com/linux-pam/linux-pam/issues/284
Upstream patch: https://github.com/linux-pam/linux-pam/pull/300
During a review of newly added PAM modules in the linux-pam project [1] I found a vulnerability [2] in the pamsetquota.so module.
Vulnerability Description =========================
The pamsetquota module iterates over all mounted file systems using setmntent() and getmntent(). It tries to find the longest match of a file system mounted on /home/$USER or above (except when the explicit fs=/some/path parameter is passed to the pam module).
The home directory /home/$USER is owned by the unprivileged user, however. There exist tools like fusermount from libfuse which is by default installed setuid-root for everybody. fusermount allows unprivileged users to mount a FUSE file system using an arbitrary source device name.
Thus given the following precondition:
1) there is only the root file system (/) or a file system is mounted on /home, but not on /home/$USER.
a non-privileged attacker can achieve the following:
2) the attacker mounts a fake FUSE file system over its own home directory:
user $ export FUSECOMMFD=0 user $ fusermount $HOME -ononempty,fsname=/dev/sda1
This will result in a mount entry in /proc/mounts looking like this:
/dev/sda1 on /home/user type fuse (rw,nosuid,nodev,relatime,userid=1000,groupid=100)
3) when the attacker now logs in with pamsetquota configured then pamsetquota will identify /dev/sda1 as the file system to apply the user's quota on.
As a result an unprivileged user has full control over onto which block device the quota is applied.
Consequences Regarding fusermount ===================================
It seems that developers find it suprising that regular user accounts can specify arbitrary source device names in mount entries. It would be desirable to apply restrictions on the source device string in the fusermount setuid-root tool. It will probably be difficult to implement this in a backward-compatible and safe way, however.
Bugfix ======
This issue is fixed via upstream commit 27ded8954a1235bb65ffc9c730ae5a50b1dfed61 [3].
Vulnerability Reporting =======================
This finding was reported privately to upstream. Since the pamsetquota.so PAM module was never part of an official release no embargo was setup. For this reason I also did not request a CVE for the issue.
[1]: https://github.com/linux-pam/linux-pam.git [2]: https://bugzilla.suse.com/showbug.cgi?id=1171721 [3]: https://github.com/linux-pam/linux-pam/commit/27ded8954a1235bb65ffc9c730ae5a50b1dfed61
Cheers
Matthias
-- Matthias Gerstner <matthias.gerstner () suse de> Dipl.-Wirtsch.-Inf. (FH), Security Engineer https://www.suse.com/security Phone: +49 911 740 53 290 GPG Key ID: 0x14C405C971923553
SUSE Software Solutions Germany GmbH HRB 36809, AG Nürnberg Geschäftsführer: Felix Imendörffer
From the original report: "If SELinux is enabled, the unixrunhelperbinary function in Linux-PAM 1.1.8 and earlier hangs indefinitely when verifying a password of 65536 characters, which allows attackers to conduct username enumeration and denial of service attacks.
When supplying a password of 65536 characters or more, the process will block on the write(2) call at modules/pamunix/support.c:614 because it tries to write strlen(passwd)+1 bytes to a blocking pipe and a pipe has a limited capacity of 65536 bytes on Linux."
Acknowledgements:
Red Hat would like to thank Sebastien Macke of Trustwave SpiderLabs for reporting this issue.
From the original report: "If SELinux is enabled, the unixrunhelperbinary function in Linux-PAM 1.1.8 and earlier hangs indefinitely when verifying a password of 65536 characters, which allows attackers to conduct username enumeration and denial of service attacks.
When supplying a password of 65536 characters or more, the process will block on the write(2) call at modules/pamunix/support.c:614 because it tries to write strlen(passwd)+1 bytes to a blocking pipe and a pipe has a limited capacity of 65536 bytes on Linux."
Acknowledgements:
Red Hat would like to thank Sebastien Macke of Trustwave SpiderLabs for reporting this issue.
Multiple directory traversal vulnerabilities in pamtimestamp.c in the pamtimestamp module for Linux-PAM (aka pam) 1.1.8 allow local users to create arbitrary files or possibly bypass authentication via a .. (dot dot) in the (1) PAMRUSER value to the getruser function or (2) PAMTTY value to the checktty function, which is used by the formattimestampname function.
It was found that in pamuserdb module for PAM, password hashes weren't compared case-sensitively, which could lead to acceptance of hashes for completely different passwords, which shouldn't be accepted.
After hashing the user's password with crypt(), pamuserdb compares the result to the stored hash case-insensitively with strncasecmp(), which should be avoided, as it could result in an increased possibility of a successful brute-force attack.
References: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=731368
Stack-based buffer overflow in the assembleline function in modules/pamenv/pamenv.c in Linux-PAM (aka pam) before 1.1.5 allows local users to cause a denial of service (crash) and possibly execute arbitrary code via a long string of white spaces at the beginning of the ~/.pamenvironment file.
The expandarg function in the pamenv module (modules/pamenv/pamenv.c) in Linux-PAM (aka pam) before 1.1.5 does not properly handle when environment variable expansion can overflow, which allows local users to cause a denial of service (CPU consumption).
The pamsmclosesession function in pamxauth.c in the pamxauth module in Linux-PAM (aka pam) 1.1.2 and earlier does not properly handle a failure to determine a certain target uid, which might allow local users to delete unintended files by executing a program that relies on the pamxauth PAM check.
The checkacl function in pamxauth.c in the pamxauth module in Linux-PAM (aka pam) 1.1.2 and earlier does not verify that a certain ACL file is a regular file, which might allow local users to cause a denial of service (resource consumption) via a special file.
The pamenv module in Linux-PAM (aka pam) 1.1.2 and earlier reads the .pamenvironment file in a user's home directory, which might allow local users to run programs with an unintended environment by executing a program that relies on the pamenv PAM check.
The privilege-dropping implementation in the (1) pamenv and (2) pammail modules in Linux-PAM (aka pam) 1.1.2 does not check the return value of the setfsuid system call, which might allow local users to obtain sensitive information by leveraging an unintended uid, as demonstrated by a symlink attack on the .pamenvironment file in a user's home directory. NOTE: this vulnerability exists because of an incomplete fix for CVE-2010-3435.
pamnamespace.c in the pamnamespace module in Linux-PAM (aka pam) before 1.1.3 uses the environment of the invoking application or service during execution of the namespace.init script, which might allow local users to gain privileges by running a setuid program that relies on the pamnamespace PAM check, as demonstrated by the sudo program.
The pammail and pamenv modules in Linux-PAM before 1.1.2 did not drop privileges before accessing users' files (CVE-2010-3435, see bug #641335). Privilege dropping was added in 1.1.2, but with couple of issues pointed out by Solar Designer:
http://thread.gmane.org/gmane.comp.security.oss.general/3311/focus=3534
The code fails to switch fsgid/egid and groups (CVE-2010-3430) and does not check setfsuid() return value (CVE-2010-3431). Fix using newly-introduced pammodutildroppriv / pammodutilregainpriv was committed in upstream CVS and should be included in 1.1.3: http://git.altlinux.org/people/ldv/packages/?p=pam.git;a=commitdiff;h=843807a3a90f52e7538be756616510730a24739a
The pammail and pamenv modules in Linux-PAM before 1.1.2 did not drop privileges before accessing users' files (CVE-2010-3435, see bug #641335). Privilege dropping was added in 1.1.2, but with couple of issues pointed out by Solar Designer:
http://thread.gmane.org/gmane.comp.security.oss.general/3311/focus=3534
The code fails to switch fsgid/egid and groups (CVE-2010-3430) and does not check setfsuid() return value (CVE-2010-3431). Fix using newly-introduced pammodutildroppriv / pammodutilregainpriv was committed in upstream CVS and should be included in 1.1.3: http://git.altlinux.org/people/ldv/packages/?p=pam.git;a=commitdiff;h=843807a3a90f52e7538be756616510730a24739a
It was reported that pamenv and pammail modules do not drop privileges before accessing users' files. This flaw can lead to information disclosure.
Issue was partially addressed in Linux-PAM 1.1.2: http://git.altlinux.org/people/ldv/packages/?p=pam.git;a=commitdiff;h=06f882f30092a39a1db867c9744b2ca8d60e4ad6
The fix in 1.1.2 has some problems though, it fails to switch fsgid/egid and groups (CVE-2010-3430) and does not check setfsuid() return value (CVE-2010-3431): http://thread.gmane.org/gmane.comp.security.oss.general/3311/focus=3551
Fix using newly-introduced pammodutildroppriv / pammodutilregainpriv was committed in upstream CVS and should be included in 1.1.3: http://git.altlinux.org/people/ldv/packages/?p=pam.git;a=commitdiff;h=843807a3a90f52e7538be756616510730a24739a
Acknowledgements:
Red Hat would like to thank Sebastian Krahmer of the SuSE Security Team for reporting this issue.