โฌข DragonFlyBSD Kernel Audit
DF-0754 / run.log
โ† back to finding โ†“ download raw
=== 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