| From d2893238ade4276e5c507b3b84feba00b2687525 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Tue, 29 Nov 2022 17:48:10 +0100 |
| Subject: hsr: Avoid double remove of a node. |
| |
| From: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| |
| [ Upstream commit 0c74d9f79ec4299365bbe803baa736ae0068179e ] |
| |
| Due to the hashed-MAC optimisation one problem become visible: |
| hsr_handle_sup_frame() walks over the list of available nodes and merges |
| two node entries into one if based on the information in the supervision |
| both MAC addresses belong to one node. The list-walk happens on a RCU |
| protected list and delete operation happens under a lock. |
| |
| If the supervision arrives on both slave interfaces at the same time |
| then this delete operation can occur simultaneously on two CPUs. The |
| result is the first-CPU deletes the from the list and the second CPUs |
| BUGs while attempting to dereference a poisoned list-entry. This happens |
| more likely with the optimisation because a new node for the mac_B entry |
| is created once a packet has been received and removed (merged) once the |
| supervision frame has been received. |
| |
| Avoid removing/ cleaning up a hsr_node twice by adding a `removed' field |
| which is set to true after the removal and checked before the removal. |
| |
| Fixes: f266a683a4804 ("net/hsr: Better frame dispatch") |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| net/hsr/hsr_framereg.c | 16 +++++++++++----- |
| net/hsr/hsr_framereg.h | 1 + |
| 2 files changed, 12 insertions(+), 5 deletions(-) |
| |
| diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c |
| index 9b8eaebce254..f2dd846ff903 100644 |
| --- a/net/hsr/hsr_framereg.c |
| +++ b/net/hsr/hsr_framereg.c |
| @@ -366,9 +366,12 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) |
| node_real->addr_B_port = port_rcv->type; |
| |
| spin_lock_bh(&hsr->list_lock); |
| - list_del_rcu(&node_curr->mac_list); |
| + if (!node_curr->removed) { |
| + list_del_rcu(&node_curr->mac_list); |
| + node_curr->removed = true; |
| + kfree_rcu(node_curr, rcu_head); |
| + } |
| spin_unlock_bh(&hsr->list_lock); |
| - kfree_rcu(node_curr, rcu_head); |
| |
| done: |
| /* Push back here */ |
| @@ -539,9 +542,12 @@ void hsr_prune_nodes(struct timer_list *t) |
| if (time_is_before_jiffies(timestamp + |
| msecs_to_jiffies(HSR_NODE_FORGET_TIME))) { |
| hsr_nl_nodedown(hsr, node->macaddress_A); |
| - list_del_rcu(&node->mac_list); |
| - /* Note that we need to free this entry later: */ |
| - kfree_rcu(node, rcu_head); |
| + if (!node->removed) { |
| + list_del_rcu(&node->mac_list); |
| + node->removed = true; |
| + /* Note that we need to free this entry later: */ |
| + kfree_rcu(node, rcu_head); |
| + } |
| } |
| } |
| spin_unlock_bh(&hsr->list_lock); |
| diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h |
| index bdbb8c822ba1..b5f902397bf1 100644 |
| --- a/net/hsr/hsr_framereg.h |
| +++ b/net/hsr/hsr_framereg.h |
| @@ -80,6 +80,7 @@ struct hsr_node { |
| bool san_a; |
| bool san_b; |
| u16 seq_out[HSR_PT_PORTS]; |
| + bool removed; |
| struct rcu_head rcu_head; |
| }; |
| |
| -- |
| 2.35.1 |
| |