Share
## https://sploitus.com/exploit?id=B1A34079-E8F9-5174-9297-C9EF365CAE42
# FUSE readdir cache out-of-bounds write PoC

Local proof of concept for a missing bounds check in
`fs/fuse/readdir.c:fuse_add_dirent_to_cache()`. A FUSE server that returns a
dirent with `namelen = 4095` produces a 4120 byte record. The kernel copies it
into a single 4096 byte page-cache page and overflows by 24 bytes into the next
physical page. The server controls those 24 bytes.

Two modes:

* marker mode (`--poc`): proves the 24 byte write lands on the adjacent page.
* LPE mode (default): grooms the page allocator so the victim page after the
  cache page is a cached copy of `/etc/passwd`, overflows root's line to an
  empty password, then uses `su` with an empty password to get uid 0.

Affected kernels: 6.15 and later (the `FUSE_NAME_MAX` bump to `PATH_MAX - 1`
made the path reachable). Fixed upstream by commit `51a8de6c50bf9`.

## Warning: VM only, never on bare metal

Do not run this on a machine you care about. 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:

* another file's cached pages, including dirty pages that get written back to
  disk, which corrupts real files on the host,
* kernel data, which can panic the box or cause silent corruption.

Run it only inside a throwaway VM whose disk and uptime you do not care about.
The scripts here always run the exploit inside QEMU/KVM, never on the host.

## Files

In this directory:

* `exploit.c` PoC and LPE. Single file, no libfuse dependency.
* `Makefile` builds a static `exploit` binary.
* `run_in_vm.sh` host side driver. Boots the guest, provisions it, copies the
  exploit, runs the marker PoC and then the LPE, and checks for uid 0.
* `vm_provision.sh` runs inside the guest. Installs `fuse3`, makes sure
  `/dev/fuse` exists, leaves PAM `nullok` in place for empty password `su`, and
  saves a pristine `/etc/passwd.orig` for clean re-runs.

In `../vm_setup/`:

* `build_kernel_fuse_vuln.sh` builds a vulnerable `bzImage-fuse-vuln` from a
  kernel tree whose `fs/fuse/readdir.c` does not have the `reclen > PAGE_SIZE`
  guard. It restores the working tree file afterward and does not touch the git
  index.
* `run_fuse_vuln.sh` boots that kernel headless under QEMU/KVM.

## Local environment

Host packages: `qemu-system-x86_64`, `sshpass`, `gcc`, `make`, and KVM access
(`/dev/kvm` readable, your user in the `kvm` group).

Kernel tree: a Linux checkout at a revision without the fix, so
`fs/fuse/readdir.c` lacks the guard, with a `.config` that has
`CONFIG_FUSE_FS=y`, `CONFIG_USER_NS=y`, virtio block and scsi, 9p, and ext4.

Rootfs: an Ubuntu 24.04 (noble) qcow2 with a `test` user (password `test`) in
the `sudo` group, a serial console, and `PasswordAuthentication yes`. The base
image's `/etc/fstab` names the root device `/dev/sda` and a 9p mount tagged
`source`, so the boot script presents the overlay disk as virtio-scsi (which
enumerates as `/dev/sda`) and shares the kernel tree over 9p. Root login is key
only, so the scripts drive the guest as the unprivileged `test` user over SSH
forwarded to `localhost:10022`, using `sshpass`.

Paths: the three `.sh` scripts set a `BASEDIR` variable at the top. Point it at
your kernel tree before running them.

## Build and run

One command does everything (build kernel if needed, boot, provision, run,
verify):

```
./run_in_vm.sh
```

Manual steps:

```
../vm_setup/build_kernel_fuse_vuln.sh      # produces vm_setup/bzImage-fuse-vuln
../vm_setup/run_fuse_vuln.sh &             # boot headless, serial to a log file
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
```

`exploit` options:

* `--poc` marker mode only.
* `-n N` number of rounds.
* no args: 5 warmup marker rounds, then up to 200 LPE attempts. On success it
  writes a passwordless root line to `/etc/passwd`, drops caches, and opens a
  root shell.

## Expected output

Marker mode reports hits per round 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.

## Cleanup

```
pkill -f bzImage-fuse-vuln                 # stop the guest
rm -f ../vm_setup/noble-overlay-fuse-vuln.qcow2   # discard guest disk state
```

The exploit saves `/etc/passwd.orig` in the guest, and `run_in_vm.sh` restores
from it before each LPE run, so repeated runs start from a clean `/etc/passwd`.