# DF-3849 — VERDICT

**Status: `inconclusive` (runtime) · source-level: certain**
**Impact ceiling: kernel heap OOB read (info leak, up to ~14.0 KB per
frame past a 2040-byte RX cluster) handed to the network stack / BPF; and
negative `m_len` (unsigned ≈4 GB) on FRAMESZ < 4 ⇒ stack OOB walk / panic.
Local DoS + limited info leak from a malicious or misbehaving dc(4) NIC
(any of the 21143/PNIC/PNICII/Macronix/ADMtek/ASIX/Xircom/Conexant/Davicom
clones this driver binds).**

## What was verified, and how

### 1. Source-level verification (certain)

`sys/dev/netif/dc/if_dc.c` read end-to-end; guest `/usr/src` copy
verified byte-identical (md5 `f396b494478178deff15f9ba999ef0b9`).

- RX buffers are single mbuf clusters trimmed by 8 bytes:
  - `dc_newbuf` `if_dc.c:2321-2324` `m_new = m_getcl(M_NOWAIT, MT_DATA,
    M_PKTHDR)`; `m_new->m_len = m_new->m_pkthdr.len = MCLBYTES`
    (= 2048), then `if_dc.c:2331` `m_adj(m_new, sizeof(u_int64_t))`
    ⇒ **2040 usable bytes**, posted to the descriptor with
    `c->dc_ctl = DC_RXCTL_RLINK | DC_RXLEN` (`:2343`,
    `DC_RXLEN = 1536`, `if_dcreg.h:465`).
- The only length source for the handed-up frame is the device-DMA'd
  descriptor status word:
  - `if_dc.c:2523` `total_len = DC_RXBYTES(rxstat);`
    (`if_dcreg.h:415-418`: `DC_RXSTAT_RXLEN 0x3FFF0000` — a **14-bit**
    field, 0..16383).
  - The only sanity gate is `if_dc.c:2548-2565`, which reads error bits
    out of the **same device-produced status word** and explicitly
    tolerates `DC_RXSTAT_GIANT` without other error bits ("could be
    vlans") — i.e. the length is gated by the same producer that wrote it.
  - `if_dc.c:2568` `total_len -= ETHER_CRC_LEN;` — `total_len` is `int`;
    `FRAMESZ < 4` goes negative.
  - `if_dc.c:2581-2584` (x86_64 quick path, `dc_quick=1` default,
    `if_dc.c:317-318`) `m->m_pkthdr.len = m->m_len = total_len;` — the
    freshly re-posted 2040-byte cluster is handed up with the trusted
    length.
  - `if_dc.c:2590-2591` (copy path) `m_devget(mtod(m, char *) -
    ETHER_ALIGN, total_len + ETHER_ALIGN, 0, ifp)` — copies the trusted
    length out of the cluster.
- **No clamp exists anywhere on this path.** A descriptor with RXERR
  clear and `FRAMESZ = 16383` yields `m_len = 16379` on a 2040-byte
  cluster ⇒ up to **14,339 bytes** of heap over-read when the stack
  consumes the mbuf (BPF taps copy it to userspace; ether_input walks
  headers within the claimed length). `FRAMESZ = 0..3` yields a negative
  `m_len` (≈4 GB unsigned) ⇒ immediate OOB walk.
- The setup-frame RX path (`dc_pnic_rx_bug_war`, `if_dc.c:2402-2457`)
  has its own unbounded-copy/length-trust issues already filed as the
  known DF-1249 — this finding is the **mainline** `dc_rxeof` trust,
  which every supported chip hits.

Ring math cross-checked so nothing else bounds it: the walk is
ring-bounded (`DC_INC`, ≤ `DC_RX_LIST_CNT` iterations, each processed
descriptor re-posted with OWN), every path either advances `i` or
returns — the defect is purely the missing length-vs-cluster check.

### 2. Guest runtime verification (why inconclusive, not reproduced)

Audit guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC), virtio-net
only. Evidence in `run.log`:

- driver present in the running kernel: `kldstat -v` → `pci/if_dc`,
  `cardbus/if_dc`; `/boot/kernel/if_dc.ko` exists;
- **no supported PCI NIC**: `ifconfig -l` → `vtnet0 lo0`; `pciconf -lv`
  shows only QEMU i440fx/PIIX4 + virtio devices ⇒ the descriptor-DMA
  precondition cannot be produced on this guest;
- the exact integer mechanics executed on the guest via `trigger.c`
  (`build.log`): FRAMESZ 16383 → `m_len=16379` on the 2040-byte cluster
  (14,339 bytes OOB); FRAMESZ 2 → `m_len=-2` (unsigned ~4294967294).
  Compiled with the macros copied verbatim from `if_dcreg.h`.

## Threat model / preconditions

Requires a dc(4)-attached device that DMA-writes a descriptor with RXERR
clear and a FRAMESZ field that exceeds the posted buffer (or < 4):
buggy clone silicon (this driver binds ~15 cheap clone families), a
compromised/attacker-controlled PCIe device (Thunderbolt/external-chassis
DMA), or platform DMA corruption. Same trust boundary as DF-3604
(bce_rx_intr) — the dc(4) instance of that family.

## Fix

`fix.diff` (git apply --check clean against the read-only tree): clamp
after `total_len -= ETHER_CRC_LEN` — drop frames with
`total_len <= 0 || total_len > MCLBYTES - sizeof(u_int64_t)`, re-posting
the cluster (same treatment as the error path). Covers both the quick
path and the `m_devget` path, and re-covers the PNIC-war re-entry since
it re-enters above the clamp.

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