In the vif_started==0 branch rwnx_close() evaluates
usbdev->bus_if->state without checking usbdev. On the disconnect path
aicwf_bus_deinit() runs before netdev close, so close observes usbdev==NULL
and oopses (CR2=0x8, pinned by disassembly relocation rwnx_send_reset;
field symptom: host hard-hang on physical unplug, reproduced x8 in the lab
with this stack -> BUG=0 after this guard). In the unpatched tree the same
expression reads a still-allocated DOWN-state object in the synchronous
disconnect path — our earlier "latent UAF" phrasing is retracted (no
freed-object access was observed or proven). Covers USB and SDIO variants.
Review-driven rework of the teardown lifetime (shenmintao/aic8800d80#103):
- aicwf_bus_deinit(): keep the entry NULL guard (sequential double-deinit
on probe-failure paths), but move dev_set_drvdata(dev, NULL) to the END
of completed cleanup — after URB cancel, platform/rx/cmd teardown and the
kthread_stop() join of bustx_thread. The previous placement (right after
bus_stop) opened a real race: aicwf_usb_bus_txdata() re-reads drvdata and
runs from bustx_thread, which is joined only later, and the TX-path state
check is not a synchronization point. With the new ordering every cleanup
callback and TX reader observes a non-NULL pointer throughout.
- aicwf_usb_bus_stop(): NULL-check bus_if before dereferencing
bus_if->bus_priv.usb (defense in depth; historical host oops
RIP aicwf_usb_bus_stop+0x11 on probe-failure rollback).
Verification (isolated VFIO lab, one 2357:0147 unit, sustained ping -f):
a test-only fault-injection build (not included) reaches the repeated-deinit
path and prints "already deinitialized, skip" with BUG=0; missing-firmware
init failure runs one clean deinit; physical unplug x8, sysfs unbind and
authorized cold cycles, disconnect during scanning and during connection
establishment, ifdown/up x5, rmmod/insmod x3 -> BUG=0 throughout (only the
pre-existing WARNs at rwnx_main.c:1529 and rwnx_cfg80211_disconnect:3328).