# DF-5795 — m_move_pkthdr() does not clear M_PKTHDR on the source mbuf

## What

`m_move_pkthdr()` (sys/kern/uipc_mbuf.c:2551-2558) moves the packet header
from `from` to `to` but never clears the `M_PKTHDR` flag on `from` — and
leaves `from`'s `m_pkthdr` content (stale `len`, `rcvif`, `csum_*`, `pf`,
emptied `tags`) fully intact.  Its documented contract in sys/sys/mbuf.h
(lines 405-410) states: *"from should have M_PKTHDR set, and to must be
empty.  from no longer has a pkthdr after this operation."*  FreeBSD's
`M_MOVE_PKTHDR` macro, from which this API is derived, does
`(from)->m_flags &= ~M_PKTHDR`.

The in-tree callers were written to the documented (FreeBSD) semantics —
e.g. icmp6.c:556 saves `n0len = n0->m_pkthdr.len` **before** the move
"for use below", precisely because the move is supposed to invalidate the
source's pkthdr.

## Result

After every `M_MOVE_PKTHDR` in the tree, a mid-chain mbuf keeps a live,
wrong packet header:

* the moved-from mbuf claims `M_PKTHDR` with a **stale total length**
  (`m_adj()` on it mutates that stale header — reproduced: 30 → 22),
* stale `rcvif`, `csum_*`, `fw_flags`, `pf` metadata rides along until
  the mbuf is freed,
* remote-reachable producers: icmp6.c:559 (ICMPv6 echo reflect — any
  ping6), ip6_input.c:255, ip6_output.c:3072 (ip6_splithdr),
  if_stge.c:1535, ieee80211_output.c:1271, ieee80211_dragonfly.c:643.

No in-tree sink turns the stale header into memory corruption (all
pkthdr readers use the chain head), so this is a kernel-state/contract
bug, not an exploitable primitive on its own — but it silently breaks
the invariant every consumer and every auditor (and FreeBSD) assumes.

## Build / run / expected (guest, root)

```
cd /tmp/df5795 && sh build.sh      # builds mbuf_probe.ko via bsd.kmod.mk
sh run.sh                          # kldload, kldunload, dmesg tail
```

Expected decisive lines in dmesg:

```
DF5795: after  move : from.flags=00008002 ... M_PKTHDR-on-from=1
DF5795: REPRODUCED: source mbuf still carries M_PKTHDR after M_MOVE_PKTHDR
DF5795: m_adj(from, 8) mutated the 'moved' pkthdr.len: 30 -> 22 (MUTATED)
```

With the one-line fix the first two lines would report
`M_PKTHDR-on-from=0` / `NOT REPRODUCED`, and `m_adj` would leave the
(no-longer-existent) header untouched.

## Fix

`fix.diff` (single functional line): clear `M_PKTHDR` on `from`.
Deliberately does **not** touch `from`'s `M_EXT`/data pointer: DF callers
(m_unshare uipc_mbuf.c:2037, m_prepend:1513) rely on `from` keeping its
cluster attachment — the comment in m_unshare claiming M_MOVE_PKTHDR
"will smash the data pointer and drop the M_EXT marker" describes the
old FreeBSD macro and is wrong for DF's function; the diff corrects that
comment too.
