# DF-0754 — `mpls_output()` by-value bug (manifestations 1 & 2)

**Status:** REPRODUCED (deterministic harness) — FIX VALIDATED
**Impact:** panic / DoS (double-free, UAF, mbuf leak)
**Confidence:** high
**Root cause:** `mpls_output(struct mbuf *m, ...)` takes `m` **by value**
(`mpls_output.c:50`). When `mpls_push`/`mpls_swap`/`mpls_pop` reallocate the
head mbuf (via `M_PREPEND`/`m_pullup`), the rebound local never escapes —
callers `mpls_output_process` (`:143`) and `ip_output` (`:695`) hold a **stale
pointer**. Same root cause as **DF-0753** (manifestation 3).

## Manifestations covered by THIS finding (DF-0754)

| # | Caller | Path | Effect |
|---|--------|------|--------|
| **M1** | `mpls_output_process` | `mpls_output.c:143-146` | error-path `m_freem(stale m)` → **double-free** (OOM) or **new-head leak** (push-then-error) |
| **M2** | `ip_output` | `ip_output.c:698` / `:742` | success-path `if_output(stale m)` → **stale/demoted mbuf to driver** (PUSH) or **UAF** (SWAP/POP via `m_pullup`) |

(M3 = `mpls_forward` in `mpls_input.c` = **DF-0753**, verified separately.)

## How to reproduce

```sh
./build.sh    # builds harness (unpatched proof) + harness_fixed (fix proof)
./run.sh      # runs both: unpatched shows all 5 manifestations, fixed shows all PASS
```

The harness transcribes `mpls_output`/`mpls_push`/`mpls_swap`/`mpls_pop`/
`mpls_output_process` + the `ip_output` MPLS dispatch + `m_prepend`/`m_pullup`
**verbatim** from the kernel, with a poisoned allocator (`0xdeadc0de`) and an
OOM injection knob. Five scenarios fire deterministically:

| Scenario | Condition | Unpatched result |
|---|---|---|
| M1a | PUSH + m_prepend OOM | **DOUBLE-FREE** (`:145`) |
| M1b | SWAP + m_pullup OOM | **DOUBLE-FREE** (`:145`) |
| M1c | PUSH ok, then unknown-op ENOTSUP | **NEW-HEAD LEAK** |
| M2a | PUSH realloc SUCCESS | **STALE-TO-DRIVER + LEAK** |
| M2b | SWAP m_pullup realloc SUCCESS | **UAF** (driver derefs freed m) |
| CTRL | PUSH, leading_space=14 (normal ether) | no realloc → no bug (control) |

## The fix

`fix.diff` (identical to DF-0753's root-cause fix): change `mpls_output` to
`struct mbuf **mp`, propagate the head through `*mp` at every rebind, and
update `mpls_swap`/`mpls_pop`/`mpls_output_process` + all callers. **This
single fix closes DF-0753 AND DF-0754.**

Validated by:
- Fixed harness: all 5 scenarios PASS (zero double-free/UAF/leak).
- Patched `mpls.ko` builds clean under `-Werror` (sha256 `72fb3810...`,
  byte-identical to DF-0753's patched module — deterministic).

## Exploitation ceiling

panic/DoS on GENERIC (INVARIANTS ON). The double-free is caught by the slab
allocator's `chunk_mark_free` check before grooming lands, and mbufs are
confined to the dedicated `mbuf_zone` slab (no cross-object grooming). These
are valid hard blockers; no escalation chain was developed. See `VERDICT.md`.
