# DF-0598 — leak sample (code-level proof harness output) # Source: ./run.sh 10 and ./run.sh 1000 on the unpatched #0 guest. # The harness replicates smb_sm_lookupint (sys/netproto/smb/smb_conn.c:123-180) # line-for-line in userspace. Each failing lookup leaks exactly one VC ref # onto the LAST VC visited by SMBCO_FOREACH (the stale post-loop `vcp`). ================ run.sh 10 (10 failing lookups) ================ === VULNERABLE smb_sm_lookupint (sys/netproto/smb/smb_conn.c:123-180) === call #0: rc=1 (nonzero => lookup FAILED), but *vcpp=alice@a (NOT NULL!) after 10 failed lookups: usecount[alice@a] = 11 (baseline 1, delta +10) <-- +10 leaked refs usecount[bob@b] = 1 (baseline 1, delta +0) usecount[carol@c] = 1 (baseline 1, delta +0) TOTAL leaked refs across all VCs = 10 (expected 10) every miss returned *vcpp=alice@a (WRONG VC) === FIXED smb_sm_lookupint (fix.diff applied) === call #0: rc=1 (nonzero => lookup FAILED), *vcpp=(null) — correct after 10 failed lookups: usecount[alice@a] = 1 (baseline 1, delta +0) <-- 0 leaked refs usecount[bob@b] = 1 (baseline 1, delta +0) usecount[carol@c] = 1 (baseline 1, delta +0) TOTAL leaked refs across all VCs = 0 (expected 0) ================ run.sh 1000 (1000 failing lookups) ================ === VULNERABLE smb_sm_lookupint === after 1000 failed lookups: usecount[alice@a] = 1001 (baseline 1, delta +1000) <-- linear, unbounded usecount[bob@b] = 1 (baseline 1, delta +0) usecount[carol@c] = 1 (baseline 1, delta +0) TOTAL leaked refs across all VCs = 1000 (expected 1000) === FIXED smb_sm_lookupint === after 1000 failed lookups: TOTAL leaked refs across all VCs = 0 (expected 0) ================ INTERPRETATION ================ The leak is deterministic and linear: N failing lookups → N leaked refs, all pinned onto ONE VC (the last one SMBCO_FOREACH visited before the list exhausted). That VC's co_usecount grows without bound and can never return to 0, so smb_co_gone()'s drain loop (smb_conn.c:263-265 `while (co_usecount > 0) tsleep(...)`) hangs the closing thread forever. The fix (clearing vcp on every unlock/continue path + an `error==0` guard on the post-loop ref block) produces exactly 0 leaked refs at any scale.