]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
hsr: Avoid double remove of a node.
authorSebastian Andrzej Siewior <bigeasy@linutronix.de>
Tue, 29 Nov 2022 16:48:10 +0000 (17:48 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 1 Mar 2024 12:16:46 +0000 (13:16 +0100)
[ 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
net/hsr/hsr_framereg.h

index afc97d65cf2d8925b6451c5205c144124ce66123..87fc86aade5c95161e9ba9a792f17811501e1cf9 100644 (file)
@@ -327,9 +327,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:
        /* PRP uses v0 header */
@@ -506,9 +509,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);
index 5a771cb3f03250712234ed0808c3f7cf41e3198a..48990166e4c4e49e1aaf8f6c292f0d5f48b30fb1 100644 (file)
@@ -82,6 +82,7 @@ struct hsr_node {
        bool                    san_a;
        bool                    san_b;
        u16                     seq_out[HSR_PT_PORTS];
+       bool                    removed;
        struct rcu_head         rcu_head;
 };