Commit 0c74d9f7 authored by Sebastian Andrzej Siewior's avatar Sebastian Andrzej Siewior Committed by Jakub Kicinski

hsr: Avoid double remove of a node.

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: f266a683 ("net/hsr: Better frame dispatch")
Signed-off-by: default avatarSebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 5aa28201
...@@ -366,9 +366,12 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) ...@@ -366,9 +366,12 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
node_real->addr_B_port = port_rcv->type; node_real->addr_B_port = port_rcv->type;
spin_lock_bh(&hsr->list_lock); spin_lock_bh(&hsr->list_lock);
if (!node_curr->removed) {
list_del_rcu(&node_curr->mac_list); list_del_rcu(&node_curr->mac_list);
spin_unlock_bh(&hsr->list_lock); node_curr->removed = true;
kfree_rcu(node_curr, rcu_head); kfree_rcu(node_curr, rcu_head);
}
spin_unlock_bh(&hsr->list_lock);
done: done:
/* Push back here */ /* Push back here */
...@@ -539,11 +542,14 @@ void hsr_prune_nodes(struct timer_list *t) ...@@ -539,11 +542,14 @@ void hsr_prune_nodes(struct timer_list *t)
if (time_is_before_jiffies(timestamp + if (time_is_before_jiffies(timestamp +
msecs_to_jiffies(HSR_NODE_FORGET_TIME))) { msecs_to_jiffies(HSR_NODE_FORGET_TIME))) {
hsr_nl_nodedown(hsr, node->macaddress_A); hsr_nl_nodedown(hsr, node->macaddress_A);
if (!node->removed) {
list_del_rcu(&node->mac_list); list_del_rcu(&node->mac_list);
node->removed = true;
/* Note that we need to free this entry later: */ /* Note that we need to free this entry later: */
kfree_rcu(node, rcu_head); kfree_rcu(node, rcu_head);
} }
} }
}
spin_unlock_bh(&hsr->list_lock); spin_unlock_bh(&hsr->list_lock);
/* Restart timer */ /* Restart timer */
......
...@@ -80,6 +80,7 @@ struct hsr_node { ...@@ -80,6 +80,7 @@ struct hsr_node {
bool san_a; bool san_a;
bool san_b; bool san_b;
u16 seq_out[HSR_PT_PORTS]; u16 seq_out[HSR_PT_PORTS];
bool removed;
struct rcu_head rcu_head; struct rcu_head rcu_head;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment