# DF-2931 VERDICT — REPRODUCED (leak)

**Finding:** `if_getanyethermac()` reads the "MAC" for UUID v1 node IDs by
casting `ifp->if_softc` to `struct arpcom *` and copying from
`offsetof(struct arpcom, ac_enaddr)` (sys/net/if.c:3032). That offset equals
`sizeof(struct ifnet)` (928 on this build) and is only meaningful when the
driver softc *starts* with `struct arpcom` (assumption documented at
sys/net/if_var.h:153-171 and sys/net/if_arp.h:115). For drivers that use the
new-bus/`if_alloc()` style with a separate ifnet (vtnet, ena, oce in-tree),
the 6 bytes come from an arbitrary softc-relative location — driver state
that is not a MAC — and flow, through `uuid_node()` (sys/kern/kern_uuid.c:88)
and `kern_uuidgen()`, into every UUID returned by the unprivileged
`uuidgen(2)` syscall. For any such driver whose softc is smaller than 928
bytes the same expression is a heap out-of-bounds read.

## How it was proven
1. **Trigger (unprivileged, end-to-end):** `/tmp/uuidleak` run as uid 1001
   (run.first.log): all 4 UUIDs version 1, node field
   `01:00:00:00:00:00`, decoded v1 timestamp == host wall clock to the
   second. vtnet0's real MAC is `52:54:00:12:34:56` — the node is *not*
   the MAC.
2. **Structural proof (KLD module uuidoff.ko):** prints
   `sizeof(struct ifnet)=928`, `offsetof(struct arpcom,ac_enaddr)=928`,
   `sizeof(struct vtnet_softc)=1040`, and dumps the bytes at
   `if_softc+928` for every IFT_ETHER interface:
   * vtnet0: `00:00:00:00:00:00` at `sc+928` (== `vtnet_vlan_shadow[100..101]`,
     the field that actually lives there) — this is exactly what
     `if_getanyethermac` copied into the UUID node (module.log).
   * tap0 (conforming, arpcom-first): bytes at `sc+928` == `00:bd:e8:3f:01:00`
     == tap0's *exact* MAC (module.tap.log) — proving the read address is
     literally `sc+928` and that on conforming drivers the real MAC is
     returned (that branch is finding DF-2932).
3. **Determinism:** 3 spaced runs (run.determinism.log): node constant
   `01:00:00:00:00:00`, timestamp tracks wall clock — a fixed memory read,
   not RNG output. (If `if_getanyethermac` had returned ENOENT,
   `read_random` at kern_uuid.c:89 would have produced per-call random
   node bytes — refuted.)
4. **Mutation attempt:** created `vlan3200` to set a bit inside
   `vtnet_vlan_shadow` (run.vlan.log): shadow stayed 0 because the
   vlan_config eventhandler is only registered when the
   VIRTIO_NET_F_CTRL_VLAN feature is negotiated (if_vtnet.c:835-848), which
   this QEMU does not offer. Negative result does not weaken the proof in
   (1)-(3); it only blocks an optional flourish.

## Why the leaked bytes matter (impact ceiling)
* 6 bytes of kernel driver softc memory per call, address pinned to
  `first_ether_softc + 928`, readable by any unprivileged local user (and
  from jails — `sys_uuidgen` has no prison check), unlimited call rate.
* Content is entirely a function of the first ether driver's struct layout
  at offset 928: today, zeros on stock vtnet (vlan shadow), but any layout
  change or other non-conforming driver can put pointers/counters there;
  for a non-conforming softc < 928 bytes the read is heap OOB (none of the
  three in-tree violators is currently < 928 — vtnet=1040 — so the OOB
  variant is *speculative for current in-tree drivers*, the wrong-field
  leak is *certain*).
* Side effect: on every vtnet-based VM (the default virtio NIC), ALL hosts
  emit v1 UUIDs with the identical node `01:00:00:00:00:00`, voiding the
  v1 uniqueness guarantee across machines (same node + same 100 ns tick +
  same 14-bit clock-seq ⇒ collision). UUIDs are used as persistent
  identities (hammer2 PFS ids via kern_uuidgen at
  sys/vfs/hammer2/hammer2_ioctl.c:902, disklabel64 d_stor_uuid at
  sys/kern/subr_disklabel64.c:472).

## Why not uid=0
This is a read-only disclosure primitive (6 bytes at a fixed
driver-determined offset, no attacker-controlled index), not a memory
*corruption* bug — there is no write primitive to develop into an
escalation chain. Primary objective (2) applies: the leak genuinely
manifests; realistic ceiling is limited kernel-memory disclosure plus the
cross-host v1-collision correctness break.

## False-positive check
None: reproduced end-to-end unprivileged, plus in-kernel structural proof
with a counter-control (tap0). The code path is mainline; no config needed
beyond an ether interface existing (else the ENOENT/random path runs, which
is correct behavior).

## Fix
`if_getanyethermac()` must not derive the MAC from `if_softc`; it should
read the link-level sockaddr that `if_attach`/`ether_ifattach` maintains
for every driver style. See fix.diff (3-line change). Note DF-2932's fix
(random node ids) additionally stops embedding the MAC at the uuid layer
and supersedes the *privacy* half; both are worth applying.

fix_status: not_testable — fix validation rebuild is mandated for
memory-corruption findings; this is a leak-class finding, and the guest was
kept clean instead (module unloaded, tap/vlan destroyed).
