Root Cause Analysis
Involved Functions, Structures, and Source Files
- Source Files:
- Data Structures and Fields:
- Functions:
Bug Mechanism and Lifecycle Invariant Violation
In the IEEE 802.15.4 hardware simulator driver (mac802154_hwsim), radio state such as channel, page, and filtering parameters is held in a dynamically allocated struct hwsim_pib object referenced by phy->pib. Driver callbacks such as .set_channel (hwsim_hw_channel()), .set_promiscuous_mode (hwsim_set_promiscuous_mode()), and .set_hw_addr_filt (hwsim_hw_addr_filt()) dereference phy->pib and call hwsim_update_pib() to allocate a new PIB object and retire the old one via kfree_rcu(pib_old, rcu).
Under the driver lifecycle model, driver-private data structures accessed by device callbacks must remain valid until the core stack has finished unregistering the hardware via ieee802154_unregister_hw(phy->hw).
When deleting a radio instance via HWSIM_CMD_DEL_RADIO (hwsim_del_radio_nl() -> hwsim_del()), hwsim_del() executes the following sequence:
static void hwsim_del(struct hwsim_phy *phy)
{
...
pib = rcu_dereference(phy->pib);
rcu_read_unlock();
kfree_rcu(pib, rcu);
ieee802154_unregister_hw(phy->hw);
ieee802154_free_hw(phy->hw);
}
- Premature Free:
hwsim_del() reads pib = rcu_dereference(phy->pib) and invokes kfree_rcu(pib, rcu) to enqueue the PIB object for RCU deferred freeing. Crucially, this is done before ieee802154_unregister_hw(phy->hw), and phy->pib is not updated or cleared.
- Synchronous Device Teardown Callback:
ieee802154_unregister_hw() proceeds to call ieee802154_remove_interfaces(), which invokes unregister_netdevice(). This forces running slave interfaces to close via mac802154_slave_close().
- Aborting In-Progress Scan: If a scan operation is active on the interface,
mac802154_slave_close() calls mac802154_abort_scan_locked() -> mac802154_scan_cleanup_locked(). To restore the radio's channel, mac802154_scan_cleanup_locked() calls drv_set_channel() -> hwsim_hw_channel().
- Second
kfree_rcu Invocation (Double Free): hwsim_hw_channel() reads phy->pib (which still points to the PIB object currently in the RCU grace-period queue) and invokes hwsim_update_pib(). Inside hwsim_update_pib():
pib_old = rcu_replace_pointer(phy->pib, pib,
lockdep_is_held(&phy->pib_lock));
spin_unlock_bh(&phy->pib_lock);
kfree_rcu(pib_old, rcu);
pib_old is the exact same pointer passed to kfree_rcu() in step 1. Calling kfree_rcu() / kvfree_call_rcu() on the already-queued object triggers KASAN's double-free detection in kfree_rcu_sheaf / kvfree_call_rcu.