summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-09-04 15:16:28 -0700
committerJakub Kicinski <kuba@kernel.org>2026-09-04 15:16:28 -0700
commitfb3088dc5811a27dcc4775eaf44bd85ade265762 (patch)
tree70deccfec5d9f8a2eac1624f248f61860882d630
parent641d03105cc0d2437e32fdeec164f91a4ccef6c4 (diff)
parent2f37fba846c9fdff5fc15b6d93656057ccd13031 (diff)
downloadlinux-next-fb3088dc5811a27dcc4775eaf44bd85ade265762.tar.gz
linux-next-fb3088dc5811a27dcc4775eaf44bd85ade265762.zip
Merge tag 'ieee802154-for-net-2026-09-03' of git://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan
Stefan Schmidt says: ==================== pull-request: ieee802154 for net 2026-09-03 Zhiling Zou fixed a NULL deref when coming from a TUN device. Fan Wu fixed a UAF in the cc2520 driver. Chenguang Zhao fixed up some out of date comments in 6lowpan. David Carlier fixed a potential double free in the hwsim driver. Ibrahim Hashimov reworked the queuing in the RX path to fix a UAF on beacon and MAC frames. * tag 'ieee802154-for-net-2026-09-03' of git://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan: mac802154: fix use-after-free of sdata via queued RX frames ieee802154: hwsim: serialize pib updates to fix double-free ieee802154: 6lowpan: fix NULL dereference in lowpan_newlink ieee802154: cc2520: fix FIFOP work use-after-free net: 6lowpan: fix mismatched comments ==================== Link: https://patch.msgid.link/20260903093012.4032586-1-stefan@datenfreihafen.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/ieee802154/cc2520.c5
-rw-r--r--drivers/net/ieee802154/mac802154_hwsim.c10
-rw-r--r--include/net/cfg802154.h1
-rw-r--r--net/6lowpan/nhc.h3
-rw-r--r--net/ieee802154/6lowpan/core.c2
-rw-r--r--net/mac802154/ieee802154_i.h8
-rw-r--r--net/mac802154/iface.c6
-rw-r--r--net/mac802154/main.c1
-rw-r--r--net/mac802154/rx.c120
-rw-r--r--net/mac802154/scan.c10
10 files changed, 128 insertions, 38 deletions
diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c
index 2b7034193a00..abfcfe07246a 100644
--- a/drivers/net/ieee802154/cc2520.c
+++ b/drivers/net/ieee802154/cc2520.c
@@ -1156,11 +1156,10 @@ static void cc2520_remove(struct spi_device *spi)
{
struct cc2520_private *priv = spi_get_drvdata(spi);
- mutex_destroy(&priv->buffer_mutex);
- flush_work(&priv->fifop_irqwork);
-
+ disable_work_sync(&priv->fifop_irqwork);
ieee802154_unregister_hw(priv->hw);
ieee802154_free_hw(priv->hw);
+ mutex_destroy(&priv->buffer_mutex);
}
static const struct spi_device_id cc2520_ids[] = {
diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c
index 6daa0f198b9f..a9bd1555d2dc 100644
--- a/drivers/net/ieee802154/mac802154_hwsim.c
+++ b/drivers/net/ieee802154/mac802154_hwsim.c
@@ -72,6 +72,8 @@ struct hwsim_phy {
struct ieee802154_hw *hw;
u32 idx;
+ /* Serializes phy->pib_updates. */
+ spinlock_t pib_lock;
struct hwsim_pib __rcu *pib;
bool suspended;
@@ -102,8 +104,6 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel,
if (!pib)
return -ENOMEM;
- pib_old = rtnl_dereference(phy->pib);
-
pib->page = page;
pib->channel = channel;
pib->filt.short_addr = filt->short_addr;
@@ -112,7 +112,10 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel,
pib->filt.pan_coord = filt->pan_coord;
pib->filt_level = filt_level;
- rcu_assign_pointer(phy->pib, pib);
+ spin_lock_bh(&phy->pib_lock);
+ 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);
return 0;
}
@@ -952,6 +955,7 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev,
goto err_pib;
}
+ spin_lock_init(&phy->pib_lock);
pib->channel = 13;
pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
diff --git a/include/net/cfg802154.h b/include/net/cfg802154.h
index 76d2cd2e2b30..2e960441ea49 100644
--- a/include/net/cfg802154.h
+++ b/include/net/cfg802154.h
@@ -376,6 +376,7 @@ struct cfg802154_mac_pkt {
struct list_head node;
struct sk_buff *skb;
struct ieee802154_sub_if_data *sdata;
+ netdevice_tracker dev_tracker;
u8 page;
u8 channel;
};
diff --git a/net/6lowpan/nhc.h b/net/6lowpan/nhc.h
index ab7b4977c32b..c995029696d0 100644
--- a/net/6lowpan/nhc.h
+++ b/net/6lowpan/nhc.h
@@ -15,7 +15,7 @@
* @__nhc: variable name of the lowpan_nhc struct.
* @_name: const char * of common header compression name.
* @_nexthdr: ipv6 nexthdr field for the header compression.
- * @_nexthdrlen: ipv6 nexthdr len for the reserved space.
+ * @_hdrlen: ipv6 nexthdr len for the reserved space.
* @_id: one byte nhc id value.
* @_idmask: one byte nhc id mask value.
* @_uncompress: callback for uncompression call.
@@ -102,7 +102,6 @@ int lowpan_nhc_do_compression(struct sk_buff *skb, const struct ipv6hdr *hdr,
/**
* lowpan_nhc_do_uncompression - calling uncompress callback for nhc
*
- * @nhc: 6LoWPAN nhc context, get by lowpan_nhc_by_ functions.
* @skb: skb of 6LoWPAN header, skb->data should be pointed to nhc id value.
* @dev: netdevice for print logging information.
* @hdr: ipv6hdr for setting nexthdr value.
diff --git a/net/ieee802154/6lowpan/core.c b/net/ieee802154/6lowpan/core.c
index 018929563c6b..6a8d6852cb93 100644
--- a/net/ieee802154/6lowpan/core.c
+++ b/net/ieee802154/6lowpan/core.c
@@ -150,7 +150,7 @@ static int lowpan_newlink(struct net_device *ldev,
wdev = dev_get_by_index(dev_net(ldev), nla_get_u32(tb[IFLA_LINK]));
if (!wdev)
return -ENODEV;
- if (wdev->type != ARPHRD_IEEE802154) {
+ if (wdev->type != ARPHRD_IEEE802154 || !wdev->ieee802154_ptr) {
dev_put(wdev);
return -EINVAL;
}
diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h
index c53aa293a222..992ce6698c20 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -74,6 +74,10 @@ struct ieee802154_local {
struct work_struct rx_beacon_work;
struct list_head rx_mac_cmd_list;
struct work_struct rx_mac_cmd_work;
+ /* Serializes rx_beacon_list and rx_mac_cmd_list against the RX
+ * softirq producer, the mac_wq workers and the teardown flush.
+ */
+ spinlock_t rx_lock;
/* Association */
/* assoc_lock protects assoc_dev_extended_addr, assoc_addr,
@@ -305,6 +309,10 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local)
}
void mac802154_rx_mac_cmd_worker(struct work_struct *work);
+void mac802154_flush_list(struct list_head *list,
+ struct ieee802154_sub_if_data *sdata);
+void mac802154_flush_queued_pkts(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata);
int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
struct ieee802154_pan_device *coord,
diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c
index b823720630e7..31353795fa24 100644
--- a/net/mac802154/iface.c
+++ b/net/mac802154/iface.c
@@ -694,6 +694,7 @@ void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata)
mutex_unlock(&sdata->local->iflist_mtx);
synchronize_rcu();
+ mac802154_flush_queued_pkts(sdata->local, sdata);
unregister_netdevice(sdata->dev);
}
@@ -705,6 +706,11 @@ void ieee802154_remove_interfaces(struct ieee802154_local *local)
list_for_each_entry_safe(sdata, tmp, &local->interfaces, list) {
list_del_rcu(&sdata->list);
+ /* Best-effort: a frame the RX softirq queues for this sdata
+ * after the flush still pins the netdev, so the
+ * unregister_netdevice() below waits it out.
+ */
+ mac802154_flush_queued_pkts(local, sdata);
unregister_netdevice(sdata->dev);
}
mutex_unlock(&local->iflist_mtx);
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
index 63e89bd586e3..8ed6de111f5a 100644
--- a/net/mac802154/main.c
+++ b/net/mac802154/main.c
@@ -91,6 +91,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
INIT_LIST_HEAD(&local->interfaces);
INIT_LIST_HEAD(&local->rx_beacon_list);
INIT_LIST_HEAD(&local->rx_mac_cmd_list);
+ spin_lock_init(&local->rx_lock);
mutex_init(&local->iflist_mtx);
tasklet_setup(&local->tasklet, ieee802154_tasklet_handler);
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index cd8f2a11920d..19b5382e85a8 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -35,16 +35,23 @@ void mac802154_rx_beacon_worker(struct work_struct *work)
container_of(work, struct ieee802154_local, rx_beacon_work);
struct cfg802154_mac_pkt *mac_pkt;
- mac_pkt = list_first_entry_or_null(&local->rx_beacon_list,
- struct cfg802154_mac_pkt, node);
- if (!mac_pkt)
- return;
+ for (;;) {
+ spin_lock_bh(&local->rx_lock);
+ mac_pkt = list_first_entry_or_null(&local->rx_beacon_list,
+ struct cfg802154_mac_pkt, node);
+ if (mac_pkt)
+ list_del(&mac_pkt->node);
+ spin_unlock_bh(&local->rx_lock);
+ if (!mac_pkt)
+ break;
- mac802154_process_beacon(local, mac_pkt->skb, mac_pkt->page, mac_pkt->channel);
+ mac802154_process_beacon(local, mac_pkt->skb,
+ mac_pkt->page, mac_pkt->channel);
- list_del(&mac_pkt->node);
- kfree_skb(mac_pkt->skb);
- kfree(mac_pkt);
+ netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker);
+ kfree_skb(mac_pkt->skb);
+ kfree(mac_pkt);
+ }
}
static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local)
@@ -68,22 +75,15 @@ static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local)
return interval == IEEE802154_ACTIVE_SCAN_DURATION;
}
-void mac802154_rx_mac_cmd_worker(struct work_struct *work)
+static void mac802154_rx_mac_cmd(struct ieee802154_local *local,
+ struct cfg802154_mac_pkt *mac_pkt)
{
- struct ieee802154_local *local =
- container_of(work, struct ieee802154_local, rx_mac_cmd_work);
- struct cfg802154_mac_pkt *mac_pkt;
u8 mac_cmd;
int rc;
- mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list,
- struct cfg802154_mac_pkt, node);
- if (!mac_pkt)
- return;
-
rc = ieee802154_get_mac_cmd(mac_pkt->skb, &mac_cmd);
if (rc)
- goto out;
+ return;
switch (mac_cmd) {
case IEEE802154_CMD_BEACON_REQ:
@@ -121,11 +121,81 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work)
default:
break;
}
+}
-out:
- list_del(&mac_pkt->node);
- kfree_skb(mac_pkt->skb);
- kfree(mac_pkt);
+void mac802154_rx_mac_cmd_worker(struct work_struct *work)
+{
+ struct ieee802154_local *local =
+ container_of(work, struct ieee802154_local, rx_mac_cmd_work);
+ struct cfg802154_mac_pkt *mac_pkt;
+
+ for (;;) {
+ spin_lock_bh(&local->rx_lock);
+ mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list,
+ struct cfg802154_mac_pkt, node);
+ if (mac_pkt)
+ list_del(&mac_pkt->node);
+ spin_unlock_bh(&local->rx_lock);
+ if (!mac_pkt)
+ break;
+
+ /* A stopped interface cannot transmit; skipping avoids a
+ * needless association response (and the !netif_running()
+ * warning it would trip) during teardown. The beacon worker
+ * needs no such check as it never transmits.
+ */
+ if (ieee802154_sdata_running(mac_pkt->sdata))
+ mac802154_rx_mac_cmd(local, mac_pkt);
+
+ netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker);
+ kfree_skb(mac_pkt->skb);
+ kfree(mac_pkt);
+ }
+}
+
+/**
+ * mac802154_flush_list - free queued RX frames on @list
+ * @list: rx_beacon_list or rx_mac_cmd_list
+ * @sdata: only free frames received on this interface, or %NULL for all
+ *
+ * Each frame pins the net_device it was received on (via netdev_hold()),
+ * so release that reference as the frame is dropped. Caller must hold
+ * local->rx_lock.
+ */
+void mac802154_flush_list(struct list_head *list,
+ struct ieee802154_sub_if_data *sdata)
+{
+ struct cfg802154_mac_pkt *mac_pkt, *tmp;
+
+ list_for_each_entry_safe(mac_pkt, tmp, list, node) {
+ if (sdata && mac_pkt->sdata != sdata)
+ continue;
+ list_del(&mac_pkt->node);
+ netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker);
+ kfree_skb(mac_pkt->skb);
+ kfree(mac_pkt);
+ }
+}
+
+/**
+ * mac802154_flush_queued_pkts - drop queued RX work referencing @sdata
+ * @local: the mac802154 device
+ * @sdata: interface being removed
+ *
+ * The workers dereference the queued frame's interface directly
+ * (mac_pkt->sdata) or through skb->dev in mac802154_process_beacon(). Drop
+ * the not-yet-started entries belonging to @sdata before it is unregistered
+ * so their netdev reference is released; an entry already dequeued by a
+ * running worker keeps its own reference until the worker completes, which
+ * unregister_netdevice() then waits out.
+ */
+void mac802154_flush_queued_pkts(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata)
+{
+ spin_lock_bh(&local->rx_lock);
+ mac802154_flush_list(&local->rx_beacon_list, sdata);
+ mac802154_flush_list(&local->rx_mac_cmd_list, sdata);
+ spin_unlock_bh(&local->rx_lock);
}
static int
@@ -221,7 +291,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
mac_pkt->sdata = sdata;
mac_pkt->page = sdata->local->scan_page;
mac_pkt->channel = sdata->local->scan_channel;
+ netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC);
+ spin_lock(&sdata->local->rx_lock);
list_add_tail(&mac_pkt->node, &sdata->local->rx_beacon_list);
+ spin_unlock(&sdata->local->rx_lock);
queue_work(sdata->local->mac_wq, &sdata->local->rx_beacon_work);
return NET_RX_SUCCESS;
@@ -233,7 +306,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
mac_pkt->skb = skb_get(skb);
mac_pkt->sdata = sdata;
+ netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC);
+ spin_lock(&sdata->local->rx_lock);
list_add_tail(&mac_pkt->node, &sdata->local->rx_mac_cmd_list);
+ spin_unlock(&sdata->local->rx_lock);
queue_work(sdata->local->mac_wq, &sdata->local->rx_mac_cmd_work);
return NET_RX_SUCCESS;
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
index dd156c01ac49..d393b1f4e74e 100644
--- a/net/mac802154/scan.c
+++ b/net/mac802154/scan.c
@@ -104,13 +104,9 @@ static unsigned int mac802154_scan_get_channel_time(u8 duration_order,
static void mac802154_flush_queued_beacons(struct ieee802154_local *local)
{
- struct cfg802154_mac_pkt *mac_pkt, *tmp;
-
- list_for_each_entry_safe(mac_pkt, tmp, &local->rx_beacon_list, node) {
- list_del(&mac_pkt->node);
- kfree_skb(mac_pkt->skb);
- kfree(mac_pkt);
- }
+ spin_lock_bh(&local->rx_lock);
+ mac802154_flush_list(&local->rx_beacon_list, NULL);
+ spin_unlock_bh(&local->rx_lock);
}
static void