# DF-0825 — nfs_getnickauth post-increment bug

## Verdict: REPRODUCED — confirmed OOB heap read + deferred slab-corruption panic; fix VALIDATED

## Bug mechanism (confirmed by source trace + live kernel harness)

**Root cause:** `nfs_getnickauth()` in `sys/vfs/nfs/nfs_syscalls.c:1115-1119` has a
classic post-increment bug. The code allocates an 8-byte buffer and writes two
4-byte XDR words using `*nickp++`, which advances `nickp` past the first word.
It then assigns the **post-increment pointer** (not the base) to `*auth_str`:

```c
nickp = (u_int32_t *)kmalloc(2 * NFSX_UNSIGNED, M_TEMP, M_WAITOK);  // P = base, 8 bytes
*nickp++ = txdr_unsigned(RPCAKN_NICKNAME);   // writes P[0..3], nickp → P+4
*nickp = txdr_unsigned(nuidp->nu_nickname);   // writes P[4..7], nickp stays P+4
*auth_str = (char *)nickp;                     // BUG: auth_str = P+4, not P
*auth_len = 2 * NFSX_UNSIGNED;                 // auth_len = 8
```

**Consequence 1 — 4-byte OOB heap read (info leak):** The caller
`nfs_request()` (`sys/vfs/nfs/nfs_socket.c:1233-1235`) passes `auth_str` and
`auth_len=8` to `nfsm_rpchead()`. In the `RPCAUTH_KERB4` branch
(`sys/vfs/nfs/nfsm_subs.c:195-211`), `bcopy(auth_str, info.bpos, i)` copies 8
bytes starting at P+4 — i.e. P[4..7] (valid, the nickname) + P[8..11] (4 bytes
**past the 8-byte allocation** = OOB kernel heap). These 4 OOB bytes are placed
into the NFS RPC credential mbuf and transmitted to the NFS server.

**Consequence 2 — kfree of non-base pointer → slab corruption → panic:** After
building the RPC header, `nfs_request()` at `nfs_socket.c:1237-1238` does
`kfree(auth_str, M_TEMP)` where `auth_str == P+4`. On the default GENERIC kernel
(INVARIANTS ON), this corrupts the slab allocator's chunk tracking. The
corruption is detected **deferred** by the periodic `slab_cleanup()` GC timer,
which calls `chunk_mark_free()` and trips:
```
panic: assertion "(((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0"
       failed in chunk_mark_free at kern_slaballoc.c:1675
```

## Live reproduction (deterministic kernel harness)

A KLD module (`df0825_harness.c`) constructs a minimal `struct nfsmount` with a
planted nickname entry (uid 1001, nickname 0xDEADBEEF) and calls the **real**
`nfs_getnickauth()` (kernel symbol `0xffffffff80809570`). It then inspects
where `auth_str` actually points.

**Unpatched #0 kernel — 3 runs, all show the bug:**
```
DF-0825: auth_str = 0xfffff8008d680c84  ← last nibble 4 = off-by-4 from base
DF-0825: auth_str[0..3] = 0xdeadbeef    ← nickname, NOT RPCAKN_NICKNAME
DF-0825: auth_str[4..7] = 0x00000000    ← OOB heap (4 bytes past alloc)
DF-0825: *** BUG CONFIRMED ***
```
The auth_str pointer consistently ends in nibble `4` across all 3 runs
(0x...c84, 0x...6a4, 0x...914) — proving it is always `base+4`, never the base.

After the harness runs `kfree(auth_str)` (= kfree(P+4)), the deferred slab
panic fires:
```
panic: assertion "(((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0"
       failed in chunk_mark_free at kern_slaballoc.c:1675
chunk_mark_free() → slab_cleanup() → slotimer_callback() → softclock_handler()
```

**Patched #1 kernel (fix applied) — bug is GONE:**
```
DF-0825: auth_str = 0xfffff8008d382270  ← last nibble 0 = allocation base
DF-0825: auth_str[0..3] = 0x00000001    ← RPCAKN_NICKNAME ✓
DF-0825: auth_str[4..7] = 0xdeadbeef    ← nickname ✓
DF-0825: OK — auth_str points at allocation base (bug fixed)
```
Guest stays alive after 15+ seconds; no deferred slab panic.

## Reachability assessment

`nfs_getnickauth` is reached when an NFS client mount has `NFSMNT_KERB` set
(`mount -o kerb`) AND a nickname entry exists for the calling uid. The nickname
is established via `nfs_savenickauth()` (`nfs_socket.c:1499-1501`) after the
server responds to an RPC with a `RPCAUTH_KERB4` verifier.

**Important:** `NFSKERB` (the compile-time Kerberos v4 switch) is **never
defined** in any shipped DragonFlyBSD kernel config (`grep -rn "define NFSKERB"
sys/` → empty). The code compiles with stub 2-byte key typedefs and no-op
encryption. The `#ifdef NFSKERB / XXX / #endif` block at `nfs_syscalls.c:1136`
contains a literal `XXX` placeholder, confirming this path was never fully
implemented for modern use.

However, the **buggy code at lines 1115-1119 is NOT behind `#ifdef NFSKERB`** —
it is unconditionally compiled into every GENERIC kernel. And `nfs_getnickauth`
IS called at runtime whenever an `NFSMNT_KERB` mount is active. The function is
a live kernel symbol (`nm` confirms `T nfs_getnickauth` at `0xffffffff80809570`).

An unprivileged user on a client with a `-o kerb` NFS mount, after the nickname
exchange completes, triggers this bug on every subsequent NFS RPC.

## Impact

- **OOB heap read:** 4 bytes of uninitialized/freed kernel heap memory leaked
  into the NFS RPC auth credential, potentially transmitted to a remote NFS
  server (info leak).
- **Corrupted RPC auth:** The credential data sent to the server is shifted by
  4 bytes (wrong auth type/sequence → auth failures or misparse).
- **Slab corruption → panic:** `kfree(P+4)` corrupts the slab allocator; under
  INVARIANTS (default GENERIC) this is detected by the periodic slab GC and
  causes a kernel panic (local DoS).
- **No uid=0 escalation path:** This is an OOB read + data corruption primitive,
  not a write primitive. The 4 OOB bytes are read (leaked), not written through.
  There is no attacker-controlled write here → no escalation chain.

## Fix

`fix.diff` replaces the post-increment writes with array-index writes that
preserve the base pointer:
```c
-	*nickp++ = txdr_unsigned(RPCAKN_NICKNAME);
-	*nickp = txdr_unsigned(nuidp->nu_nickname);
+	nickp[0] = txdr_unsigned(RPCAKN_NICKNAME);
+	nickp[1] = txdr_unsigned(nuidp->nu_nickname);
 	*auth_str = (char *)nickp;   // now correctly = base
```
Validated on a single-fix kernel (#1): the off-by-4 is gone, auth_str is
properly aligned, auth_str[0] = RPCAKN_NICKNAME, and no panic occurs.
