β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4084

sis_stop uses callout_stop (which does not interlock against an in-progress callback) while sis_tick re-arms itself as its last serialized action: timer resurrects after stop; on detach the late tick dereferences freed softc/miibus/bus resources (use-after-free) β€” DF-3715 callout-discipline family

Summary

kern_timeout documents callout_stop as non-interlocking against an in-progress callback. sis_tick takes if_serializer then unconditionally callout_reset re-arms before releasing it; every sis_stop caller holds that same serializer, so a tick that fired and blocked during sis_stop completes afterwards and re-arms the 'stopped' timer. On the detach path the resurrected/late tick enters the freed ifnet's serializer, dereferences the deleted miibus child and released bus resources -> UAF panic. Also leaves a stray 1 Hz tick after plain ifconfig down. oce uses the correct callout_terminate; sis does not. callout_terminate fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4084 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 728 B view raw

DF-4084 β€” sis_stop() uses non-blocking callout_stop while sis_tick re-arms:

timer resurrects after stop; on detach, sis_tick runs against freed

softc/miibus/resources (use-after-free) β€” DF-3715 callout-discipline family

File: sys/dev/netif/sis/if_sis.c (pass 2 β€” first pass over this file) Class: callout/lifetime race (privileged detach β†’ UAF)

Root cause (path:line)

  • if_sis.c:1123: callout_init(&sc->sis_timer); (plain init)
  • if_sis.c:1477-1500: sis_tick callback:
  • 1484 lwkt_serialize_enter(ifp->if_serializer); … work …
  • 1498 callout_reset(&sc->sis_timer, hz, sis_tick, sc); … 1499 exit.
  • if_sis.c:2033: sis_stop(): callout_stop(&sc->sis_timer);
  • sys/kern/kern_timeout.c:1086-1088 (API contract): "callout_stop … does not interlock against a callout that is in-progress."

Sequence: sis_tick fires on CPU1 and blocks acquiring if_serializer, which a thread doing sis_stop (ifconfig down via sis_ioctl:1974-1980, watchdog via sis_watchdog:2013, or sis_detach:1203-1208) holds. callout_stop returns without stopping the in-progress callback. The stopper releases the serializer; sis_tick then completes and β€” at line 1498 β€” callout_reset re-arms the timer after stop. The callout is resurrected.

Consequences:

  1. ifconfig down / sis_init cycle: timer keeps ticking against a stopped chip (mii_tick on a halted PHY) β€” incorrect but mostly benign.
  2. sis_detach (devctl detach / kldunload if_sis): after sis_stop, detach tears down bus_teardown_intr (1207), ether_ifdetach (1210), device_delete_child(miibus) (1213), releases sis_irq/sis_res (1216-1219) and sis_dma_free (1221); the softc is freed when the driver detaches. The resurrected/late sis_tick then executes: enters the freed ifnet's serializer, device_get_softc(sc->sis_miibus) on the deleted child, CSR reads on released resources β†’ use-after-free β†’ panic or worse.

Contrast with the correct pattern used by oce in the same commit family: callout_terminate(&sc->timer) (sys/dev/netif/oce/oce_if.c:331), which cancels and waits.

Fix

In sis_stop (or sis_detach, before releasing resources):

callout_terminate(&sc->sis_timer);   /* instead of callout_stop */

(callout_terminate, kern_timeout.c:1104-1121, synchronously cancels and prevents resurrection.)

Verification status

UNTESTED β€” hardware-gated: the sis_tick path needs the SiS/DP83815 NIC (mii_tick on sc->sis_miibus); QEMU has no model (guest pciconf shows no 0x1039/0x100b devices). The callout_stop contract violation is source-certain.

VERDICT.md
↓ download raw

DF-4084 VERDICT β€” sis_stop callout resurrection / post-detach UAF

Status: UNTESTED (hardware-gated; mechanism source-certain)

No SiS/DP83815 device exists in the QEMU guest (pciconf: no 0x1039/0x100b), so sis_tick cannot be made to fire; the race cannot be staged end-to-end on the available system.

Why the mechanism is certain from source

  1. sys/kern/kern_timeout.c:1080-1094: callout_stop is explicitly documented "does not interlock against a callout that is in-progress" β€” it cannot stop a callback that has already fired and is blocked on the serializer.
  2. if_sis.c:1484/1498-1499: sis_tick takes if_serializer, then re-arms with callout_reset as its last action before releasing it.
  3. if_sis.c:2033: sis_stop only does callout_stop. Every sis_stop caller (sis_ioctl:1974-1980, sis_watchdog:2013, sis_init:1769, sis_detach:1206) holds if_serializer β€” exactly the lock the in-flight tick is waiting on, guaranteeing the resurrection window whenever stop races a firing tick.
  4. sis_detach (1196-1224) then frees the miibus child (1213), the IRQ and register resources (1216-1219) and DMA state (1221) β€” a late/resurrected tick dereferences all of them (1484-1495) β†’ UAF.
  5. The sibling driver oce uses callout_terminate for exactly this reason (sys/dev/netif/oce/oce_if.c:331); sis predates the discipline (callout code even carries a DF-3715-era comment about lock-aided callouts at kern_timeout.c:1080-1082).

Trigger: privileged (devctl detach / kldunload if_sis / reboot-time sis_shutdown) racing the 1 Hz tick. Probability per attempt is small (microseconds-scale window per second) but the action repeats across up/down cycles (ifconfig down leaves the timer alive too, enlarging later windows).

Fix

callout_terminate(&sc->sis_timer);  in sis_stop (replaces callout_stop)

β€” synchronous cancel; no resurrection; safe because sis_stop runs under the same serializer the tick needs, so no callback can be executing when it returns.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix validation not possible: no SiS hardware/emulation in QEMU.

Guest pciconf -l: no vendor 0x1039/0x100b devices.
per-fix-DF-4084

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md, VERDICT.md. Key lines: if_sis.c:1123 (callout_init), 1484/1498 (tick serialize + re-arm), 2033 (callout_stop), 1196-1224 (detach frees), kern_timeout.c:1087-1088 (non-interlocking stop), oce_if.c:331 (correct callout_terminate pattern).

PoC changes

No runnable PoC: hardware-gated (sis_tick requires the SiS/DP83815 PHY). Mechanism documented at source level.

Verified recommended fix

Replace callout_stop(&sc->sis_timer) with callout_terminate(&sc->sis_timer) in sis_stop.

Verdict

sis_stop uses callout_stop (if_sis.c:2033), which per kern_timeout.c:1087 'does not interlock against a callout that is in-progress', while sis_tick unconditionally re-arms itself with callout_reset as its last serialized action (if_sis.c:1498). A tick blocked on if_serializer during sis_stop completes afterwards and resurrects the timer; in the detach path (if_sis.c:1196-1224) that late tick dereferences the freed ifnet serializer, deleted miibus child, and released bus resources β€” use-after-free panic. Mechanism is source-certain (stop-vs-rearm contract), trigger is privileged (detach/kldunload) plus timing. Untested: QEMU has no SiS NIC.