--- /dev/null
+From ff24f2ecfd94c07a2b89bac497433e3b23271cac Mon Sep 17 00:00:00 2001
+From: Sven Eckelmann <sven@narfation.org>
+Date: Sat, 16 May 2026 12:33:41 +0200
+Subject: batman-adv: tp_meter: avoid role confusion in tp_list
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit ff24f2ecfd94c07a2b89bac497433e3b23271cac upstream.
+
+Session lookups in tp_list matched only on destination address (and
+optionally session ID), leaving role validation to the caller. If two
+sessions with the same other_end coexisted (one as sender, one as receiver)
+a lookup could silently return the wrong one, causing the caller's role to
+bail out early, potentially skipping necessary cleanup.
+
+Move the role check into the lookup functions themselves so the correct
+entry is always returned, or none at all. Since batadv_tp_start()
+legitimately needs to detect any active session to a destination regardless
+of role, introduce a dedicated helper for that case rather than bending the
+existing lookup semantics.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/batman-adv/tp_meter.c | 59 ++++++++++++++++++++++++++++------------------
+ 1 file changed, 36 insertions(+), 23 deletions(-)
+
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -255,6 +255,7 @@ static void batadv_tp_batctl_error_notif
+ * batadv_tp_list_find() - find a tp_vars object in the global list
+ * @bat_priv: the bat priv with all the soft interface information
+ * @dst: the other endpoint MAC address to look for
++ * @role: role of the session
+ *
+ * Look for a tp_vars object matching dst as end_point and return it after
+ * having increment the refcounter. Return NULL is not found
+@@ -262,7 +263,8 @@ static void batadv_tp_batctl_error_notif
+ * Return: matching tp_vars or NULL when no tp_vars with @dst was found
+ */
+ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+- const u8 *dst)
++ const u8 *dst,
++ enum batadv_tp_meter_role role)
+ {
+ struct batadv_tp_vars *pos, *tp_vars = NULL;
+
+@@ -271,6 +273,9 @@ static struct batadv_tp_vars *batadv_tp_
+ if (!batadv_compare_eth(pos->other_end, dst))
+ continue;
+
++ if (pos->role != role)
++ continue;
++
+ /* most of the time this function is invoked during the normal
+ * process..it makes sens to pay more when the session is
+ * finished and to speed the process up during the measurement
+@@ -287,11 +292,32 @@ static struct batadv_tp_vars *batadv_tp_
+ }
+
+ /**
++ * batadv_tp_list_active() - check if session from/to destination is ongoing
++ * @bat_priv: the bat priv with all the mesh interface information
++ * @dst: the other endpoint MAC address to look for
++ *
++ * Return: if matching session with @dst was found
++ */
++static bool batadv_tp_list_active(struct batadv_priv *bat_priv, const u8 *dst)
++ __must_hold(&bat_priv->tp_list_lock)
++{
++ struct batadv_tp_vars *tp_vars;
++
++ hlist_for_each_entry_rcu(tp_vars, &bat_priv->tp_list, list) {
++ if (batadv_compare_eth(tp_vars->other_end, dst))
++ return true;
++ }
++
++ return false;
++}
++
++/**
+ * batadv_tp_list_find_session() - find tp_vars session object in the global
+ * list
+ * @bat_priv: the bat priv with all the soft interface information
+ * @dst: the other endpoint MAC address to look for
+ * @session: session identifier
++ * @role: role of the session
+ *
+ * Look for a tp_vars object matching dst as end_point, session as tp meter
+ * session and return it after having increment the refcounter. Return NULL
+@@ -301,7 +327,7 @@ static struct batadv_tp_vars *batadv_tp_
+ */
+ static struct batadv_tp_vars *
+ batadv_tp_list_find_session(struct batadv_priv *bat_priv, const u8 *dst,
+- const u8 *session)
++ const u8 *session, enum batadv_tp_meter_role role)
+ {
+ struct batadv_tp_vars *pos, *tp_vars = NULL;
+
+@@ -313,6 +339,9 @@ batadv_tp_list_find_session(struct batad
+ if (memcmp(pos->session, session, sizeof(pos->session)) != 0)
+ continue;
+
++ if (pos->role != role)
++ continue;
++
+ /* most of the time this function is invoked during the normal
+ * process..it makes sense to pay more when the session is
+ * finished and to speed the process up during the measurement
+@@ -665,13 +694,10 @@ static void batadv_tp_recv_ack(struct ba
+
+ /* find the tp_vars */
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+- icmp->session);
++ icmp->session, BATADV_TP_SENDER);
+ if (unlikely(!tp_vars))
+ return;
+
+- if (unlikely(tp_vars->role != BATADV_TP_SENDER))
+- goto out;
+-
+ if (unlikely(batadv_tp_sender_stopped(tp_vars)))
+ goto out;
+
+@@ -980,10 +1006,8 @@ void batadv_tp_start(struct batadv_priv
+ return;
+ }
+
+- tp_vars = batadv_tp_list_find(bat_priv, dst);
+- if (tp_vars) {
++ if (batadv_tp_list_active(bat_priv, dst)) {
+ spin_unlock_bh(&bat_priv->tp_list_lock);
+- batadv_tp_vars_put(tp_vars);
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: test to or from the same node already ongoing, aborting\n");
+ batadv_tp_batctl_error_notify(BATADV_TP_REASON_ALREADY_ONGOING,
+@@ -1104,18 +1128,14 @@ void batadv_tp_stop(struct batadv_priv *
+ if (!orig_node)
+ return;
+
+- tp_vars = batadv_tp_list_find(bat_priv, orig_node->orig);
++ tp_vars = batadv_tp_list_find(bat_priv, orig_node->orig, BATADV_TP_SENDER);
+ if (!tp_vars) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: trying to interrupt an already over connection\n");
+ goto out_put_orig_node;
+ }
+
+- if (unlikely(tp_vars->role != BATADV_TP_SENDER))
+- goto out_put_tp_vars;
+-
+ batadv_tp_sender_shutdown(tp_vars, return_value);
+-out_put_tp_vars:
+ batadv_tp_vars_put(tp_vars);
+ out_put_orig_node:
+ batadv_orig_node_put(orig_node);
+@@ -1371,7 +1391,7 @@ batadv_tp_init_recv(struct batadv_priv *
+ goto out_unlock;
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+- icmp->session);
++ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars)
+ goto out_unlock;
+
+@@ -1442,7 +1462,7 @@ static void batadv_tp_recv_msg(struct ba
+ }
+ } else {
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+- icmp->session);
++ icmp->session, BATADV_TP_RECEIVER);
+ if (!tp_vars) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Unexpected packet from %pM!\n",
+@@ -1451,13 +1471,6 @@ static void batadv_tp_recv_msg(struct ba
+ }
+ }
+
+- if (unlikely(tp_vars->role != BATADV_TP_RECEIVER)) {
+- batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+- "Meter: dropping packet: not expected (role=%u)\n",
+- tp_vars->role);
+- goto out;
+- }
+-
+ tp_vars->last_recv_time = jiffies;
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
--- /dev/null
+From 71dce47f0758537fff78fddb5fb0d4632d29b29f Mon Sep 17 00:00:00 2001
+From: Sven Eckelmann <sven@narfation.org>
+Date: Wed, 13 May 2026 23:38:54 +0200
+Subject: batman-adv: tp_meter: fix race condition in send error reporting
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 71dce47f0758537fff78fddb5fb0d4632d29b29f upstream.
+
+batadv_tp_sender_shutdown() previously used two separate variables to track
+session state: sending (an atomic flag indicating whether the session was
+active) and reason (a plain enum storing the stop reason). This introduced
+a race window between the two writes: after sending was cleared to 0,
+batadv_tp_send() could observe the stopped state and call
+batadv_tp_sender_end() before reason was written, causing the wrong stop
+reason to be reported to the caller.
+
+Fix this by consolidating both variables into a single atomic send_result,
+which holds 0 while the session is running and the stop reason once it
+ends.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/batman-adv/tp_meter.c | 40 +++++++++++++++++++++++++---------------
+ net/batman-adv/types.h | 10 +++++-----
+ 2 files changed, 30 insertions(+), 20 deletions(-)
+
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -413,11 +413,14 @@ static void batadv_tp_sender_cleanup(str
+ static void batadv_tp_sender_end(struct batadv_priv *bat_priv,
+ struct batadv_tp_vars *tp_vars)
+ {
++ enum batadv_tp_meter_reason reason;
+ u32 session_cookie;
+
++ reason = atomic_read(&tp_vars->send_result);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Test towards %pM finished..shutting down (reason=%d)\n",
+- tp_vars->other_end, tp_vars->reason);
++ tp_vars->other_end, reason);
+
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Last timing stats: SRTT=%ums RTTVAR=%ums RTO=%ums\n",
+@@ -430,7 +433,7 @@ static void batadv_tp_sender_end(struct
+ session_cookie = batadv_tp_session_cookie(tp_vars->session,
+ tp_vars->icmp_uid);
+
+- batadv_tp_batctl_notify(tp_vars->reason,
++ batadv_tp_batctl_notify(reason,
+ tp_vars->other_end,
+ bat_priv,
+ tp_vars->start_time,
+@@ -446,10 +449,18 @@ static void batadv_tp_sender_end(struct
+ static void batadv_tp_sender_shutdown(struct batadv_tp_vars *tp_vars,
+ enum batadv_tp_meter_reason reason)
+ {
+- if (atomic_xchg(&tp_vars->sending, 0) != 1)
+- return;
++ atomic_cmpxchg(&tp_vars->send_result, 0, reason);
++}
+
+- tp_vars->reason = reason;
++/**
++ * batadv_tp_sender_stopped() - check if tp session was stopped with reason
++ * @tp_vars: the private data of the current TP meter session
++ *
++ * Return: whether stop reason was found
++ */
++static bool batadv_tp_sender_stopped(struct batadv_tp_vars *tp_vars)
++{
++ return atomic_read(&tp_vars->send_result) != 0;
+ }
+
+ /**
+@@ -479,7 +490,7 @@ static void batadv_tp_reset_sender_timer
+ /* most of the time this function is invoked while normal packet
+ * reception...
+ */
+- if (unlikely(atomic_read(&tp_vars->sending) == 0))
++ if (unlikely(batadv_tp_sender_stopped(tp_vars)))
+ /* timer ref will be dropped in batadv_tp_sender_cleanup */
+ return;
+
+@@ -499,7 +510,7 @@ static void batadv_tp_sender_timeout(str
+ struct batadv_tp_vars *tp_vars = from_timer(tp_vars, t, timer);
+ struct batadv_priv *bat_priv = tp_vars->bat_priv;
+
+- if (atomic_read(&tp_vars->sending) == 0)
++ if (batadv_tp_sender_stopped(tp_vars))
+ return;
+
+ /* if the user waited long enough...shutdown the test */
+@@ -661,7 +672,7 @@ static void batadv_tp_recv_ack(struct ba
+ if (unlikely(tp_vars->role != BATADV_TP_SENDER))
+ goto out;
+
+- if (unlikely(atomic_read(&tp_vars->sending) == 0))
++ if (unlikely(batadv_tp_sender_stopped(tp_vars)))
+ goto out;
+
+ /* old ACK? silently drop it.. */
+@@ -827,21 +838,21 @@ static int batadv_tp_send(void *arg)
+
+ if (unlikely(tp_vars->role != BATADV_TP_SENDER)) {
+ err = BATADV_TP_REASON_DST_UNREACHABLE;
+- tp_vars->reason = err;
++ batadv_tp_sender_shutdown(tp_vars, err);
+ goto out;
+ }
+
+ orig_node = batadv_orig_hash_find(bat_priv, tp_vars->other_end);
+ if (unlikely(!orig_node)) {
+ err = BATADV_TP_REASON_DST_UNREACHABLE;
+- tp_vars->reason = err;
++ batadv_tp_sender_shutdown(tp_vars, err);
+ goto out;
+ }
+
+ primary_if = batadv_primary_if_get_selected(bat_priv);
+ if (unlikely(!primary_if)) {
+ err = BATADV_TP_REASON_DST_UNREACHABLE;
+- tp_vars->reason = err;
++ batadv_tp_sender_shutdown(tp_vars, err);
+ goto out;
+ }
+
+@@ -860,7 +871,7 @@ static int batadv_tp_send(void *arg)
+ queue_delayed_work(batadv_event_workqueue, &tp_vars->finish_work,
+ msecs_to_jiffies(tp_vars->test_length));
+
+- while (atomic_read(&tp_vars->sending) != 0) {
++ while (!batadv_tp_sender_stopped(tp_vars)) {
+ if (unlikely(!batadv_tp_avail(tp_vars, payload_len))) {
+ batadv_tp_wait_available(tp_vars, payload_len);
+ continue;
+@@ -883,8 +894,7 @@ static int batadv_tp_send(void *arg)
+ "Meter: %s() cannot send packets (%d)\n",
+ __func__, err);
+ /* ensure nobody else tries to stop the thread now */
+- if (atomic_xchg(&tp_vars->sending, 0) == 1)
+- tp_vars->reason = err;
++ batadv_tp_sender_shutdown(tp_vars, err);
+ break;
+ }
+
+@@ -1006,7 +1016,7 @@ void batadv_tp_start(struct batadv_priv
+ ether_addr_copy(tp_vars->other_end, dst);
+ kref_init(&tp_vars->refcount);
+ tp_vars->role = BATADV_TP_SENDER;
+- atomic_set(&tp_vars->sending, 1);
++ atomic_set(&tp_vars->send_result, 0);
+ memcpy(tp_vars->session, session_id, sizeof(session_id));
+ tp_vars->icmp_uid = icmp_uid;
+
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1397,15 +1397,15 @@ struct batadv_tp_vars {
+ /** @role: receiver/sender modi */
+ enum batadv_tp_meter_role role;
+
+- /** @sending: sending binary semaphore: 1 if sending, 0 is not */
+- atomic_t sending;
++ /**
++ * @send_result: 0 when sending is ongoing and otherwise
++ * enum batadv_tp_meter_reason
++ */
++ atomic_t send_result;
+
+ /** @receiving: receiving binary semaphore: 1 if receiving, 0 is not */
+ atomic_t receiving;
+
+- /** @reason: reason for a stopped session */
+- enum batadv_tp_meter_reason reason;
+-
+ /** @finish_work: work item for the finishing procedure */
+ struct delayed_work finish_work;
+
media-rc-igorplugusb-fix-control-request-setup-packet.patch
bluetooth-mgmt-fix-backward-compatibility-with-userspace.patch
ksmbd-oob-read-regression-in-smb_check_perm_dacl-ace-walk-loops.patch
+batman-adv-tp_meter-fix-race-condition-in-send-error-reporting.patch
+batman-adv-tp_meter-avoid-role-confusion-in-tp_list.patch