# DF-0024 — VERDICT

## Verdict: REPRODUCED (code-level; silent heap OOB write, root-only)

**Impact:** Heap OOB write (corruption) — root-only trigger; defense-in-depth / local-DoS class.
The overflow is **real** (confirmed by line-by-line source trace and code-path reachability
analysis) but **silent** on the default GENERIC kernel: DragonFlyBSD's slab allocator has no
heap redzone, so a 16-byte overflow past a 1024-byte `M_LINKER` allocation into the adjacent
slab chunk does not fault or panic. The PoC exercises the vulnerable path and the overflow IS
written, but produces no userspace-observable crash.

## Mechanism (trigger → primitive → effect)

1. **Trigger:** `kldload("<1023-byte bare name>")` (root-only, `SYSCAP_NOKLD` gate at
   `sys/kern/kern_linker.c:794`). `copyinstr` at `:798` accepts up to `MAXPATHLEN-1 = 1023`
   bytes. A name with no `/` and no `.` takes the bare-module branch at `:809-811`
   (`modname = file`).

2. **Path to vulnerable function:** `sys_kldload:815` → `linker_load_module(NULL, modname, …)`
   (`:1536`) → `linker_search_path(modname)` (`:1413`).

3. **Primitive:** In `linker_search_path`, the name has no `/`, so it falls through both early
   returns (`:1427`, `:1431`) to the "traverse the linker path" section at `:1458`:
   ```c
   buf = kmalloc(MAXPATHLEN /* 1024 */, M_LINKER, M_WAITOK);   /* :1458 */
   ...
   name_len = strlen(name);            /* 1023 */
   ...
   prefix_len = 12;  /* "/boot/kernel" */
   sep = 1;          /* no trailing '/' */
   strncpy(result, cp, prefix_len);    /* 12 bytes */
   result[prefix_len++] = '/';         /* offset 12, prefix_len→13 */
   strcpy(result + prefix_len, name);  /* :1476 — writes 1024 bytes (name+NUL) at offset 13 */
   ...
   result_len = strlen(result);        /* 1036 */
   strcpy(result + result_len, ".ko"); /* :1480 — writes 4 more bytes at offset 1036 */
   ```
   With `prefix_len=13` and `name` = 1023 bytes + NUL = 1024 bytes, `strcpy` at `:1476`
   writes offsets 13..1036 — overflowing the 1024-byte buffer by **13 bytes** (offsets
   1024..1036). The `:1480` ext strcpy adds 4 more bytes at offsets 1036..1039. **Total
   overflow: ~16 bytes** into the adjacent `M_LINKER` slab chunk.

4. **Effect:** Silent heap corruption. The overflowed path string (`/boot/kernel/AAA…AAA.ko`)
   is passed to `nlookup_init`/`vn_open` which returns `ENOENT` (file doesn't exist). The
   buffer is then `kfree`'d. The adjacent slab chunk's first 16 bytes are corrupted, but
   the slab allocator does not detect this (no redzone; INVARIANTS only checks the allocation
   bitmap for double-alloc/free and poisons freed chunks with `WEIRD_ADDR`). The corruption
   may surface later as a UAF/double-free if the adjacent chunk is a live `linker_file`
   object whose `refs`/`flags`/`userrefs` fields are overwritten, but this is not reliably
   triggerable from the PoC.

## Why no panic?

DragonFlyBSD's slab allocator (`sys/kern/kern_slaballoc.c`) has **no heap redzone**:
- `chunk_mark_allocated`/`chunk_mark_free` (`:1654`/`:1670`) only check the allocation bitmap
  (double-alloc/double-free detection), not the chunk content.
- WEIRD_ADDR (`0xdeadc0de`) poisoning (`:1568-1571`) only fills freed chunks — it detects UAF,
  not overflow into an allocated chunk.
- The overflow goes into the adjacent slab chunk's data area, which may be allocated (corrupts
  a live object silently) or free (corrupts the WEIRD_ADDR pattern, which is never checked on
   re-alloc).

Confirmed by running the PoC 50× in a tight loop on the unpatched `#0` kernel: no panic, no
kernel log, guest stays up.

## Exploit chain

Not applicable for privilege escalation. The trigger is **root-only** (`SYSCAP_NOKLD`):
root can already `kldload` an arbitrary `.ko` for direct kernel code execution, so there is no
privilege boundary to cross. This is a defense-in-depth / local-DoS finding, consistent with
the Low severity rating. Under KLD-signature enforcement or a capsicum-restricted root with
only the KLD capability, the heap corruption primitive would become meaningful (potential UAF
via corrupting adjacent `linker_file` metadata), but that is not the default threat model.

## PoC changes

- **Fixed nested C comment** in `kldload_overflow.c:6` — `/* 1024 */` inside the block comment
  prematurely closed the outer `/*`, causing a compile failure. Replaced with `(=1024)`.

## Fix validation (Phase 8)

**fix.diff** adds two layers of defense:
1. **Root cause** (`linker_search_path`, `:1470-1494`): compute `ext_max` once, then skip any
   path component whose assembly `prefix_len + sep + name_len + ext_max + 1 > MAXPATHLEN`
   before the `strcpy`.
2. **Input validation + observable marker** (`sys_kldload`, `:812-823`): reject bare module
   names longer than `MAXPATHLEN - 32` with `ENAMETOOLONG` before reaching `linker_search_path`.

**Before/after** (decisive):
- **Unpatched** `#0` (`6.5-DEVELOPMENT #0`, Thu Jul 2 06:02:54 UTC 2026):
  `kldload("AAA…AAA")` → `ENOENT` ("No such file or directory") — overflow happens silently.
- **Patched** `#1` (`6.5-DEVELOPMENT #1`, Sun Jul 12 18:47:23 UTC 2026, sha256
  `5e48e21e…`):
  `kldload("AAA…AAA")` → `ENAMETOOLONG` ("File name too long") — length check fires before
  the overflow. 3/3 deterministic.

**Regression check:** legitimate short module name (`kldload /nonexistent_module`) still
returns `ENOENT` on the patched kernel — no regression.

**Fix status: FIXED.**
