There’s a joke that goes around my office: if corporate ever tries to lock down root access on our workstations, we’ll just use the latest sudo vulnerability to get it back. This is not an idle threat. CVE-2025-32463 has just been announced, and it allows arbitrary unprivileged users to acquire root privileges on most Linux systems out-of-the-box. This is an all-too-common story: a bug in an SUID binary allows unchecked privilege escalation. Once an attacker acquires root privileges the game is up: MAC systems can be disabled (setenforce 0), your firewall rules can be changed (nft flush ruleset), and the attacker can read arbitrary files (cat /etc/shadow). With one command all additional security measures can be bypassed, leading to a complete compromise of your system. Scary stuff!
Unfortunately, patching these vulnerabilities is playing a game of whack-a-mole. You may be able to patch some after the fact, but other vulnerabilities of this sort almost certainly remain. In cases like this, proactive measures are much better than reactive ones. How then can we prevent these vulnerabilities when we have little idea where the next one will show up?
Many local privilege escalation vulnerabilities require executing custom binaries on the target system. What if we just … block executing everything by default? This is exactly the approach that fapolicyd uses. It may seem extreme, but it is effective. fapolicyd maintains a database of trusted files on the system (by default those installed by your package manager), and all attempts to open or execute a file are checked against that database. Any access of a potentially dangerous file (e.g. an executable, shared library, or even Python script) is denied if that file is not on the trusted list. (For extra security, fapolicyd can also optionally track the SHA-256 hashes of trusted files and deny the access if that hash has changed.)
Let’s recreate the privilege escalation with a vulnerable version of sudo, and then see how fapolicyd blocks it. The exploit itself is quite clever: it creates a custom shared library that execs a root shell, and then tricks sudo into loading it via chroot(2). For a testing system I will be using CentOS Stream 10 with sudo version 1.9.15p5.
- As root, create an unprivileged user that isn’t in the wheel group: useradd -m lowpriv
- Set the password for that user: passwd lowpriv
- Switch to that user account: su -l lowpriv
- As the unprivileged user, copy the sudo-chwoot.sh script and run it. You should now have a root shell.
Next, we can see how fapolicyd blocks this. Assuming you are on a RHEL-based system, install fapolicyd and then start it. Re-running the exploit will now show the following:
[lowpriv@localhost ~]$ ./sudo-chwoot.sh woot! [sudo] password for lowpriv: Sorry, try again. [sudo] password for lowpriv: Sorry, try again. [sudo] password for lowpriv: sudo: 3 incorrect password attempts [lowpriv@localhost ~]$No dice—the exploit failed. We can see what happened using ausearch -i -ts recent -m FANOTIFY to view the audit log.
---- type=PROCTITLE msg=audit(07/01/2025 19:31:57.613:39571) : proctitle=sudo -R woot woot type=PATH msg=audit(07/01/2025 19:31:57.613:39571) : item=0 name=libnss_/woot1337.so.2 inode=10034633 dev=fd:00 mode=file,755 ouid=lowpriv ogid=lowpriv rdev=00:00 obj=unconfined_u:object_r:user_tmp_t:s0 nametype=NORMAL cap_fp=none cap_fi=none cap_fe=0 cap_fver=0 cap_frootid=0 type=CWD msg=audit(07/01/2025 19:31:57.613:39571) : cwd=/tmp/sudowoot.stage.eaL5jR type=SYSCALL msg=audit(07/01/2025 19:31:57.613:39571) : arch=x86_64 syscall=openat success=no exit=EPERM(Operation not permitted) a0=AT_FDCWD a1=0x643319d81e10 a2=O_RDONLY|O_CLOEXEC a3=0x0 items=1 ppid=119624 pid=119634 auid=admin uid=lowpriv gid=lowpriv euid=root suid=root fsuid=root egid=lowpriv sgid=lowpriv fsgid=lowpriv tty=pts3 ses=33 comm=sudo exe=/usr/bin/sudo subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null) type=FANOTIFY msg=audit(07/01/2025 19:31:57.613:39571) : resp=deny fan_type=rule_info fan_info=8 subj_trust=unknown obj_trust=noThe output is a little abstruse, but it shows that:
- A user tried to execute the vulnerable sudo command
- That sudo command tried to open libnss_/woot1337.so.2
- The openat syscall on that path failed with EPERM
- fapolicyd denied the access because the subject (the shared library) was untrusted.
The system overhead of fapolicyd is non-neglible: it evaluates all open and exec calls against the trust database, and it can be tricky to setup. However, given the existence of these privilege escalation vulnerabilies that allow trivial takeover of your entire system, you cannot run a secure system without it.
.png)
