# DF-2611 — icmp6_redirect_output TLLA option padding leak

## What this proves

`icmp6_redirect_output` builds the target-link-layer-address option with
`len = (2 + ifp->if_addrlen + 7) & ~7` (`icmp6.c:2496-2497`) but writes
only the 2-byte option header + `ifp->if_addrlen` address bytes
(`icmp6.c:2506-2511`). Nothing zeroes the pad region
`[2+if_addrlen, len)`, yet `p += len` and the final
`m->m_pkthdr.len = m->m_len = p - ip6` (`icmp6.c:2516`) TRANSMIT it. For
any interface whose link-layer address length is not 6, **1-7 bytes of
uninitialized mbuf-cluster heap are put on the wire per redirect**.

## Live reproduction (run.log, leak_sample.txt)

QEMU cannot emulate FireWire (the natural `if_addrlen=8` ND interface), so
the harness reproduces the finding's exact precondition by rewriting
tap0's `ifi_addrlen` 6 -> 8 through `/dev/kmem` (offsets validated against
three known interfaces first: vtnet0 type=6/addrlen=6/mtu=1500, lo0
type=24/addrlen=0/mtu=16384, tap0 type=6/addrlen=6/mtu=1500) — a
privileged stand-in for plugging in an EUI-64 interface; the leaked code
path (`bcopy` length, option rounding, checksum coverage) is identical.

Sequence per iteration: dirty the cluster cache with a recognizable UDP
pattern (`0xb0 | iter`), inject a transit Ethernet/IPv6/UDP frame into
tap0, capture the ND_REDIRECT the kernel emits.

**Results (35/35 redirects captured):**

- Control, stock tap0 (addrlen 6): TLLA option `02 01 | 6 MAC bytes`,
  option length 8 — **zero pad bytes**, exactly as the finding states for
  Ethernet.
- addrlen 8 + spray, 20/20 redirects: TLLA option length 16, bytes
  `[10,16)` = **6 bytes of the spray pattern**, e.g. iter 1
  `b1 b1 b1 b1 b1 b1`, iter 2 `b2 b2 ...`, iter 3 `b3 ...` — the pad is
  the immediately-freed cluster's contents: attacker-influenced stale
  heap memory, checksummed and transmitted.
- addrlen 8, **no spray**, 10/10 redirects: first 4 leaked `00 00 00 00
  00 00` (naturally clean cluster), then 6 leaked `b5 b5 b5 b5 b5 b5` —
  the pattern from the *previous, already-finished* spray run, still
  resident in the cluster cache. This decouples the leak from the spray
  mechanism itself: the pad bytes are whatever the heap last held,
  including fragments of unrelated previously-processed packets.

Quantification: 6 pad bytes per redirect for addrlen=8 (1-7 for other
non-6 addrlens); 100% of redirects leak (20/20 sprayed, 10/10 unsprayed);
variability across runs: pattern tracks heap history (b1..b4f sprayed,
b5 stale, 00 virgin) — a byte-for-byte kernel-heap disclosure primitive
on the wire, passive to any on-link sniffer.

## Preconditions (as filed)

Router role (`net.inet6.ip6.forwarding=1`, `net.inet6.ip6.redirect=1`
default), transit traffic redirected out an interface with
`if_addrlen != 6` and a resolved next-hop. The harness creates all of
this with tap0 + static ND entries + a scoped gateway route
(`route add ... fe80::42:1%tap0` — note: an *unscoped* link-local gateway
sockaddr makes `rt_llroute()`'s `rtlookup(rt_gateway)` miss and the
forward fails with EHOSTUNREACH instead of emitting a redirect).

## Files

- `tap_rdr.c`     full harness: tap owner, setup, spray, inject, capture,
                  TLLA-pad parser (`trigger N [nospray] [patch8]`)
- `kmem_addrlen.c` ifnet walker + validated `ifi_addrlen` rewriter
- `setup.sh`      superseded by tap_rdr's in-process setup (tap0 is
                  destroy-on-close; kept for the record)
- `build.sh`, `run.sh`, `run.log`, `leak_sample.txt`, `env.txt`, `fix.diff`

## Fix

`fix.diff` zeroes `len - sizeof(nd_opt) - if_addrlen` bytes after the
`bcopy` (`icmp6.c:2510`). Kernel-rebuild validation was not performed
(info-leak class, not memory corruption — the mandatory-rebuild rule does
not apply); the change is a mechanical 5-line bzero mirroring DF-0329's
verified fix for the redirected-header-option site.
