Commit be193568 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-hsr-fixes-for-prp-duplication-and-vlan-unwind'

Luka Gejak says:

====================
net: hsr: fixes for PRP duplication and VLAN unwind

This series addresses two logic bugs in the HSR/PRP implementation
identified during a protocol audit. These are targeted for the 'net'
tree as they fix potential memory corruption and state inconsistency.

The primary change resolves a race condition in the node merging path by
implementing address-based lock ordering. This ensures that concurrent
mutations of sequence blocks do not lead to state corruption or
deadlocks.

An additional fix corrects asymmetric VLAN error unwinding by
implementing a centralized unwind path on slave errors.
====================

Link: https://patch.msgid.link/20260401092243.52121-1-luka.gejak@linux.dev


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents b18c8338 2e3514e6
Loading
Loading
Loading
Loading
+17 −15
Original line number Diff line number Diff line
@@ -532,8 +532,8 @@ static void hsr_change_rx_flags(struct net_device *dev, int change)
static int hsr_ndo_vlan_rx_add_vid(struct net_device *dev,
				   __be16 proto, u16 vid)
{
	bool is_slave_a_added = false;
	bool is_slave_b_added = false;
	struct net_device *slave_a_dev = NULL;
	struct net_device *slave_b_dev = NULL;
	struct hsr_port *port;
	struct hsr_priv *hsr;
	int ret = 0;
@@ -549,33 +549,35 @@ static int hsr_ndo_vlan_rx_add_vid(struct net_device *dev,
		switch (port->type) {
		case HSR_PT_SLAVE_A:
			if (ret) {
				/* clean up Slave-B */
				netdev_err(dev, "add vid failed for Slave-A\n");
				if (is_slave_b_added)
					vlan_vid_del(port->dev, proto, vid);
				return ret;
				goto unwind;
			}

			is_slave_a_added = true;
			slave_a_dev = port->dev;
			break;

		case HSR_PT_SLAVE_B:
			if (ret) {
				/* clean up Slave-A */
				netdev_err(dev, "add vid failed for Slave-B\n");
				if (is_slave_a_added)
					vlan_vid_del(port->dev, proto, vid);
				return ret;
				goto unwind;
			}

			is_slave_b_added = true;
			slave_b_dev = port->dev;
			break;
		default:
			if (ret)
				goto unwind;
			break;
		}
	}

	return 0;

unwind:
	if (slave_a_dev)
		vlan_vid_del(slave_a_dev, proto, vid);

	if (slave_b_dev)
		vlan_vid_del(slave_b_dev, proto, vid);

	return ret;
}

static int hsr_ndo_vlan_rx_kill_vid(struct net_device *dev,
+36 −2
Original line number Diff line number Diff line
@@ -123,6 +123,40 @@ static void hsr_free_node_rcu(struct rcu_head *rn)
	hsr_free_node(node);
}

static void hsr_lock_seq_out_pair(struct hsr_node *node_a,
				  struct hsr_node *node_b)
{
	if (node_a == node_b) {
		spin_lock_bh(&node_a->seq_out_lock);
		return;
	}

	if (node_a < node_b) {
		spin_lock_bh(&node_a->seq_out_lock);
		spin_lock_nested(&node_b->seq_out_lock, SINGLE_DEPTH_NESTING);
	} else {
		spin_lock_bh(&node_b->seq_out_lock);
		spin_lock_nested(&node_a->seq_out_lock, SINGLE_DEPTH_NESTING);
	}
}

static void hsr_unlock_seq_out_pair(struct hsr_node *node_a,
				    struct hsr_node *node_b)
{
	if (node_a == node_b) {
		spin_unlock_bh(&node_a->seq_out_lock);
		return;
	}

	if (node_a < node_b) {
		spin_unlock(&node_b->seq_out_lock);
		spin_unlock_bh(&node_a->seq_out_lock);
	} else {
		spin_unlock(&node_a->seq_out_lock);
		spin_unlock_bh(&node_b->seq_out_lock);
	}
}

void hsr_del_nodes(struct list_head *node_db)
{
	struct hsr_node *node;
@@ -432,7 +466,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
	}

	ether_addr_copy(node_real->macaddress_B, ethhdr->h_source);
	spin_lock_bh(&node_real->seq_out_lock);
	hsr_lock_seq_out_pair(node_real, node_curr);
	for (i = 0; i < HSR_PT_PORTS; i++) {
		if (!node_curr->time_in_stale[i] &&
		    time_after(node_curr->time_in[i], node_real->time_in[i])) {
@@ -455,7 +489,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
				  src_blk->seq_nrs[i], HSR_SEQ_BLOCK_SIZE);
		}
	}
	spin_unlock_bh(&node_real->seq_out_lock);
	hsr_unlock_seq_out_pair(node_real, node_curr);
	node_real->addr_B_port = port_rcv->type;

	spin_lock_bh(&hsr->list_lock);