Blocking Sudo Exploits with fapolicyd
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?1

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.2 (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.
[lowpriv@localhost ~]$ ./sudo-chwoot.sh
woot!
[root@localhost /]# id
uid=0(root) gid=0(root) groups=0(root),1001(lowpriv) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
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=no
The 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 withEPERM
- 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.
P.S. fapolicyd would also block the recent CVE-2025-6019, since the root shell that udisks2 is tricked into mounting would not be trusted.
I mean, $10 there’ll be another one in sudo, but it’s not the only problem.
I didn’t want to say it, but fapolicyd is similar in spirit to an anti-virus, except those use an allow-by-default-deny-if-suspicious approach (and are usually crap), while fapolicyd maintains a much more strict (and thus effective) deny-by-default-allow-if-trusted.