summaryrefslogtreecommitdiff
path: root/net/mac802154
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac802154')
-rw-r--r--net/mac802154/ieee802154_i.h15
-rw-r--r--net/mac802154/iface.c6
-rw-r--r--net/mac802154/main.c2
-rw-r--r--net/mac802154/rx.c120
-rw-r--r--net/mac802154/scan.c61
5 files changed, 161 insertions, 43 deletions
diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h
index 8f2bff268392..992ce6698c20 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -74,9 +74,18 @@ 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 */
- struct ieee802154_pan_device *assoc_dev;
+ /* assoc_lock protects assoc_dev_extended_addr, assoc_addr,
+ * assoc_status, the assoc_done reinit/complete pairing and the
+ * IEEE802154_IS_ASSOCIATING bit in @ongoing.
+ */
+ spinlock_t assoc_lock;
+ __le64 assoc_dev_extended_addr;
struct completion assoc_done;
__le16 assoc_addr;
u8 assoc_status;
@@ -300,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 ea1efef3572a..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);
@@ -104,6 +105,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
INIT_WORK(&local->rx_mac_cmd_work, mac802154_rx_mac_cmd_worker);
init_completion(&local->assoc_done);
+ spin_lock_init(&local->assoc_lock);
/* init supported flags with 802.15.4 default ranges */
phy->supported.max_minbe = 8;
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 005338f89b75..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
@@ -536,7 +532,9 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
struct ieee802154_association_req_frame frame = {};
struct ieee802154_local *local = sdata->local;
struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ __le16 resp_short_addr;
struct sk_buff *skb;
+ u8 resp_status;
int ret;
frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD;
@@ -578,9 +576,11 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
return ret;
}
- local->assoc_dev = coord;
+ spin_lock(&local->assoc_lock);
reinit_completion(&local->assoc_done);
+ local->assoc_dev_extended_addr = coord->extended_addr;
set_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
+ spin_unlock(&local->assoc_lock);
ret = ieee802154_mlme_tx_one_locked(local, sdata, skb);
if (ret) {
@@ -599,25 +599,37 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
goto clear_assoc;
}
- if (local->assoc_status != IEEE802154_ASSOCIATION_SUCCESSFUL) {
- if (local->assoc_status == IEEE802154_PAN_AT_CAPACITY)
+ /* The association is complete: mac802154_process_association_resp()
+ * cleared the associating bit before waking us, so a second (e.g.
+ * malicious) ASSOC RESP can no longer pass the recheck and overwrite
+ * the result. Snapshot assoc_status/assoc_addr under the lock.
+ */
+ spin_lock(&local->assoc_lock);
+ resp_status = local->assoc_status;
+ resp_short_addr = local->assoc_addr;
+ spin_unlock(&local->assoc_lock);
+
+ if (resp_status != IEEE802154_ASSOCIATION_SUCCESSFUL) {
+ if (resp_status == IEEE802154_PAN_AT_CAPACITY)
ret = -ERANGE;
else
ret = -EPERM;
dev_warn(&sdata->dev->dev,
"Negative ASSOC RESP received from %8phC: %s\n", &ceaddr,
- local->assoc_status == IEEE802154_PAN_AT_CAPACITY ?
+ resp_status == IEEE802154_PAN_AT_CAPACITY ?
"PAN at capacity" : "access denied");
- goto clear_assoc;
+ return ret;
}
- ret = 0;
- *short_addr = local->assoc_addr;
+ *short_addr = resp_short_addr;
+
+ return 0;
clear_assoc:
+ spin_lock(&local->assoc_lock);
clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
- local->assoc_dev = NULL;
+ spin_unlock(&local->assoc_lock);
return ret;
}
@@ -639,19 +651,28 @@ int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata,
dest->mode != IEEE802154_EXTENDED_ADDRESSING))
return -EINVAL;
- if (unlikely(dest->extended_addr != wpan_dev->extended_addr ||
- src->extended_addr != local->assoc_dev->extended_addr))
+ spin_lock(&local->assoc_lock);
+ if (unlikely(!test_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing) ||
+ dest->extended_addr != wpan_dev->extended_addr ||
+ src->extended_addr != local->assoc_dev_extended_addr)) {
+ spin_unlock(&local->assoc_lock);
return -ENODEV;
+ }
memcpy(&resp_pl, skb->data, sizeof(resp_pl));
local->assoc_addr = resp_pl.short_addr;
local->assoc_status = resp_pl.status;
+ /* Clear the associating bit before waking the waiter: once the result
+ * is saved, any subsequent (e.g. malicious) ASSOC RESP must fail the
+ * test_bit() recheck above and can no longer overwrite the result.
+ */
+ clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
+ complete(&local->assoc_done);
+ spin_unlock(&local->assoc_lock);
dev_dbg(&skb->dev->dev,
"ASSOC RESP 0x%x received from %8phC, getting short address %04x\n",
- local->assoc_status, &deaddr, local->assoc_addr);
-
- complete(&local->assoc_done);
+ resp_pl.status, &deaddr, resp_pl.short_addr);
return 0;
}