# DF-2253 — VERDICT

**Status: reproduced** — impact: `none` beyond a wrong diagnostic (this is a
correctness bug in a print helper, not memory unsafety; every write stays
inside the caller's buffer). All three claim elements verified through the
REAL kernel function, plus the unauthenticated-packet end-to-end on the live
stock kernel. Fix validated A/B.

## Root cause (path:line)

- `sys/libkern/hexncpy.c:56` — `outlen -= 3` unconditionally, but the
  separator write at :59-60 is conditional on `sep != NULL`.
- `sys/libkern/hexncpy.c:62` — `*--outb = 0` backs the pointer over the
  last written char regardless of what that char was.

Combined effect when `sep == NULL`:

1. **Truncation**: budget drains 3/iteration while only 2 chars are
   written — a `HEX_NCPYLEN(n)`-sized buffer converts at most ~2n/3
   bytes... (exactly: floor((outlen)/3) iterations = n for a paired
   buffer, but for buffers sized as 3n the loop stops one iteration early
   relative to what 2-char groups would allow) and the output is silently
   shorter than the input hex.
2. **Misplaced terminator**: the NUL overwrites the low-nibble digit of the
   last converted byte.

## Verified case matrix (kernel-compiled harness, kernel #1, stock code)

| case | call shape | input | stock output | correct output |
|------|-----------|-------|--------------|----------------|
| C4 | inlen=6 outlen=18 sep=NULL (generic) | de ad be ef 12 34 | `deadbeef123` (12th digit clobbered) | `deadbeef1234` |
| C5 | **if_ether.c:643 exact**: inlen=2 outlen=5 sep=NULL | 12 34 (ar_hrd 0x1234) | `1` | `1234` |
| C6 | mly.c:1344 shape: inlen=4 outlen=12 sep=NULL | de ad be ef | `deadbee` | `deadbeef` |
| C1 | control sep="-" inlen=4 outlen=12 | de ad be ef | `de-ad-be-ef` | (unchanged) |

## Reachable instance, end to end on the live stock kernel

`sys/netinet/if_ether.c:630-648` `arpintr()`: any ARP frame whose `ar_hrd`
is neither `ARPHRD_ETHER` nor `ARPHRD_IEEE802` logs
`"arp: unknown hardware address format (0x%s)"` with the value produced by
`hexncpy((unsigned char *)&ar->ar_hrd, 2, hexstr, 5, NULL)` (:643).

Demonstration (`arp_inject.c` + tap(4)): writing a crafted 42-byte ethernet
frame with `ar_hrd = 0x1234` to `/dev/tap0` lands directly in
`ifp->if_input` (`sys/net/tap/if_tap.c:981`) → `ether_input()` →
NETISR_ARP → `arpintr()` → the :643 call. Stock kernel console:

```
arp_inject: wrote 42 bytes to /dev/tap0 (ar_hrd=0x1234)
arp: unknown hardware address format (0x1)
```

**One hex digit instead of four** (`arp_e2e.log`; reproduced twice, two
identical lines). The injector needs root only because /dev/tap0 is
root-only on the guest; on a real network an *unauthenticated LAN peer*
triggers the same path with a single frame — hence the finding's threat
model. Memory safety: `hexstr` is `char hexstr[6]` on arpintr's stack
(if_ether.c:632); the function writes at most hexstr[0..1] here — no
overflow, purely a wrong diagnostic value (operators see `0x1` for any
0x10?? hrd, and `0x0` for any 0x00?? — the top nibble only).

## Fix validation (A/B, kernel #2 with fix.diff)

Same harness + same tap injection on the patched kernel (`fix_run.log`,
`arp_e2e_fixed.log`):

| case | baseline | patched |
|------|----------|---------|
| C4 | `deadbeef123` | `deadbeef1234` |
| C5 | `1` | `1234` |
| C6 | `deadbee` | `deadbeef` |
| C1 sep!="" | `de-ad-be-ef` | `de-ad-be-ef` — **bit-identical** |
| ARP e2e | `(0x1)` | `(0x1234)` — full value logged |

`fix.diff` changes the drain to `sep ? 3 : 2` and terminates right after
the last digit for `sep == NULL` (and after the trailing separator, as
historically, for `sep != NULL`); the loop still honors `outlen >= 3`
before each group, so the NUL always lands at index <= outlen-1 — the fix
cannot overflow any `HEX_NCPYLEN`-paired buffer (checked for if_ether's
`hexstr[6]` and mly's outlen=12 call).

## Artifacts

`run.log` (baseline matrix), `arp_e2e.log` / `arp_e2e_fixed.log` (packet
end-to-end A/B), `fix_run.log` (patched matrix), `build.log` /
`fix_build.log`, `arp_inject.c` (sha256
18d55cc12e5cb93fc7439c11acc73eaeb5ed4852eb07c230935e1555e2d1d98d),
`harness_hexncpy.c`, `fix.diff` (shared with DF-2252, git-apply clean),
`env.txt`, `run.sh`.

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