# Patch Analysis: CVE-2026-31694 FUSE readdir cache

## Patch / Fix Scope

The effective fix for CVE-2026-31694 is a sink-level bounds check in `fs/fuse/readdir.c:fuse_add_dirent_to_cache()`. The vulnerable function computes the serialized cache record length from the FUSE-server-controlled embedded directory entry:

```c
size_t reclen = FUSE_DIRENT_SIZE(dirent);
```

Before the fix, the function only handled entries that did not fit in the *remaining* bytes of the current page:

```c
offset = size & ~PAGE_MASK;
index = size >> PAGE_SHIFT;
if (offset + reclen > PAGE_SIZE) {
    index++;
    offset = 0;
}
...
memcpy(addr + offset, dirent, reclen);
```

That logic is insufficient when `reclen` itself is larger than `PAGE_SIZE`. For `namelen=4095`, `FUSE_DIRENT_SIZE(dirent)` is 4120 bytes. At `offset=0`, the code still copies 4120 bytes into one 4096-byte page, causing a 24-byte overflow into the next physical page.

The tested fixed module adds the equivalent of:

```c
/* Dirent does not fit in readdir cache page? Skip caching. */
if (reclen > PAGE_SIZE)
    return;
```

immediately after `reclen` is computed in `fuse_add_dirent_to_cache()`.

## Files / Functions Involved

Primary fixed function:

- `fs/fuse/readdir.c:fuse_add_dirent_to_cache()` — common readdir-cache insertion sink.

Relevant callers and parser paths:

- `fs/fuse/readdir.c:parse_dirfile()` — parses ordinary `FUSE_READDIR` response records using `FUSE_DIRENT_SIZE()` and calls `fuse_emit()`.
- `fs/fuse/readdir.c:parse_dirplusfile()` — parses `FUSE_READDIRPLUS` response records using `FUSE_DIRENTPLUS_SIZE()`, then passes the embedded `struct fuse_dirent` to `fuse_emit()`.
- `fs/fuse/readdir.c:fuse_emit()` — calls `fuse_add_dirent_to_cache()` when `FOPEN_CACHE_DIR` is set.
- `fs/fuse/readdir.c:fuse_readdir_uncached()` — chooses `FUSE_READDIRPLUS` when `fuse_use_readdirplus()` returns true, otherwise uses `FUSE_READDIR`.
- `fs/fuse/file.c:fuse_file_open()` — directory opens can set `FOPEN_CACHE_DIR`, enabling the cache path.
- `include/uapi/linux/fuse.h` — defines `struct fuse_dirent`, `struct fuse_direntplus`, `FUSE_DIRENT_SIZE()`, and `FUSE_DIRENTPLUS_SIZE()`.

## Fix Assumptions

The fix assumes the security invariant should be enforced at the cache-copy sink rather than at each wire-format parser. This is the right assumption for the observed root cause because the overflow is caused by copying an oversized `struct fuse_dirent` into a single cache page, regardless of whether that embedded dirent originated from a `FUSE_READDIR` or `FUSE_READDIRPLUS` response.

The fix does **not** assume that only ordinary `FUSE_READDIR` reaches the sink. It does **not** rely on parser-specific validation. Instead, it blocks any current or future path that calls `fuse_add_dirent_to_cache()` with `FUSE_DIRENT_SIZE(dirent) > PAGE_SIZE`.

## Code Paths / Inputs Not Covered

The patch does not prevent a malicious FUSE server from sending a large valid wire-format `FUSE_READDIRPLUS` record. `FUSE_DIRENTPLUS_SIZE(direntplus)` for `namelen=4095` is larger than a page, and the parser can still parse such a response if it fits in the kernel response buffer. However, the security-sensitive operation is the readdir-cache copy of the embedded `struct fuse_dirent`, and the sink-level guard prevents the oversized embedded dirent from being cached.

The patch also does not change normal `dir_emit()` behavior. It only skips caching entries whose serialized readdir-cache representation is too large for one page. This is acceptable because the vulnerability is specifically in the page-cache serialization step.

## Variant Search / Tested Candidate Matrix

### Candidate 1: `FUSE_READDIRPLUS` alternate parser path — confirmed on vulnerable, blocked by fix

- **Entry/message type:** `FUSE_READDIRPLUS`.
- **Different from parent:** Parent used ordinary `FUSE_READDIR`; this candidate negotiates `FUSE_DO_READDIRPLUS` and sends a `struct fuse_direntplus` response.
- **Path:** `fuse_readdir_uncached()` -> `parse_dirplusfile()` -> `fuse_emit()` -> `fuse_add_dirent_to_cache()`.
- **Vulnerable behavior:** confirmed. The vulnerable module corrupted `/etc/passwd` page-cache contents through the READDIRPLUS path.
- **Fixed behavior:** blocked. The fixed module received the same READDIRPLUS oversized embedded dirent but left `/etc/passwd` unchanged.
- **Conclusion:** real alternate trigger on vulnerable code; not a bypass.

### Candidate 2: ordinary `FUSE_READDIR` parent path — already fixed by sink guard

The parent path was already reproduced in `bundle/repro/`. Because it reaches the same `fuse_add_dirent_to_cache()` sink, the fixed module blocks it. Re-testing this same path would not be a materially distinct variant for this stage.

### Candidate 3: cached-read replay path — ruled out as trigger source

`fuse_readdir_cached()` reads and parses entries already stored in the readdir cache. It does not ingest attacker-controlled FUSE wire responses and does not call `fuse_add_dirent_to_cache()` to create new oversized records. It is therefore not a separate untrusted-input entry point for the same overwrite.

## Behavior Before and After the Fix

### Vulnerable module

- `FUSE_READDIRPLUS` response with embedded `namelen=4095` is accepted.
- `parse_dirplusfile()` calls `fuse_emit()`.
- `fuse_add_dirent_to_cache()` computes `reclen=4120`.
- The vulnerable code moves to a fresh page but does not reject the oversized record.
- `memcpy()` writes 4120 bytes into a 4096-byte page.
- Runtime proof changed `/etc/passwd` page-cache first line to `root::0:0:x:.:` as uid 1000.

### Fixed module

- The same `FUSE_READDIRPLUS` response reaches the parser and attempts to emit the embedded dirent.
- `fuse_add_dirent_to_cache()` computes `reclen=4120`.
- The new `reclen > PAGE_SIZE` guard returns before cache allocation/copy.
- No page-cache corruption occurs; `/etc/passwd` remains `root:x:0:0:root:/root:/bin/sh`.

## Threat Model / Security Policy Scope

The Linux kernel tree does not have a top-level `SECURITY.md` in this prepared source snapshot, but it includes `Documentation/process/security-bugs.rst` and a `MAINTAINERS` security contact entry. The documentation states that Linux kernel developers take security bugs seriously and asks reporters to contact the Linux kernel security team at `security@kernel.org`. This issue is in scope for kernel security analysis because an unprivileged local process controls a FUSE server response that is parsed by privileged kernel code and can corrupt kernel page-cache state for a root-owned file.

## Completeness Assessment

For the tested root cause, the fix is complete. The discovered `FUSE_READDIRPLUS` alternate path demonstrates why a parser-only fix would have been incomplete, but the actual sink-level guard covers both the original and alternate parser paths. No bypass of the fixed module was found.
