DF-0754 / run.log
=== Building unpatched harness (proves the bug) ===
BUILD: harness exit=0
=== Building fixed harness (proves the fix) ===
BUILD: harness_fixed exit=0
=== All builds done ===
-rwxr-xr-x 1 maxx maxx 15344 Jul 5 08:36 harness
-rwxr-xr-x 1 maxx maxx 14072 Jul 5 08:36 harness_fixed
============================================================
DF-0754 โ mpls_output() by-value bug (manifestations 1 & 2)
M1: mpls_output_process() error-path m_freem(stale) DOUBLE-FREE/leak
M2: ip_output() success-path if_output(stale) stale/UAF-to-driver
(M3 = mpls_forward() = DF-0753, already verified)
============================================================
>>> UNPATCHED harness (transcribes kernel code verbatim):
mpls_output(struct mbuf *m, ...) โ m by VALUE
./harness
------------------------------------------------------------
================================================================
DF-0754 harness โ mpls_output() by-value bug
Focus: mpls_output_process() [M1] + ip_output() [M2] callers
(DF-0753 already covered the mpls_forward()/mpls_input.c caller)
================================================================
----------------------------------------------------------------
M1a: PUSH + m_prepend OOM -> mpls_output_process:145 DOUBLE-FREE
(mpls_output.c:143 by-value, :145 m_freem on already-freed m)
----------------------------------------------------------------
ip_output: m=0x8004a12c0 label=101 (head)
[inject] m_prepend: alloc OOM -> m_freem(m=0x8004a12c0 label=101) + return NULL
mpls_output_process: error=105 -> m_freem(stale m=0x8004a12c0 label=101)
[DOUBLE-FREE] m_freem on already-freed mbuf 0x8004a12c0 (label=101) (magic=0xdeadc0de)
ip_output: mpls_output_process=FALSE -> goto done (m already freed at :145)
--- M1a result ---
allocs=1 frees=1 outstanding(live)=0 double_free=1 uaf=0
if_output derefs=0 last_received_label=-1
EXPECT: double_free >= 1 (mpls_output_process:145 frees freed m)
*** MANIFESTATION 1 (DOUBLE-FREE) CONFIRMED ***
----------------------------------------------------------------
M1b: SWAP + m_pullup OOM -> mpls_output_process:145 DOUBLE-FREE
(mpls_swap takes m by value; m_pullup frees old m on OOM)
----------------------------------------------------------------
ip_output: m=0x8004a1230 label=103 (head)
[inject] m_pullup: alloc OOM -> m_freem(n=0x8004a1230 label=103) + return NULL
mpls_output_process: error=105 -> m_freem(stale m=0x8004a1230 label=103)
[DOUBLE-FREE] m_freem on already-freed mbuf 0x8004a1230 (label=103) (magic=0xdeadc0de)
[DOUBLE-FREE] m_freem on already-freed mbuf 0x8004a11a0 (label=102) (magic=0xdeadc0de)
ip_output: mpls_output_process=FALSE -> goto done (m already freed at :145)
--- M1b result ---
allocs=2 frees=2 outstanding(live)=0 double_free=2 uaf=0
if_output derefs=0 last_received_label=-1
EXPECT: double_free >= 1 (stale m freed by m_pullup, re-freed at :145)
*** MANIFESTATION 1 (DOUBLE-FREE) CONFIRMED ***
----------------------------------------------------------------
M1c: PUSH ok then later op ENOTSUP -> stale head freed, NEW head LEAKED
(mpls_output_process:145 frees stale m; new pushed head unreachable)
----------------------------------------------------------------
ip_output: m=0x8004a1110 label=104 (head)
mpls_push: new head m=0x8004a1080 label=105
mpls_output_process: error=91 -> m_freem(stale m=0x8004a1110 label=104)
ip_output: mpls_output_process=FALSE -> goto done (m already freed at :145)
--- M1c result ---
allocs=2 frees=1 outstanding(live)=1 double_free=0 uaf=0
if_output derefs=0 last_received_label=-1
EXPECT: double_free=0 (old chain freed once), live_allocs >= 1 (new pushed head LEAKED)
*** MBUF LEAK: 1 outstanding alloc(s) not freed ***
----------------------------------------------------------------
M2a: PUSH realloc SUCCESS -> ip_output:698 if_output(STALE m)
(driver gets demoted old head; new pushed head leaked)
----------------------------------------------------------------
ip_output: m=0x8004a0ff0 label=106 (head)
mpls_push: new head m=0x8004a0f60 label=107
ip_output: mpls_output_process=TRUE -> ifp->if_output(stale m=0x8004a0ff0)
if_output: received m=0x8004a0ff0 label=106
if_output: m LIVE, m_len=20 pkthdr.len=20 M_PKTHDR=1 M_MPLSLABELED=0
--- M2a result ---
allocs=2 frees=1 outstanding(live)=1 double_free=0 uaf=0
if_output derefs=1 last_received_label=106
EXPECT: if_output_received_label == original IP mbuf label (STALE), live_allocs >= 1 (new head leaked)
*** MBUF LEAK: 1 outstanding alloc(s) not freed ***
----------------------------------------------------------------
M2b: SWAP m_pullup realloc SUCCESS -> ip_output:698 if_output(FREED m)
(stale m is freed memory; driver deref = UAF)
----------------------------------------------------------------
ip_output: m=0x8004a0ed0 label=109 (head)
ip_output: mpls_output_process=TRUE -> ifp->if_output(stale m=0x8004a0ed0)
if_output: received m=0x8004a0ed0 label=109
[UAF] deref of freed mbuf 0x8004a0ed0 (label=109) at if_output(m) deref (magic=0xdeadc0de)
--- M2b result ---
allocs=3 frees=2 outstanding(live)=1 double_free=0 uaf=1
if_output derefs=1 last_received_label=-1
EXPECT: uaf >= 1 (driver deref of freed m at if_output)
*** UAF CONFIRMED ***
*** MBUF LEAK: 1 outstanding alloc(s) not freed ***
----------------------------------------------------------------
CONTROL: PUSH with leading_space=14 -> fast path, NO realloc, NO bug
----------------------------------------------------------------
ip_output: m=0x8004a0d20 label=110 (head)
mpls_push: new head m=0x8004a0d20 label=111
ip_output: mpls_output_process=TRUE -> ifp->if_output(stale m=0x8004a0d20)
if_output: received m=0x8004a0d20 label=111
if_output: m LIVE, m_len=24 pkthdr.len=24 M_PKTHDR=1 M_MPLSLABELED=1
--- CONTROL result ---
allocs=1 frees=1 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=1 last_received_label=111
EXPECT: double_free=0, uaf=0, live_allocs=0 (clean)
================================================================
DF-0754 harness complete.
Root cause: mpls_output() takes `struct mbuf *m` BY VALUE
(mpls_output.c:50). Callers mpls_output_process() (:143) and
ip_output() (:695) never see the new head after a realloc.
M1: error path m_freem(stale) at :145 = DOUBLE-FREE / leak.
M2: success path if_output(stale) at ip_output.c:698 = stale/UAF.
Fix: mpls_output(struct mbuf **mp) propagates head through *mp
(same fix as DF-0753 โ closes BOTH findings).
============================================================
>>> FIXED harness (mpls_output takes struct mbuf **mp):
./harness_fixed
------------------------------------------------------------
================================================================
DF-0754 FIXED harness โ mpls_output(struct mbuf **mp)
All M1/M2 scenarios should PASS (zero double-free/UAF/leak).
================================================================
----------------------------------------------------------------
M1a (fixed): PUSH + m_prepend OOM
----------------------------------------------------------------
ip_output: m=0x8004a12c0 label=101 (head)
[inject] m_prepend: alloc OOM -> m_freem(m=0x8004a12c0 label=101) + return NULL
mpls_output_process: error=105 -> m_freem(*mp=0x0 label=-1)
ip_output: mpls_output_process=FALSE -> goto done
--- M1a fixed ---
allocs=1 frees=1 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=0 last_received_label=-1
EXPECT (fixed): double_free=0, uaf=0, live_allocs=0 (no stale deref)
*** PASS โ fix eliminates the manifestation ***
----------------------------------------------------------------
M1b (fixed): SWAP + m_pullup OOM
----------------------------------------------------------------
ip_output: m=0x8004a1230 label=103 (head)
[inject] m_pullup: alloc OOM -> m_freem(n=0x8004a1230 label=103) + return NULL
mpls_output_process: error=105 -> m_freem(*mp=0x0 label=-1)
ip_output: mpls_output_process=FALSE -> goto done
--- M1b fixed ---
allocs=2 frees=2 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=0 last_received_label=-1
EXPECT (fixed): double_free=0, uaf=0, live_allocs=0
*** PASS โ fix eliminates the manifestation ***
----------------------------------------------------------------
M1c (fixed): PUSH ok then SWAP TTL-expired
----------------------------------------------------------------
ip_output: m=0x8004a1110 label=104 (head)
mpls_push: new head m=0x8004a1080 label=105
ip_output: mpls_output_process=TRUE -> ifp->if_output(m=0x8004a1080)
if_output: received m=0x8004a1080 label=105
if_output: m LIVE, m_len=4 pkthdr.len=24 M_PKTHDR=1 M_MPLSLABELED=1
--- M1c fixed ---
allocs=2 frees=2 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=1 last_received_label=105
EXPECT (fixed): double_free=0, live_allocs=0 (new head freed, not leaked)
*** PASS โ fix eliminates the manifestation ***
----------------------------------------------------------------
M2a (fixed): PUSH realloc SUCCESS
----------------------------------------------------------------
ip_output: m=0x8004a0ff0 label=106 (head)
mpls_push: new head m=0x8004a0f60 label=107
ip_output: mpls_output_process=TRUE -> ifp->if_output(m=0x8004a0f60)
if_output: received m=0x8004a0f60 label=107
if_output: m LIVE, m_len=4 pkthdr.len=24 M_PKTHDR=1 M_MPLSLABELED=1
--- M2a fixed ---
allocs=2 frees=2 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=1 last_received_label=107
EXPECT (fixed): if_output_received_label == pushed head label (CORRECT), live_allocs=0
*** PASS โ fix eliminates the manifestation ***
----------------------------------------------------------------
M2b (fixed): SWAP m_pullup realloc SUCCESS
----------------------------------------------------------------
ip_output: m=0x8004a0ed0 label=109 (head)
ip_output: mpls_output_process=TRUE -> ifp->if_output(m=0x8004a0db0)
if_output: received m=0x8004a0db0 label=0
if_output: m LIVE, m_len=4 pkthdr.len=34 M_PKTHDR=1 M_MPLSLABELED=1
--- M2b fixed ---
allocs=3 frees=3 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=1 last_received_label=0
EXPECT (fixed): uaf=0, live_allocs=0 (driver gets the new head)
*** PASS โ fix eliminates the manifestation ***
----------------------------------------------------------------
CONTROL (fixed): PUSH leading_space=14
----------------------------------------------------------------
ip_output: m=0x8004a0d20 label=110 (head)
mpls_push: new head m=0x8004a0d20 label=111
ip_output: mpls_output_process=TRUE -> ifp->if_output(m=0x8004a0d20)
if_output: received m=0x8004a0d20 label=111
if_output: m LIVE, m_len=24 pkthdr.len=24 M_PKTHDR=1 M_MPLSLABELED=1
--- CONTROL fixed ---
allocs=1 frees=1 outstanding(live)=0 double_free=0 uaf=0
if_output derefs=1 last_received_label=111
EXPECT (fixed): clean (unchanged behavior)
*** PASS โ fix eliminates the manifestation ***
================================================================
DF-0754 FIXED harness complete.
All manifestations eliminated by mpls_output(struct mbuf **mp).
============================================================
Contrast: UNPATCHED shows DOUBLE-FREE/LEAK/UAF in M1a/M1b/M1c/M2a/M2b;
FIXED shows ALL PASS (zero double-free, zero UAF, zero leak).
============================================================
RUN_EXIT=0