# DF-0143 — nlookupdata leaked on nlookup failure (missing nlookup_done)

## Verdict: REPRODUCED (unbounded kernel memory leak, DoS)

The missing `nlookup_done()` on the `nlookup()` failure path is confirmed live:
each failed `vquotactl` on a nonexistent path leaks one MAXPATHLEN `M_NAMEI`
buffer (and a namecache reference). 20000 calls leaked ~19.5 MB. An
unprivileged user controls the path and can repeat indefinitely.

## The bug (sys/kern/vfs_quota.c:351-359)

```c
error = nlookup_init(&nd, path, UIO_USERSPACE, 0);   /* 351: kmalloc nl_path */
if (error)
    return (error);
error = nlookup(&nd);                                /* 354 */
if (error)
    return (error);                                  /* 355-356: BUG — no nlookup_done */
nch = nd.nl_nch;
cache_zero(&nd.nl_nch);
nlookup_done(&nd);                                   /* 359: only on SUCCESS */
```

`nlookup_init` (vfs_nlookup.c:131) allocates `nd->nl_path` from the `namei_oc`
objcache (backed by `M_NAMEI`, MAXPATHLEN=1024, vfs_init.c:207) and takes a
namecache reference (`cache_copy`, :150/160/171). `nlookup_done` releases both.
On the `nlookup()` failure path the function returns at line 355 **without**
`nlookup_done`, leaking `nl_path` + the namecache ref.

## Evidence (live, unprivileged maxx, vfs.quota_enabled=1)

```
vquotactl('/tmp/df0143_does_not_exist_<pid>') x 20000  -> errno ENOENT each
BEFORE: nameibufs   17   (0M)        vfs.cache.numcache 865
AFTER : nameibufs   19.5K (19.5M)    vfs.cache.numcache 886
```

Per-call leak ≈ 1 nameibuf (1024 B). 20000 × 1024 ≈ 19.5 MB of `M_NAMEI` slab
permanently leaked. The returned `errno=ENOENT` confirms the call reaches the
`nlookup()` failure path (line 354) — exactly the buggy return.

## Exploit chain

`none` (resource-leak DoS, no memory-corruption primitive) — the leaked objects
are namei path buffers + namecache refs; they are not attacker-shapeable into a
write/UAF. The realistic impact ceiling is **unbounded kernel memory growth →
exhaustion / OOM**, triggerable by an unprivileged user (no privilege check in
`sys_vquotactl`).

## Fix

`fix.diff` adds `nlookup_done(&nd)` before the error return at line 355.
`git apply --check` passes.

## Fix validation

See Phase 8 — the single-fix kernel shows `nameibufs` staying flat across the
same 20000-call workload (no leak).
