DragonFlyBSD Kernel Audit
DF-0489 / run.log
← back to finding ↓ download raw
=== DF-0489 empirical test logs (BPF + netgraph injection attempts) ===
Guest: DragonFly 6.5-DEVELOPMENT #0 (unpatched audit kernel)
Test: inject Neighbor Advertisement packets to trigger nd6_na_input, then
      observe the route "Refs" column (netstat -rn -W -f inet6) for the
      target route. If the finding's leak claim were true, Refs would climb
      by 1 per NA processed.

--- Setup ---
Created a static neighbor entry for the fake target so nd6_na_input reaches
the nd6_lookup path (otherwise nd6_lookup returns NULL -> freeit):
  # ndp -s fe80::dcad:beff:feef:1%vtnet0 de:ad:be:ef:00:01
  -> fe80::dcad:beff:feef:1%vtnet0  de:ad:be:ef:00:01  UHLS  Refs=0

--- Attempt 1: BPF injection (poc_df0489.c) ---
BPF writes call ifp->if_output (sys/net/bpf.c:598) -- i.e. TX (output), NOT
RX (input). Injected 5000 NA frames; they went OUT the interface, never
reaching nd6_na_input. icmp6 Input histogram showed no neighbor-advert count.
Refs stayed at 0 throughout (no leak, but path not actually exercised):

  Refs BEFORE: fe80::dcad:beff:feef:1%vtnet0  UHLS  Refs=0
  [inject 5000 NAs via BPF write]
  Refs AFTER:  fe80::dcad:beff:feef:1%vtnet0  UHLS  Refs=0
  (icmp6 Input histogram unchanged -- confirms BPF write = TX, not RX)

--- Attempt 2: netgraph ng_ether RX injection (poc_df0489_ng.c) ---
Loaded ng_ether + ng_socket. Attempted to name the ng_socket node via
NGIOCSETNAME ioctl so it could be connected to vtnet0:lower (the RX injection
hook). The ioctl returned ENOTSUP ("Operation not supported") on this build;
ngctl connect then failed ("send msg: No such file or directory"); all data
writes returned -1. No packets injected.

--- Conclusion ---
Empirical RX injection was not achievable with the available guest mechanisms
(BPF is TX-only; netgraph socket naming ioctl unsupported). The DEFINITIVE
proof is the source trace below -- which is sufficient on its own to classify
this as a false positive, because it identifies the exact guard the reviewer
missed.

--- Definitive source trace ---
nd6_na_input (nd6_nbr.c:734):
    rt = nd6_lookup(&taddr6, 0, ifp);   /* create = 0 */

nd6_lookup (nd6.c:860) with create=0:
  line 873:  rt = rtpurelookup(...)   /* -> _rtlookup -> route.c:276: rt->rt_refcnt++ */
  line 929:  rt->rt_refcnt--;         /* UNCONDITIONAL, before any return of rt */
  line 940:  return (rt);             /* route returned with NET-ZERO ref change */

route.c:276 (++ inside _rtlookup) is exactly balanced by nd6.c:929 (--) inside
nd6_lookup. The route handed to nd6_na_input carries NO extra reference, so
there is nothing for nd6_na_input to free and NO refcount leak.

git blame confirms nd6.c:929 is in the same commit (6cc80ee9) as the rest of
the audited file -- it was not recently added; the reviewer simply missed it.

No other caller of nd6_lookup(addr, 0, ifp) in the tree calls rtfree on the
result (icmp6.c:2493, nd6.c:1137/1649/1710, nd6_rtr.c:603/1148) -- confirming
the convention that create=0 returns a route with no holding reference.