
Linux kernel FUSE readdir cache out-of-bounds write (CVE-2026-31694): a malicious FUSE server overflows a page-cache page by 24 bytes. PoC plus an unprivileged local-root exploit via /etc/passwd page-cache corruption. Run only inside a VM.
A missing bounds check in fs/fuse/readdir.c:fuse_add_dirent_to_cache(). A FUSE
server returns a dirent with namelen = 4095, which serializes to a 4120-byte
record. The kernel copies it into a single 4096-byte page-cache page and
overflows by 24 server-controlled bytes into the next physical page.
This repo has a marker PoC for the write primitive and a local privilege
escalation that grooms the overflow onto the page-cache copy of /etc/passwd
and turns root into a passwordless account.
FUSE_NAME_MAX bump to PATH_MAX - 1 made the
readdir path reachable)51a8de6c50bf9 "fuse: reject oversized dirents in page cache" (one
of five stable backports)The overflow is a kernel write into whatever physical page sits directly after the FUSE cache page. The grooming aims that write at a chosen page, but it is probabilistic. On a miss the 24 bytes can hit an unrelated page:
Run it only inside a throwaway VM. The scripts here always run the exploit inside QEMU/KVM, never on the host.
exploit.c the PoC and LPE (single file, static build)
Makefile builds ./exploit (static)
vm/config.sh all paths and settings (edit KERNEL_TREE)
vm/make_rootfs.sh build the Ubuntu 24.04 guest image (run as root)
vm/build_vuln_kernel.sh build a vulnerable bzImage from your kernel tree
vm/run_vm.sh boot the guest headless
vm/provision_guest.sh in-guest setup (runs over ssh)
vm/run_in_vm.sh one command: boot, provision, run, verify
Host: Linux with KVM (/dev/kvm present, your user in the kvm group), and:
qemu-system-x86_64, sshpass, gcc, makemake_rootfs.sh: debootstrap (and root)You also need a Linux kernel source tree to build a vulnerable kernel from.
Edit vm/config.sh. The only value you must set is KERNEL_TREE: the path
to a Linux source checkout. Use a revision before the fix 51a8de6c50bf9
(any 6.15..7.0 checkout has the bug). If the tree already has the fix,
build_vuln_kernel.sh removes the guard for the build and restores the file
afterwards. Everything else (memory, cpus, ssh port, image paths) has a
default you can leave alone. Any value can also be set from the environment,
for example KERNEL_TREE=/path/to/linux vm/run_in_vm.sh.
Build the guest image (Ubuntu 24.04, needs root for debootstrap):
sudo vm/make_rootfs.sh
This creates a test:test sudo user, a serial console, ssh password auth,
and fuse3.
Build the vulnerable kernel:
vm/build_vuln_kernel.sh
End to end (boot, provision, marker PoC, LPE, verify uid 0):
vm/run_in_vm.sh
Manual:
vm/run_vm.sh & # boot headless; console goes to vm/serial.log
make # build the static exploit
# copy ./exploit into the guest, then as the test user:
./exploit --poc -n 20 # marker test
./exploit # warmup, then LPE
Stop the VM:
pkill -f bzImage-fuse-vuln
--poc marker mode only, no /etc/passwd changes
-n N number of rounds
(no args) 5 warmup rounds, then up to 200 LPE attempts; on success it writes a
passwordless root line to /etc/passwd and drops caches
Marker mode prints per-round hits and a total, for example 20/20 (100.0%).
LPE mode prints Warmup: 5/5, then an LPE n/200 ... HIT! line, changes the
first line of /etc/passwd from root:x:0:0:root:/root:/bin/bash to
root::0:0:root:/root:/bin/sh, and a following su -s /bin/sh root with an
empty password returns uid 0.
The readdir cache path copies each dirent into a page-cache page. The size check
only asks whether the dirent fits in the remaining space of the current page; it
never asks whether the dirent fits in a page at all. With namelen = 4095 the
record is 4120 bytes, so the copy runs 24 bytes past the end of a fresh page.
The exploit drains the per-CPU page (PCP) freelists so the allocator hands out
physically adjacent pages from buddy splits, then arranges the readdir cache
page and a victim page to be neighbours. For the LPE the victim is the page-cache
copy of /etc/passwd: the 24-byte overflow rewrites root's line so its password
field is empty, and with PAM nullok (Ubuntu default) su root then succeeds
with an empty password.
Full writeup: https://cyberstan.co.uk/fuse-readdir-oob/
Reported upstream by Qi Tang and Zijun Hu; the fix was authored by Samuel Page. I found and reported this independently. The upstream credit and CVE-2026-31694 are theirs. The bug is fixed in mainline and in the stable trees.
MIT, see LICENSE. For education and authorized testing only.