# DF-5336 — VERDICT

**Status: reproduced (3/3 deterministic runs, stock kernel) — fixed by fix.diff (validated in-guest).**
**Impact: leak** (deterministic OOB read of adjacent kernel .rodata, cross-table
content + padding disclosed on the DDB console; layout-dependent wild-deref
potential via `db_printf("%s")`). Not a crash on this build; not privilege
escalation (DDB-gated attack surface).

## Root cause (line-accurate)

`sys/cpu/x86_64/misc/db_disasm.c:1273-1277`:

```c
else if (ip->i_extra == db_Grp7) {
     if((regmodrm & 0xC0) == 0xC0) {
        i_name = db_Grp7_11[f_reg(rex, regmodrm)][regmodrm &0x7];   /* :1275 */
        i_mode = 0;
     }
```

The inner index `regmodrm & 0x7` is the modrm **rm** field, 0..7, with no
bound. The sub-tables it indexes (`db_disasm.c:174-211`) have 5
(`db_Grp7_11_000`), 4 (`_001`), 7 (`_010`) and 2 (`_111`) entries. For
`reg` ∈ {0,1,2,5} (non-NULL rows of `db_Grp7_11`) and rm past the table
length, the compiler emits a plain out-of-bounds `mov` reading a
`const char *` from whatever follows each array in `.rodata`. The stolen
pointer is printed at `db_disasm.c:1303` (`db_printf("%s", i_name)`).

Distinct from the known findings: DF-1032 is the **outer** index
(`f_reg`→7 into the 6-entry `db_Grp7_11`); DF-1033 is REX.R-dependent
`f_reg` 8..15; DF-2360 is the NULL rows 3/4 plus REX wild derefs. This
finding needs **no REX and no outer-index abuse**: plain `0f 01 c5`, `0f 01
cc`, `0f 01 d7`, `0f 01 ea` … `ef` suffice. Fixing only the known findings
(bound `f_reg`, NULL-check) leaves every vector here live.

## Reproduction

KLD `df5336_oob.c` feeds crafted byte sequences directly to the in-kernel
`db_disasm()` (global, declared `ddb/ddb.h:93`) on module load and prints
banners around each call. Stock-kernel output (serial console, captured by
`vm.sh log`), identical across 3 loads (`run.log`, `run.2.log`, `run.3.log`):

```
CTRL 0f 01 c4 -> vmxoff      (in-bounds _000[4], correct)
CTRL 0f 01 c8 -> monitor     (in-bounds _001[0], correct)
CTRL 0f 01 d6 -> xtest       (in-bounds _010[6], correct)
CTRL 0f 01 e8 -> swapgs      (in-bounds _111[0], correct)
CTRL 0f 01 e9 -> rdtscp      (in-bounds _111[1], correct)
OOB  0f 01 c5 -> (null)      (_000[5]: zero alignment padding past array)
OOB  0f 01 c7 -> (null)      (_000[7]: padding)
OOB  0f 01 cc -> <empty>     (_001[4]: pointer to "" — db_Grp7_11_000[0])
OOB  0f 01 d7 -> (null)      (_010[7]: padding past 7 entries)
OOB  0f 01 ea -> xgetbv      (_111[2]: db_Grp7_11_010[0] — WRONG TABLE)
OOB  0f 01 ef -> xend        (_111[7]: db_Grp7_11_010[5] — WRONG TABLE)
```

Adjacency proven from the guest symbol table (`env.txt`):

```
ffffffff80d9d330 r db_Grp7_11_111   (2*8 = 16 bytes -> ends 0x...d340)
ffffffff80d9d340 r db_Grp7_11_010   <- _111[2]/[7] read 0/40 bytes into this
ffffffff80d9d380 r db_Grp7_11_001   (4*8 = 32 -> ends 0x...d3a0)
ffffffff80d9d3a0 r db_Grp7_11_000   <- _001[4] reads _000[0] (pointer to "")
```

Every OOB output is explained exactly by the adjacent-memory read; no
in-bounds entry could ever produce `(null)`, `xgetbv` for a reg=5
encoding, or `xend`. The `(null)` prints show DragonFly `kvprintf`
renders a NULL `%s` argument safely, which is why the padding reads do not
crash on this build.

## Why "leak" and not "panic"

On this toolchain the qwords past each sub-table are either 0 (padding) or
valid `.rodata` string pointers, so the observable is disclosure of
adjacent static-data content (and, when the adjacent qword is itself a
table pointer, the printed bytes are raw kernel pointers). The wild-deref
variant (adjacent non-pointer qword interpreted as `char *`) is real but
layout-dependent — with a different linker order/toolchain the same code
path dereferences arbitrary adjacent bytes. Either way the class is an
unbounded kernel-memory OOB read driven purely by disassembled bytes.

## Fix validation

`fix.diff` (generated mechanically; `git apply` verified on the host copy
of `sys/`, then applied inside the guest's `/usr/src`):

1. Pads `db_Grp7_11_{000,001,010,111}` to 8 entries with `""`.
2. Bounds the outer index: `reg > 5 || db_Grp7_11[reg] == NULL` → `""`
   (also closes DF-1032's outer OOB and DF-2360's NULL rows on this line).

Flow: `vm.sh reset with-src` → `git apply fix.diff` in `/usr/src` →
`make quickkernel KERNCONF=X86_64_GENERIC && make installkernel` (`fix_build.log`,
KERNEL_FIX_BUILD_OK) → reboot → rebuild + reload the identical PoC
(`build.log`).

Patched output (`run.patched.log`): all 5 CTRL lines unchanged
(vmxoff/monitor/xtest/swapgs/rdtscp — no regression), all 6 OOB lines now
print the in-table `""` filler. Baseline bad behavior gone; `0f 01 ea`
flipping from `xgetbv` to empty on identical input also proves the patched
kernel was actually running. Guest reset to the clean `with-src` snapshot
afterwards.

## Attempt log

1. KLD build on stock guest — clean build, first try.
2. `kldload` run 1 — all 6 OOB vectors exhibited, 5/5 controls correct.
3. Runs 2 and 3 — byte-identical (determinism for the leak claim).
4. Fix kernel build/boot + patched re-run — bug gone, controls intact.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
