* FAILED: patch "[PATCH] net: hsr: defer node table free until after RCU readers" failed to apply to 6.18-stable tree
@ 2026-05-28 7:53 gregkh
2026-05-29 23:24 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sasha Levin
0 siblings, 1 reply; 6+ messages in thread
From: gregkh @ 2026-05-28 7:53 UTC (permalink / raw)
To: michael.bommarito, kuba; +Cc: stable
The patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x aaec7096f9961eb223b5b149abe9495525c205d9
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026052838-cleat-rewrite-24c4@gregkh' --subject-prefix 'PATCH 6.18.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From aaec7096f9961eb223b5b149abe9495525c205d9 Mon Sep 17 00:00:00 2001
From: Michael Bommarito <michael.bommarito@gmail.com>
Date: Wed, 13 May 2026 19:38:38 -0400
Subject: [PATCH] net: hsr: defer node table free until after RCU readers
HSR node-list and node-status generic-netlink operations run under
rcu_read_lock(). They walk hsr->node_db through hsr_get_next_node() and
hsr_get_node_data(), but RTM_DELLINK teardown removes the same node table
with plain list_del() and frees each node immediately.
That lets a generic-netlink reader hold a struct hsr_node pointer across
hsr_dellink(). In a KASAN build, widening the reader window after
hsr_get_next_node() obtains the node reproduces a slab-use-after-free
when the reader copies node->macaddress_A; the freeing stack is
hsr_del_nodes() from hsr_dellink().
Use list_del_rcu() and defer the free through the existing
hsr_free_node_rcu() callback. This matches the lifetime rule used by the
HSR prune paths, which already delete nodes with list_del_rcu() and
call_rcu().
Fixes: b9a1e627405d ("hsr: implement dellink to clean up resources")
Cc: stable@vger.kernel.org # v5.3+
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Link: https://patch.msgid.link/20260513233838.3064715-2-michael.bommarito@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index 124619920d38..b514e43766ef 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -163,8 +163,8 @@ void hsr_del_nodes(struct list_head *node_db)
struct hsr_node *tmp;
list_for_each_entry_safe(node, tmp, node_db, mac_list) {
- list_del(&node->mac_list);
- hsr_free_node(node);
+ list_del_rcu(&node->mac_list);
+ call_rcu(&node->rcu_head, hsr_free_node_rcu);
}
}
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP
2026-05-28 7:53 FAILED: patch "[PATCH] net: hsr: defer node table free until after RCU readers" failed to apply to 6.18-stable tree gregkh
@ 2026-05-29 23:24 ` Sasha Levin
2026-05-29 23:24 ` [PATCH 6.18.y 2/2] net: hsr: defer node table free until after RCU readers Sasha Levin
2026-06-01 7:52 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sebastian Andrzej Siewior
0 siblings, 2 replies; 6+ messages in thread
From: Sasha Levin @ 2026-05-29 23:24 UTC (permalink / raw)
To: stable
Cc: Felix Maurer, Steffen Lindner, Sebastian Andrzej Siewior,
Paolo Abeni, Sasha Levin
From: Felix Maurer <fmaurer@redhat.com>
[ Upstream commit 415e6367512bf8faca93eaaf46fbe23d841b4509 ]
The PRP duplicate discard algorithm does not work reliably with certain
link faults. Especially with packet loss on one link, the duplicate discard
algorithm drops valid packets which leads to packet loss on the PRP
interface where the link fault should in theory be perfectly recoverable by
PRP. This happens because the algorithm opens the drop window on the lossy
link, covering received and lost sequence numbers. If the other, non-lossy
link receives the duplicate for a lost frame, it is within the drop window
of the lossy link and therefore dropped.
Since IEC 62439-3:2012, a node has one sequence number counter for frames
it sends, instead of one sequence number counter for each destination.
Therefore, a node can not expect to receive contiguous sequence numbers
from a sender. A missing sequence number can be totally normal (if the
sender intermittently communicates with another node) or mean a frame was
lost.
The algorithm, as previously implemented in commit 05fd00e5e7b1 ("net: hsr:
Fix PRP duplicate detection"), was part of IEC 62439-3:2010 (HSRv0/PRPv0)
but was removed with IEC 62439-3:2012 (HSRv1/PRPv1). Since that, no
algorithm is specified but up to implementers. It should be "designed such
that it never rejects a legitimate frame, while occasional acceptance of a
duplicate can be tolerated" (IEC 62439-3:2021).
For the duplicate discard algorithm, this means that 1) we need to track
the sequence numbers individually to account for non-contiguous sequence
numbers, and 2) we should always err on the side of accepting a duplicate
than dropping a valid frame.
The idea of the new algorithm is to store the seen sequence numbers in a
bitmap. To keep the size of the bitmap in control, we store it as a "sparse
bitmap" where the bitmap is split into blocks and not all blocks exist at
the same time. The sparse bitmap is implemented using an xarray that keeps
the references to the individual blocks and a backing ring buffer that
stores the actual blocks. New blocks are initialized in the buffer and
added to the xarray as needed when new frames arrive. Existing blocks are
removed in two conditions:
1. The block found for an arriving sequence number is old and therefore not
relevant to the duplicate discard algorithm anymore, i.e., it has been
added more than the entry forget time ago. In this case, the block is
removed from the xarray and marked as forgotten (by setting its
timestamp to 0).
2. Space is needed in the ring buffer for a new block. In this case, the
block is removed from the xarray, if it hasn't already been forgotten
(by 1.). Afterwards, the new block is initialized in its place.
This has the nice property that we can reliably track sequence numbers on
low traffic situations (where they expire based on their timestamp) and
more quickly forget sequence numbers in high traffic situations before they
potentially wrap over and repeat before they are expired.
When nodes are merged, the blocks are merged as well. The timestamp of a
merged block is set to the minimum of the two timestamps to never keep
around a seen sequence number for too long. The bitmaps are or'd to mark
all seen sequence numbers as seen.
All of this still happens under seq_out_lock, to prevent concurrent
access to the blocks.
The KUnit test for the algorithm is updated as well. The updates are done
in a way to match the original intends pretty closely. Currently, there is
much knowledge about the actual algorithm baked into the tests (especially
the expectations) which may need some redesign in the future.
Reported-by: Steffen Lindner <steffen.lindner@de.abb.com>
Signed-off-by: Felix Maurer <fmaurer@redhat.com>
Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Tested-by: Steffen Lindner <steffen.lindner@de.abb.com>
Link: https://patch.msgid.link/8ce15a996099df2df5b700969a39e7df400e8dbb.1770299429.git.fmaurer@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Stable-dep-of: aaec7096f996 ("net: hsr: defer node table free until after RCU readers")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
net/hsr/hsr_framereg.c | 212 +++++++++++++++++++++------------
net/hsr/hsr_framereg.h | 25 +++-
net/hsr/prp_dup_discard_test.c | 138 +++++++++++----------
3 files changed, 237 insertions(+), 138 deletions(-)
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index 3a2a2fa7a0a39..7e21e607efc6d 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -11,6 +11,7 @@
* Same code handles filtering of duplicates for PRP as well.
*/
+#include <kunit/visibility.h>
#include <linux/if_ether.h>
#include <linux/etherdevice.h>
#include <linux/slab.h>
@@ -35,7 +36,6 @@ static bool seq_nr_after(u16 a, u16 b)
#define seq_nr_before(a, b) seq_nr_after((b), (a))
#define seq_nr_before_or_eq(a, b) (!seq_nr_after((a), (b)))
-#define PRP_DROP_WINDOW_LEN 32768
bool hsr_addr_is_redbox(struct hsr_priv *hsr, unsigned char *addr)
{
@@ -126,13 +126,29 @@ void hsr_del_self_node(struct hsr_priv *hsr)
kfree_rcu(old, rcu_head);
}
+static void hsr_free_node(struct hsr_node *node)
+{
+ xa_destroy(&node->seq_blocks);
+ kfree(node->block_buf);
+ kfree(node);
+}
+
+static void hsr_free_node_rcu(struct rcu_head *rn)
+{
+ struct hsr_node *node = container_of(rn, struct hsr_node, rcu_head);
+
+ hsr_free_node(node);
+}
+
void hsr_del_nodes(struct list_head *node_db)
{
struct hsr_node *node;
struct hsr_node *tmp;
- list_for_each_entry_safe(node, tmp, node_db, mac_list)
- kfree(node);
+ list_for_each_entry_safe(node, tmp, node_db, mac_list) {
+ list_del(&node->mac_list);
+ hsr_free_node(node);
+ }
}
void prp_handle_san_frame(bool san, enum hsr_port_type port,
@@ -158,7 +174,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
u16 seq_out, bool san,
enum hsr_port_type rx_port)
{
- struct hsr_node *new_node, *node;
+ struct hsr_node *new_node, *node = NULL;
unsigned long now;
int i;
@@ -169,6 +185,14 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
ether_addr_copy(new_node->macaddress_A, addr);
spin_lock_init(&new_node->seq_out_lock);
+ new_node->block_buf = kcalloc(HSR_MAX_SEQ_BLOCKS,
+ sizeof(struct hsr_seq_block),
+ GFP_ATOMIC);
+ if (!new_node->block_buf)
+ goto free;
+
+ xa_init(&new_node->seq_blocks);
+
/* We are only interested in time diffs here, so use current jiffies
* as initialization. (0 could trigger an spurious ring error warning).
*/
@@ -176,11 +200,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
for (i = 0; i < HSR_PT_PORTS; i++) {
new_node->time_in[i] = now;
new_node->time_out[i] = now;
- }
- for (i = 0; i < HSR_PT_PORTS; i++) {
new_node->seq_out[i] = seq_out;
- new_node->seq_expected[i] = seq_out + 1;
- new_node->seq_start[i] = seq_out + 1;
}
if (san && hsr->proto_ops->handle_san_frame)
@@ -199,6 +219,8 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
return new_node;
out:
spin_unlock_bh(&hsr->list_lock);
+ kfree(new_node->block_buf);
+free:
kfree(new_node);
return node;
}
@@ -280,6 +302,62 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
san, rx_port);
}
+static bool hsr_seq_block_is_old(struct hsr_seq_block *block)
+{
+ unsigned long expiry = msecs_to_jiffies(HSR_ENTRY_FORGET_TIME);
+
+ return time_is_before_jiffies(block->time + expiry);
+}
+
+static void hsr_forget_seq_block(struct hsr_node *node,
+ struct hsr_seq_block *block)
+{
+ if (block->time)
+ xa_erase(&node->seq_blocks, block->block_idx);
+ block->time = 0;
+}
+
+/* Get the currently active sequence number block. If there is no block yet, or
+ * the existing one is expired, a new block is created. The idea is to maintain
+ * a "sparse bitmap" where a bitmap for the whole sequence number space is
+ * split into blocks and not all blocks exist all the time. The blocks can
+ * expire after time (in low traffic situations) or when they are replaced in
+ * the backing fixed size buffer (in high traffic situations).
+ */
+VISIBLE_IF_KUNIT struct hsr_seq_block *hsr_get_seq_block(struct hsr_node *node,
+ u16 block_idx)
+{
+ struct hsr_seq_block *block, *res;
+
+ block = xa_load(&node->seq_blocks, block_idx);
+
+ if (block && hsr_seq_block_is_old(block)) {
+ hsr_forget_seq_block(node, block);
+ block = NULL;
+ }
+
+ if (!block) {
+ block = &node->block_buf[node->next_block];
+ hsr_forget_seq_block(node, block);
+
+ memset(block, 0, sizeof(*block));
+ block->time = jiffies;
+ block->block_idx = block_idx;
+
+ res = xa_store(&node->seq_blocks, block_idx, block, GFP_ATOMIC);
+ if (xa_is_err(res)) {
+ block->time = 0;
+ return NULL;
+ }
+
+ node->next_block =
+ (node->next_block + 1) & (HSR_MAX_SEQ_BLOCKS - 1);
+ }
+
+ return block;
+}
+EXPORT_SYMBOL_IF_KUNIT(hsr_get_seq_block);
+
/* Use the Supervision frame's info about an eventual macaddress_B for merging
* nodes that has previously had their macaddress_B registered as a separate
* node.
@@ -288,16 +366,18 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
{
struct hsr_node *node_curr = frame->node_src;
struct hsr_port *port_rcv = frame->port_rcv;
+ struct hsr_seq_block *src_blk, *merge_blk;
struct hsr_priv *hsr = port_rcv->hsr;
- struct hsr_sup_payload *hsr_sp;
struct hsr_sup_tlv *hsr_sup_tlv;
+ struct hsr_sup_payload *hsr_sp;
struct hsr_node *node_real;
struct sk_buff *skb = NULL;
struct list_head *node_db;
struct ethhdr *ethhdr;
- int i;
- unsigned int pull_size = 0;
unsigned int total_pull_size = 0;
+ unsigned int pull_size = 0;
+ unsigned long idx;
+ int i;
/* Here either frame->skb_hsr or frame->skb_prp should be
* valid as supervision frame always will have protocol
@@ -391,6 +471,18 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
if (seq_nr_after(node_curr->seq_out[i], node_real->seq_out[i]))
node_real->seq_out[i] = node_curr->seq_out[i];
}
+
+ xa_for_each(&node_curr->seq_blocks, idx, src_blk) {
+ if (hsr_seq_block_is_old(src_blk))
+ continue;
+
+ merge_blk = hsr_get_seq_block(node_real, src_blk->block_idx);
+ if (!merge_blk)
+ continue;
+ merge_blk->time = min(merge_blk->time, src_blk->time);
+ bitmap_or(merge_blk->seq_nrs, merge_blk->seq_nrs,
+ src_blk->seq_nrs, HSR_SEQ_BLOCK_SIZE);
+ }
spin_unlock_bh(&node_real->seq_out_lock);
node_real->addr_B_port = port_rcv->type;
@@ -398,7 +490,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
if (!node_curr->removed) {
list_del_rcu(&node_curr->mac_list);
node_curr->removed = true;
- kfree_rcu(node_curr, rcu_head);
+ call_rcu(&node_curr->rcu_head, hsr_free_node_rcu);
}
spin_unlock_bh(&hsr->list_lock);
@@ -505,17 +597,12 @@ int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
return 0;
}
-/* Adaptation of the PRP duplicate discard algorithm described in wireshark
- * wiki (https://wiki.wireshark.org/PRP)
- *
- * A drop window is maintained for both LANs with start sequence set to the
- * first sequence accepted on the LAN that has not been seen on the other LAN,
- * and expected sequence set to the latest received sequence number plus one.
- *
- * When a frame is received on either LAN it is compared against the received
- * frames on the other LAN. If it is outside the drop window of the other LAN
- * the frame is accepted and the drop window is updated.
- * The drop window for the other LAN is reset.
+/* PRP duplicate discard algorithm: we maintain a bitmap where we set a bit for
+ * every seen sequence number. The bitmap is split into blocks and the block
+ * management is detailed in hsr_get_seq_block(). In any case, we err on the
+ * side of accepting a packet, as the specification requires the algorithm to
+ * be "designed such that it never rejects a legitimate frame, while occasional
+ * acceptance of a duplicate can be tolerated." (IEC 62439-3:2021, 4.1.10.3).
*
* 'port' is the outgoing interface
* 'frame' is the frame to be sent
@@ -526,18 +613,21 @@ int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
*/
int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
{
- enum hsr_port_type other_port;
- enum hsr_port_type rcv_port;
+ u16 sequence_nr, seq_bit, block_idx;
+ struct hsr_seq_block *block;
struct hsr_node *node;
- u16 sequence_diff;
- u16 sequence_exp;
- u16 sequence_nr;
- /* out-going frames are always in order
- * and can be checked the same way as for HSR
- */
- if (frame->port_rcv->type == HSR_PT_MASTER)
- return hsr_register_frame_out(port, frame);
+ node = frame->node_src;
+ sequence_nr = frame->sequence_nr;
+
+ /* out-going frames are always in order */
+ if (frame->port_rcv->type == HSR_PT_MASTER) {
+ spin_lock_bh(&node->seq_out_lock);
+ node->time_out[port->type] = jiffies;
+ node->seq_out[port->type] = sequence_nr;
+ spin_unlock_bh(&node->seq_out_lock);
+ return 0;
+ }
/* for PRP we should only forward frames from the slave ports
* to the master port
@@ -545,52 +635,28 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
if (port->type != HSR_PT_MASTER)
return 1;
- node = frame->node_src;
- sequence_nr = frame->sequence_nr;
- sequence_exp = sequence_nr + 1;
- rcv_port = frame->port_rcv->type;
- other_port = rcv_port == HSR_PT_SLAVE_A ? HSR_PT_SLAVE_B :
- HSR_PT_SLAVE_A;
-
spin_lock_bh(&node->seq_out_lock);
- if (time_is_before_jiffies(node->time_out[port->type] +
- msecs_to_jiffies(HSR_ENTRY_FORGET_TIME)) ||
- (node->seq_start[rcv_port] == node->seq_expected[rcv_port] &&
- node->seq_start[other_port] == node->seq_expected[other_port])) {
- /* the node hasn't been sending for a while
- * or both drop windows are empty, forward the frame
- */
- node->seq_start[rcv_port] = sequence_nr;
- } else if (seq_nr_before(sequence_nr, node->seq_expected[other_port]) &&
- seq_nr_before_or_eq(node->seq_start[other_port], sequence_nr)) {
- /* drop the frame, update the drop window for the other port
- * and reset our drop window
- */
- node->seq_start[other_port] = sequence_exp;
- node->seq_expected[rcv_port] = sequence_exp;
- node->seq_start[rcv_port] = node->seq_expected[rcv_port];
- spin_unlock_bh(&node->seq_out_lock);
- return 1;
- }
- /* update the drop window for the port where this frame was received
- * and clear the drop window for the other port
- */
- node->seq_start[other_port] = node->seq_expected[other_port];
- node->seq_expected[rcv_port] = sequence_exp;
- sequence_diff = sequence_exp - node->seq_start[rcv_port];
- if (sequence_diff > PRP_DROP_WINDOW_LEN)
- node->seq_start[rcv_port] = sequence_exp - PRP_DROP_WINDOW_LEN;
+ block_idx = hsr_seq_block_index(sequence_nr);
+ block = hsr_get_seq_block(node, block_idx);
+ if (!block)
+ goto out_new;
+
+ seq_bit = hsr_seq_block_bit(sequence_nr);
+ if (__test_and_set_bit(seq_bit, block->seq_nrs))
+ goto out_seen;
node->time_out[port->type] = jiffies;
node->seq_out[port->type] = sequence_nr;
+out_new:
spin_unlock_bh(&node->seq_out_lock);
return 0;
-}
-#if IS_MODULE(CONFIG_PRP_DUP_DISCARD_KUNIT_TEST)
-EXPORT_SYMBOL(prp_register_frame_out);
-#endif
+out_seen:
+ spin_unlock_bh(&node->seq_out_lock);
+ return 1;
+}
+EXPORT_SYMBOL_IF_KUNIT(prp_register_frame_out);
static struct hsr_port *get_late_port(struct hsr_priv *hsr,
struct hsr_node *node)
@@ -672,7 +738,7 @@ void hsr_prune_nodes(struct timer_list *t)
list_del_rcu(&node->mac_list);
node->removed = true;
/* Note that we need to free this entry later: */
- kfree_rcu(node, rcu_head);
+ call_rcu(&node->rcu_head, hsr_free_node_rcu);
}
}
}
@@ -706,7 +772,7 @@ void hsr_prune_proxy_nodes(struct timer_list *t)
list_del_rcu(&node->mac_list);
node->removed = true;
/* Note that we need to free this entry later: */
- kfree_rcu(node, rcu_head);
+ call_rcu(&node->rcu_head, hsr_free_node_rcu);
}
}
}
diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
index b04948659d84d..686f2a5954007 100644
--- a/net/hsr/hsr_framereg.h
+++ b/net/hsr/hsr_framereg.h
@@ -74,9 +74,27 @@ bool hsr_is_node_in_db(struct list_head *node_db,
int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame);
+#if IS_ENABLED(CONFIG_KUNIT)
+struct hsr_seq_block *hsr_get_seq_block(struct hsr_node *node, u16 block_idx);
+#endif
+
+#define HSR_SEQ_BLOCK_SHIFT 7 /* 128 bits */
+#define HSR_SEQ_BLOCK_SIZE (1 << HSR_SEQ_BLOCK_SHIFT)
+#define HSR_SEQ_BLOCK_MASK (HSR_SEQ_BLOCK_SIZE - 1)
+#define HSR_MAX_SEQ_BLOCKS 64
+
+#define hsr_seq_block_index(sequence_nr) ((sequence_nr) >> HSR_SEQ_BLOCK_SHIFT)
+#define hsr_seq_block_bit(sequence_nr) ((sequence_nr) & HSR_SEQ_BLOCK_MASK)
+
+struct hsr_seq_block {
+ unsigned long time;
+ u16 block_idx;
+ DECLARE_BITMAP(seq_nrs, HSR_SEQ_BLOCK_SIZE);
+};
+
struct hsr_node {
struct list_head mac_list;
- /* Protect R/W access to seq_out */
+ /* Protect R/W access to seq_out and seq_blocks */
spinlock_t seq_out_lock;
unsigned char macaddress_A[ETH_ALEN];
unsigned char macaddress_B[ETH_ALEN];
@@ -91,8 +109,9 @@ struct hsr_node {
u16 seq_out[HSR_PT_PORTS];
bool removed;
/* PRP specific duplicate handling */
- u16 seq_expected[HSR_PT_PORTS];
- u16 seq_start[HSR_PT_PORTS];
+ struct xarray seq_blocks;
+ struct hsr_seq_block *block_buf;
+ unsigned int next_block;
struct rcu_head rcu_head;
};
diff --git a/net/hsr/prp_dup_discard_test.c b/net/hsr/prp_dup_discard_test.c
index e86b7b633ae89..bfa3d318ec046 100644
--- a/net/hsr/prp_dup_discard_test.c
+++ b/net/hsr/prp_dup_discard_test.c
@@ -4,6 +4,8 @@
#include "hsr_main.h"
#include "hsr_framereg.h"
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
struct prp_test_data {
struct hsr_port port;
struct hsr_port port_rcv;
@@ -17,13 +19,17 @@ static struct prp_test_data *build_prp_test_data(struct kunit *test)
sizeof(struct prp_test_data), GFP_USER);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, data);
+ data->node.block_buf = kunit_kcalloc(test, HSR_MAX_SEQ_BLOCKS,
+ sizeof(struct hsr_seq_block),
+ GFP_ATOMIC);
+ KUNIT_EXPECT_NOT_ERR_OR_NULL(test, data->node.block_buf);
+
+ xa_init(&data->node.seq_blocks);
+ spin_lock_init(&data->node.seq_out_lock);
+
data->frame.node_src = &data->node;
data->frame.port_rcv = &data->port_rcv;
data->port_rcv.type = HSR_PT_SLAVE_A;
- data->node.seq_start[HSR_PT_SLAVE_A] = 1;
- data->node.seq_expected[HSR_PT_SLAVE_A] = 1;
- data->node.seq_start[HSR_PT_SLAVE_B] = 1;
- data->node.seq_expected[HSR_PT_SLAVE_B] = 1;
data->node.seq_out[HSR_PT_MASTER] = 0;
data->node.time_out[HSR_PT_MASTER] = jiffies;
data->port.type = HSR_PT_MASTER;
@@ -31,19 +37,32 @@ static struct prp_test_data *build_prp_test_data(struct kunit *test)
return data;
}
-static void check_prp_counters(struct kunit *test,
- struct prp_test_data *data,
- u16 seq_start_a, u16 seq_expected_a,
- u16 seq_start_b, u16 seq_expected_b)
+static void check_prp_frame_seen(struct kunit *test, struct prp_test_data *data,
+ u16 sequence_nr)
+{
+ u16 block_idx, seq_bit;
+ struct hsr_seq_block *block;
+
+ block_idx = sequence_nr >> HSR_SEQ_BLOCK_SHIFT;
+ block = xa_load(&data->node.seq_blocks, block_idx);
+ KUNIT_EXPECT_NOT_NULL(test, block);
+
+ seq_bit = sequence_nr & HSR_SEQ_BLOCK_MASK;
+ KUNIT_EXPECT_TRUE(test, test_bit(seq_bit, block->seq_nrs));
+}
+
+static void check_prp_frame_unseen(struct kunit *test,
+ struct prp_test_data *data, u16 sequence_nr)
{
- KUNIT_EXPECT_EQ(test, data->node.seq_start[HSR_PT_SLAVE_A],
- seq_start_a);
- KUNIT_EXPECT_EQ(test, data->node.seq_start[HSR_PT_SLAVE_B],
- seq_start_b);
- KUNIT_EXPECT_EQ(test, data->node.seq_expected[HSR_PT_SLAVE_A],
- seq_expected_a);
- KUNIT_EXPECT_EQ(test, data->node.seq_expected[HSR_PT_SLAVE_B],
- seq_expected_b);
+ u16 block_idx, seq_bit;
+ struct hsr_seq_block *block;
+
+ block_idx = sequence_nr >> HSR_SEQ_BLOCK_SHIFT;
+ block = hsr_get_seq_block(&data->node, block_idx);
+ KUNIT_EXPECT_NOT_NULL(test, block);
+
+ seq_bit = sequence_nr & HSR_SEQ_BLOCK_MASK;
+ KUNIT_EXPECT_FALSE(test, test_bit(seq_bit, block->seq_nrs));
}
static void prp_dup_discard_forward(struct kunit *test)
@@ -57,49 +76,58 @@ static void prp_dup_discard_forward(struct kunit *test)
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
KUNIT_EXPECT_EQ(test, jiffies, data->node.time_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, data->frame.sequence_nr,
- data->frame.sequence_nr + 1, 1, 1);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
}
-static void prp_dup_discard_inside_dropwindow(struct kunit *test)
+static void prp_dup_discard_drop_duplicate(struct kunit *test)
{
- /* Normal situation, other LAN ahead by one. Frame is dropped */
struct prp_test_data *data = build_prp_test_data(test);
unsigned long time = jiffies - 10;
- data->frame.sequence_nr = 1;
- data->node.seq_expected[HSR_PT_SLAVE_B] = 3;
- data->node.seq_out[HSR_PT_MASTER] = 2;
+ data->frame.sequence_nr = 2;
+ KUNIT_EXPECT_EQ(test, 0,
+ prp_register_frame_out(&data->port, &data->frame));
+ KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
+ data->node.seq_out[HSR_PT_MASTER]);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
data->node.time_out[HSR_PT_MASTER] = time;
KUNIT_EXPECT_EQ(test, 1,
prp_register_frame_out(&data->port, &data->frame));
- KUNIT_EXPECT_EQ(test, 2, data->node.seq_out[HSR_PT_MASTER]);
+ KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
+ data->node.seq_out[HSR_PT_MASTER]);
KUNIT_EXPECT_EQ(test, time, data->node.time_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, 2, 2, 2, 3);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
}
-static void prp_dup_discard_node_timeout(struct kunit *test)
+static void prp_dup_discard_entry_timeout(struct kunit *test)
{
/* Timeout situation, node hasn't sent anything for a while */
struct prp_test_data *data = build_prp_test_data(test);
+ struct hsr_seq_block *block;
+ u16 block_idx;
data->frame.sequence_nr = 7;
- data->node.seq_start[HSR_PT_SLAVE_A] = 1234;
- data->node.seq_expected[HSR_PT_SLAVE_A] = 1235;
- data->node.seq_start[HSR_PT_SLAVE_B] = 1234;
- data->node.seq_expected[HSR_PT_SLAVE_B] = 1234;
- data->node.seq_out[HSR_PT_MASTER] = 1234;
- data->node.time_out[HSR_PT_MASTER] =
- jiffies - msecs_to_jiffies(HSR_ENTRY_FORGET_TIME) - 1;
+ KUNIT_EXPECT_EQ(test, 0,
+ prp_register_frame_out(&data->port, &data->frame));
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
+
+ data->frame.sequence_nr = 11;
+ KUNIT_EXPECT_EQ(test, 0,
+ prp_register_frame_out(&data->port, &data->frame));
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
+
+ block_idx = data->frame.sequence_nr >> HSR_SEQ_BLOCK_SHIFT;
+ block = hsr_get_seq_block(&data->node, block_idx);
+ block->time = jiffies - msecs_to_jiffies(HSR_ENTRY_FORGET_TIME) - 1;
KUNIT_EXPECT_EQ(test, 0,
prp_register_frame_out(&data->port, &data->frame));
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
KUNIT_EXPECT_EQ(test, jiffies, data->node.time_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, data->frame.sequence_nr,
- data->frame.sequence_nr + 1, 1234, 1234);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
+ check_prp_frame_unseen(test, data, 7);
}
static void prp_dup_discard_out_of_sequence(struct kunit *test)
@@ -107,11 +135,13 @@ static void prp_dup_discard_out_of_sequence(struct kunit *test)
/* One frame is received out of sequence on both LANs */
struct prp_test_data *data = build_prp_test_data(test);
- data->node.seq_start[HSR_PT_SLAVE_A] = 10;
- data->node.seq_expected[HSR_PT_SLAVE_A] = 10;
- data->node.seq_start[HSR_PT_SLAVE_B] = 10;
- data->node.seq_expected[HSR_PT_SLAVE_B] = 10;
- data->node.seq_out[HSR_PT_MASTER] = 9;
+ /* initial frame, should be accepted */
+ data->frame.sequence_nr = 9;
+ KUNIT_EXPECT_EQ(test, 0,
+ prp_register_frame_out(&data->port, &data->frame));
+ KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
+ data->node.seq_out[HSR_PT_MASTER]);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
/* 1st old frame, should be accepted */
data->frame.sequence_nr = 8;
@@ -119,18 +149,13 @@ static void prp_dup_discard_out_of_sequence(struct kunit *test)
prp_register_frame_out(&data->port, &data->frame));
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, data->frame.sequence_nr,
- data->frame.sequence_nr + 1, 10, 10);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
/* 2nd frame should be dropped */
data->frame.sequence_nr = 8;
data->port_rcv.type = HSR_PT_SLAVE_B;
KUNIT_EXPECT_EQ(test, 1,
prp_register_frame_out(&data->port, &data->frame));
- check_prp_counters(test, data, data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1);
/* Next frame, this is forwarded */
data->frame.sequence_nr = 10;
@@ -139,18 +164,13 @@ static void prp_dup_discard_out_of_sequence(struct kunit *test)
prp_register_frame_out(&data->port, &data->frame));
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, data->frame.sequence_nr,
- data->frame.sequence_nr + 1, 9, 9);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
/* and next one is dropped */
data->frame.sequence_nr = 10;
data->port_rcv.type = HSR_PT_SLAVE_B;
KUNIT_EXPECT_EQ(test, 1,
prp_register_frame_out(&data->port, &data->frame));
- check_prp_counters(test, data, data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1,
- data->frame.sequence_nr + 1);
}
static void prp_dup_discard_lan_b_late(struct kunit *test)
@@ -158,10 +178,6 @@ static void prp_dup_discard_lan_b_late(struct kunit *test)
/* LAN B is behind */
struct prp_test_data *data = build_prp_test_data(test);
- data->node.seq_start[HSR_PT_SLAVE_A] = 9;
- data->node.seq_expected[HSR_PT_SLAVE_A] = 9;
- data->node.seq_start[HSR_PT_SLAVE_B] = 9;
- data->node.seq_expected[HSR_PT_SLAVE_B] = 9;
data->node.seq_out[HSR_PT_MASTER] = 8;
data->frame.sequence_nr = 9;
@@ -169,32 +185,30 @@ static void prp_dup_discard_lan_b_late(struct kunit *test)
prp_register_frame_out(&data->port, &data->frame));
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, 9, 10, 9, 9);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
data->frame.sequence_nr = 10;
KUNIT_EXPECT_EQ(test, 0,
prp_register_frame_out(&data->port, &data->frame));
KUNIT_EXPECT_EQ(test, data->frame.sequence_nr,
data->node.seq_out[HSR_PT_MASTER]);
- check_prp_counters(test, data, 9, 11, 9, 9);
+ check_prp_frame_seen(test, data, data->frame.sequence_nr);
data->frame.sequence_nr = 9;
data->port_rcv.type = HSR_PT_SLAVE_B;
KUNIT_EXPECT_EQ(test, 1,
prp_register_frame_out(&data->port, &data->frame));
- check_prp_counters(test, data, 10, 11, 10, 10);
data->frame.sequence_nr = 10;
data->port_rcv.type = HSR_PT_SLAVE_B;
KUNIT_EXPECT_EQ(test, 1,
prp_register_frame_out(&data->port, &data->frame));
- check_prp_counters(test, data, 11, 11, 11, 11);
}
static struct kunit_case prp_dup_discard_test_cases[] = {
KUNIT_CASE(prp_dup_discard_forward),
- KUNIT_CASE(prp_dup_discard_inside_dropwindow),
- KUNIT_CASE(prp_dup_discard_node_timeout),
+ KUNIT_CASE(prp_dup_discard_drop_duplicate),
+ KUNIT_CASE(prp_dup_discard_entry_timeout),
KUNIT_CASE(prp_dup_discard_out_of_sequence),
KUNIT_CASE(prp_dup_discard_lan_b_late),
{}
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 2/2] net: hsr: defer node table free until after RCU readers
2026-05-29 23:24 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sasha Levin
@ 2026-05-29 23:24 ` Sasha Levin
2026-06-01 7:52 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sebastian Andrzej Siewior
1 sibling, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-05-29 23:24 UTC (permalink / raw)
To: stable; +Cc: Michael Bommarito, Jakub Kicinski, Sasha Levin
From: Michael Bommarito <michael.bommarito@gmail.com>
[ Upstream commit aaec7096f9961eb223b5b149abe9495525c205d9 ]
HSR node-list and node-status generic-netlink operations run under
rcu_read_lock(). They walk hsr->node_db through hsr_get_next_node() and
hsr_get_node_data(), but RTM_DELLINK teardown removes the same node table
with plain list_del() and frees each node immediately.
That lets a generic-netlink reader hold a struct hsr_node pointer across
hsr_dellink(). In a KASAN build, widening the reader window after
hsr_get_next_node() obtains the node reproduces a slab-use-after-free
when the reader copies node->macaddress_A; the freeing stack is
hsr_del_nodes() from hsr_dellink().
Use list_del_rcu() and defer the free through the existing
hsr_free_node_rcu() callback. This matches the lifetime rule used by the
HSR prune paths, which already delete nodes with list_del_rcu() and
call_rcu().
Fixes: b9a1e627405d ("hsr: implement dellink to clean up resources")
Cc: stable@vger.kernel.org # v5.3+
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Link: https://patch.msgid.link/20260513233838.3064715-2-michael.bommarito@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
net/hsr/hsr_framereg.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index 7e21e607efc6d..177e0879ddbb4 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -146,8 +146,8 @@ void hsr_del_nodes(struct list_head *node_db)
struct hsr_node *tmp;
list_for_each_entry_safe(node, tmp, node_db, mac_list) {
- list_del(&node->mac_list);
- hsr_free_node(node);
+ list_del_rcu(&node->mac_list);
+ call_rcu(&node->rcu_head, hsr_free_node_rcu);
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP
2026-05-29 23:24 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sasha Levin
2026-05-29 23:24 ` [PATCH 6.18.y 2/2] net: hsr: defer node table free until after RCU readers Sasha Levin
@ 2026-06-01 7:52 ` Sebastian Andrzej Siewior
2026-06-09 9:24 ` Felix Maurer
1 sibling, 1 reply; 6+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 7:52 UTC (permalink / raw)
To: Sasha Levin; +Cc: stable, Felix Maurer, Steffen Lindner, Paolo Abeni
On 2026-05-29 19:24:05 [-0400], Sasha Levin wrote:
> From: Felix Maurer <fmaurer@redhat.com>
>
> [ Upstream commit 415e6367512bf8faca93eaaf46fbe23d841b4509 ]
>
…
> Reported-by: Steffen Lindner <steffen.lindner@de.abb.com>
> Signed-off-by: Felix Maurer <fmaurer@redhat.com>
> Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> Tested-by: Steffen Lindner <steffen.lindner@de.abb.com>
> Link: https://patch.msgid.link/8ce15a996099df2df5b700969a39e7df400e8dbb.1770299429.git.fmaurer@redhat.com
> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
> Stable-dep-of: aaec7096f996 ("net: hsr: defer node table free until after RCU readers")
> Signed-off-by: Sasha Levin <sashal@kernel.org>
This is sort of big. It should not introduce any regressions and if so
we need to fix them anyway so early exposer might be good. However
aaec7096f9961 ("net: hsr: defer node table free until after RCU
readers") should be backported down to v5.10-stable so I wonder if
taking this huge patch is economic for this two liner fix going down the
road.
Sebastian
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP
2026-06-01 7:52 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sebastian Andrzej Siewior
@ 2026-06-09 9:24 ` Felix Maurer
2026-06-09 11:39 ` Felix Maurer
0 siblings, 1 reply; 6+ messages in thread
From: Felix Maurer @ 2026-06-09 9:24 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Sasha Levin, stable, Steffen Lindner, Paolo Abeni
On Mon, Jun 01, 2026 at 09:52:22AM +0200, Sebastian Andrzej Siewior wrote:
> On 2026-05-29 19:24:05 [-0400], Sasha Levin wrote:
> > From: Felix Maurer <fmaurer@redhat.com>
> >
> > [ Upstream commit 415e6367512bf8faca93eaaf46fbe23d841b4509 ]
> >
> …
> > Reported-by: Steffen Lindner <steffen.lindner@de.abb.com>
> > Signed-off-by: Felix Maurer <fmaurer@redhat.com>
> > Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > Tested-by: Steffen Lindner <steffen.lindner@de.abb.com>
> > Link: https://patch.msgid.link/8ce15a996099df2df5b700969a39e7df400e8dbb.1770299429.git.fmaurer@redhat.com
> > Signed-off-by: Paolo Abeni <pabeni@redhat.com>
> > Stable-dep-of: aaec7096f996 ("net: hsr: defer node table free until after RCU readers")
> > Signed-off-by: Sasha Levin <sashal@kernel.org>
>
> This is sort of big. It should not introduce any regressions and if so
> we need to fix them anyway so early exposer might be good. However
> aaec7096f9961 ("net: hsr: defer node table free until after RCU
> readers") should be backported down to v5.10-stable so I wonder if
> taking this huge patch is economic for this two liner fix going down the
> road.
I agree, taking the big commit above down to v5.10 probably isn't a good
idea, just for this fix. It should be easy to build a small version of
415e6367512b ("hsr: Implement more robust duplicate discard for PRP")
that just has the changes needed to apply (a slightly modified version
of) aaec7096f996 ("net: hsr: defer node table free until after RCU
readers"), i.e., the changes that introduce list_del() and thereby fix
that the nodes were just freed before 415e6367512b and not removed from
the list at all (which would be another UAF). That should apply to 6.18
and probably to most releases below as well.
I don't really know the guidelines around the stable trees. Do we want
such (heavily modfied) patches so that others can be applied?
Thanks,
Felix
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP
2026-06-09 9:24 ` Felix Maurer
@ 2026-06-09 11:39 ` Felix Maurer
0 siblings, 0 replies; 6+ messages in thread
From: Felix Maurer @ 2026-06-09 11:39 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Sasha Levin, stable, Steffen Lindner, Paolo Abeni
On Tue, Jun 09, 2026 at 11:24:34AM +0200, Felix Maurer wrote:
> I don't really know the guidelines around the stable trees. Do we want
> such (heavily modfied) patches so that others can be applied?
I only now saw the patches Sasha posted for the older stable trees (like
[1] for 6.12.y). I think we should do the same thing for 6.18.y as well
and not backport the rather big 415e6367512b ("hsr: Implement more
robust duplicate discard for PRP").
Thanks,
Felix
[1]: https://lore.kernel.org/stable/20260529233922.1913663-1-sashal@kernel.org/
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-09 11:39 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-28 7:53 FAILED: patch "[PATCH] net: hsr: defer node table free until after RCU readers" failed to apply to 6.18-stable tree gregkh
2026-05-29 23:24 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sasha Levin
2026-05-29 23:24 ` [PATCH 6.18.y 2/2] net: hsr: defer node table free until after RCU readers Sasha Levin
2026-06-01 7:52 ` [PATCH 6.18.y 1/2] hsr: Implement more robust duplicate discard for PRP Sebastian Andrzej Siewior
2026-06-09 9:24 ` Felix Maurer
2026-06-09 11:39 ` Felix Maurer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).