# DF-3747 — VERDICT

**Status: inconclusive at runtime (kernel path untriggerable on this guest);
defect, codegen and layout proven certain from the shipped kernel binary +
executed userland replica.**

## The defect (source)

`sys/dev/misc/syscons/syscons.c:2004-2005` declares two function-scope
statics:

```c
static struct fkeytab fkey;   /* { u_char str[16]; u_char len; } == 17 bytes */
static int fkeycp;            /* 4 bytes */
```

At `syscons.c:2054` the console input path passes a type-punned pointer:

```c
p = kbd_get_fkeystr(scp->sc->kbd, KEYCHAR(c), (size_t *)&fkeycp);
```

`kbd_get_fkeystr` dispatches to `genkbd_get_fkeystr()`
(`sys/dev/misc/kbd/kbd.c:1110-1127`), which executes

```c
*len = kbd->kb_fkeytab[fkey].len;   /* kbd.c:1123 — size_t store */
```

an 8-byte `size_t` store into a 4-byte object — 4 bytes written past
`fkeycp` (upper half always 0, since the value is a zero-extended `u_char`).
Then `syscons.c:2055-2057`:

```c
fkey.len = fkeycp;              /* int -> u_char truncation */
if ((p != NULL) && (fkey.len > 0))
    bcopy(p, fkey.str, fkey.len);   /* length never clamped to 16 */
```

The copy length is `kb_fkeytab[].len` — an *untrusted* `u_char`. DF-1503
(pass-1, kbd.c) proved SETFKEY can leave `len` set to arbitrary values up
to 255 (`imin(-1, MAXFK)` wraps into a `u_char`). Through this sink that
corrupted length drives `bcopy` up to 255 bytes into the 17-byte static
`fkey.13184` → up to 238 bytes of attacker-influenced overflow into
adjacent kernel .bss statics of `syscons.ko`-adjacent objects. Even
without DF-1503, the pun alone is a genuine OOB write whose victim depends
purely on link layout.

## Binary proof (guest `/boot/kernel/kernel`, objdump/nm)

1. Layout: `fkeycp.13185 @ 0xffffffff817a6da0`; the next symbol is
   `fkey.13184 @ 0xffffffff817a6db0` (+0x10). The pun's stray 4 bytes
   ([0x…6da4, 0x…6da8)) therefore land in the 12-byte alignment gap on
   **this** build — no functional corruption on this exact link, but the
   write is outside any C object.
2. `genkbd_get_fkeystr+0x4c` (`0xffffffff80b7c10c`):
   `mov %rax,(%r12)` — the 8-byte store through the pun is really emitted
   (the pun crosses translation units, so GCC cannot shrink it; an in-TU
   replica IS shrunk to 32 bits, which the harness initially reproduced
   and then defeated with `volatile`).
3. `sccngetch+0x228` (`0xffffffff80b8e2a8`):
   `mov fkeycp.13185,%ecx ; movzbl %cl,%edx ; mov %cl,fkey.13184+0x10 ;
   … callq memmove(&fkey.str, p, len)` — the unclamped copy into the
   17-byte static is a real `memmove` call with the untrusted length.

## Harness (executed on the guest, `build.log` / `run.log`)

`pun_harness.c` models the exact layout (int @+0, gap, 17-byte struct,
canary guards) and performs both stores verbatim:
* [1] the 8-byte store zeroes `&fkeycp+4..+8` (the OOB bytes) — shown as
  `[4]=00 [5]=00 [6]=00 [7]=00`;
* [2] with a corrupted len=255 (DF-1503 style), 239 bytes are written past
  the model of `fkey` into the guard region. GCC flags the replicated copy
  itself: `warning: 'memcpy' forming offset [101, 303] is out-of-bounds`.

## Why not `reproduced`

The trigger is the kernel-console `cngetc` path: a **function-key press
read through the kernel console** (DDB `db>` prompt via Ctrl-Alt-Esc, boot
"press any key", panic/dump prompts). On the audit guest:

* the active console driver is `sio0` (serial); `sc0` is attached but its
  `cngetc`/`cncheckc` are never dispatched (`dmesg`: "sio0: console"),
* there is no physical keyboard (headless QEMU), and
  `machdep.enable_panic_key=0`.

So the kernel sink cannot be exercised at runtime on this guest — evidence
is binary-level certainty + the executed replica (same convention as
DF-3746). On a desktop DragonFly install where `sc` *is* the console, any
physical-console user can enter DDB (Ctrl-Alt-Esc, no privilege required)
and press F1 to fire the pun store; overflowing `fkey` additionally
requires the DF-1503 corruption (console-logged-in user via SETFKEY on the
inherited tty fd).

## Exploitability assessment

* Standalone pun: 4 zero bytes at a fixed link-layout-dependent offset,
  DDB/boot context only. On the audited build: padding. Low.
* With DF-1503: up to 238 attacker-chosen bytes past a kernel .bss static
  (`fkey.13184`), DDB/boot context. Corrupting adjacent syscons statics
  (e.g. `sc_console`, `cctl_dev`, `scrn_blank_time`) yields at best
  panic-on-next-use on this no-KASLR guest; context (debugger prompt)
  makes weaponization marginal. Low overall — the fix is cheap
  defense-in-depth and DF-1503's own fix removes the length corruption.

## Fix

`fix.diff` (never applied to `sys/`): declare `static size_t fkeycp` and
clamp `fkeycp` to `sizeof(fkey.str)` before the copy. Compiles by
inspection; not kernel-build-validated because the path cannot be
exercised on this guest — `fix_status: not_testable`.

## Cross-file note

The *store* lives in kbd.c (already covered by DF-1503/DF-1504 territory);
this finding is filed against syscons.c for the **pun creation** and the
**unclamped sink**, both of which are in this file.

## 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.
