⬢ DragonFlyBSD Kernel Audit
DF-3637 / verify_df3637.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""DF-3637 source-level verification.

Asserts the exact control-flow defect in sys/dev/netif/ath/ath/if_ath_tx.c:
ath_tx_start() discards the error from ath_tx_normal_setup() and returns 0,
leaking the ath_buf (10 descriptors + dmamap) after the mbuf was freed inside
ath_tx_dmasetup().
"""
import sys, re

SRC = "sys/dev/netif/ath/ath/if_ath_tx.c"
src = open(SRC).read().splitlines()

def find(pat, start=0):
    for i in range(start, len(src)):
        if re.search(pat, src[i]):
            return i
    return -1

ok = True
def check(name, cond, detail=""):
    global ok
    print("[%s] %s%s" % ("PASS" if cond else "FAIL", name,
                         (" -- " + detail) if detail else ""))
    if not cond:
        ok = False

# 1. r = ath_tx_normal_setup(...) ; if (r != 0) goto done;
i_call = find(r"r = ath_tx_normal_setup\(")
check("ath_tx_start calls ath_tx_normal_setup into r", i_call != -1,
      "line %d" % (i_call + 1))
i_chk = find(r"if \(r != 0\)", i_call)
nxt = src[i_chk + 1].strip() if i_chk != -1 else ""
check("failure branch jumps to done", i_chk != -1 and nxt == "goto done;",
      "line %d: %s / %s" % (i_chk + 1, src[i_chk].strip(), nxt))

# 2. done: return 0;  (error swallowed)
i_done = find(r"^done:$", i_chk)
check("done: label exists after failure branch", i_done != -1,
      "line %d" % (i_done + 1))
ret = "\n".join(src[i_done:i_done + 4])
check("done: block returns literal 0 (r discarded)", "return 0;" in ret,
      "lines %d-%d" % (i_done + 1, i_done + 4))

# 3. ath_tx_dmasetup frees the mbuf on every error return
i_ds = find(r"^ath_tx_dmasetup\(")
seg = "\n".join(src[i_ds:i_ds + 80])
check("dmasetup error path frees mbuf before returning error",
      "ieee80211_free_mbuf(m0);" in seg and "return error;" in seg,
      "function at line %d" % (i_ds + 1))

# 4. caller treats 0 as success (ath_raw_xmit legacy path in same file)
i_rx = find(r"if \(ath_tx_start\(sc, ni, bf, m\)\) \{")
check("caller ath_raw_xmit branches on non-zero return",
      i_rx != -1, "line %d" % (i_rx + 1))

# 5. bf is obtained BEFORE and only returned on the error path the caller sees
i_gb = find(r"bf = ath_getbuf\(sc, ATH_BUFTYPE_MGMT\);")
check("ath_raw_xmit grabs bf via ath_getbuf before ath_tx_start",
      i_gb != -1 and i_gb < i_rx, "line %d" % (i_gb + 1))
seg2 = "\n".join(src[i_rx:i_rx + 10])
check("success path never returns bf (no ath_returnbuf on r==0)",
      "ath_returnbuf" not in seg2, "lines %d-%d" % (i_rx + 1, i_rx + 10))

print()
print("DF-3637 CONFIRMED at source level" if ok else "DF-3637 NOT confirmed")
sys.exit(0 if ok else 1)