--- /dev/null
+From ff876916d00d800ff39013e984e4ac836fc253f5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:15:58 +0200
+Subject: batman-adv: bla: avoid double decrement of bla.num_requests
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 83ab69bd12b80f6ea169c8bea6977701b53a043d upstream.
+
+The bla.num_requests is increased when no request_sent was in progress. And
+it is decremented in various places (announcement was received, backbone is
+purged, periodic work). But the check if the request_sent is actually set
+to a specific state and the atomic_dec/_inc are not safe because they are
+not atomic (TOCTOU) and multiple such code portions can run concurrently.
+
+At the same time, it is necessary to modify request_sent (state) and
+bla.num_requests atomically. Otherwise batadv_bla_send_request() might set
+request_sent to 1 and is interrupted. batadv_handle_announce() can then
+set request_sent back to 0 and decrement num_requests before
+batadv_bla_send_request() incremented it.
+
+The two operations must therefore be locked. And since state (request_sent)
+and wait_periods are only accessed inside this lock, they can be converted
+to simpler datatypes. And to avoid that the bla.num_requests is touched by
+a parallel running context with a valid backbone_gw reference after
+batadv_bla_purge_backbone_gw() ran, a third state "stopped" is required to
+correctly signal that a backbone_gw is in the state of being cleaned up.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 51 ++++++++++++++++++--------
+ net/batman-adv/soft-interface.c | 1 +
+ net/batman-adv/types.h | 39 ++++++++++++++++----
+ 3 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index d01c636024ce15..8354e5473dc698 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -521,8 +521,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+- atomic_set(&entry->request_sent, 0);
+- atomic_set(&entry->wait_periods, 0);
++ entry->state = BATADV_BLA_BACKBONE_GW_SYNCED;
++ entry->wait_periods = 0;
+ ether_addr_copy(entry->orig, orig);
+ INIT_WORK(&entry->report_work, batadv_bla_loopdetect_report);
+ kref_init(&entry->refcount);
+@@ -551,9 +551,13 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ batadv_bla_send_announce(bat_priv, entry);
+
+ /* this will be decreased in the worker thread */
+- atomic_inc(&entry->request_sent);
+- atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS);
+- atomic_inc(&bat_priv->bla.num_requests);
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (entry->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ entry->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
++ entry->wait_periods = BATADV_BLA_WAIT_PERIODS;
++ atomic_inc(&bat_priv->bla.num_requests);
++ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ return entry;
+@@ -656,10 +660,12 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw)
+ backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST);
+
+ /* no local broadcasts should be sent or received, for now. */
+- if (!atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
+ atomic_inc(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 1);
+ }
++ spin_unlock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
+ }
+
+ /**
+@@ -880,10 +886,12 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ /* if we have sent a request and the crc was OK,
+ * we can allow traffic again.
+ */
+- if (atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
+ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ batadv_backbone_gw_put(backbone_gw);
+@@ -1262,9 +1270,13 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ purged = true;
+
+ /* don't wait for the pending request anymore */
+- if (atomic_read(&backbone_gw->request_sent))
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED)
+ atomic_dec(&bat_priv->bla.num_requests);
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_STOPPED;
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
++
+ batadv_bla_del_backbone_claims(backbone_gw);
+
+ hlist_del_rcu(&backbone_gw->hash_entry);
+@@ -1515,7 +1527,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ batadv_bla_send_loopdetect(bat_priv,
+ backbone_gw);
+
+- /* request_sent is only set after creation to avoid
++ /* state is only set to unsynced after creation to avoid
+ * problems when we are not yet known as backbone gw
+ * in the backbone.
+ *
+@@ -1524,14 +1536,21 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ * some grace time.
+ */
+
+- if (atomic_read(&backbone_gw->request_sent) == 0)
+- continue;
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state != BATADV_BLA_BACKBONE_GW_UNSYNCED)
++ goto unlock_next;
+
+- if (!atomic_dec_and_test(&backbone_gw->wait_periods))
+- continue;
++ if (backbone_gw->wait_periods > 0)
++ backbone_gw->wait_periods--;
++
++ if (backbone_gw->wait_periods > 0)
++ goto unlock_next;
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
++
++unlock_next:
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+ rcu_read_unlock();
+ }
+diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
+index 0e6d0a5e684130..c8c62b0cd54c76 100644
+--- a/net/batman-adv/soft-interface.c
++++ b/net/batman-adv/soft-interface.c
+@@ -822,6 +822,7 @@ static int batadv_softif_init_late(struct net_device *dev)
+ atomic_set(&bat_priv->tt.ogm_append_cnt, 0);
+ #ifdef CONFIG_BATMAN_ADV_BLA
+ atomic_set(&bat_priv->bla.num_requests, 0);
++ spin_lock_init(&bat_priv->bla.num_requests_lock);
+ #endif
+ atomic_set(&bat_priv->tp_num, 0);
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index aee90f613e13c5..86c149d5b52ae2 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1036,6 +1036,12 @@ struct batadv_priv_bla {
+ /** @num_requests: number of bla requests in flight */
+ atomic_t num_requests;
+
++ /**
++ * @num_requests_lock: locks update num_requests +
++ * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
++ */
++ spinlock_t num_requests_lock;
++
+ /**
+ * @claim_hash: hash table containing mesh nodes this host has claimed
+ */
+@@ -1815,6 +1821,27 @@ struct batadv_socket_packet {
+
+ #ifdef CONFIG_BATMAN_ADV_BLA
+
++enum batadv_bla_backbone_gw_state {
++ /**
++ * @BATADV_BLA_BACKBONE_GW_STOPPED: backbone gw is being removed
++ * and it must not longer work on requests
++ */
++ BATADV_BLA_BACKBONE_GW_STOPPED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_UNSYNCED: backbone was detected out
++ * of sync and a request was send. No traffic is forwarded until the
++ * situation is resolved
++ */
++ BATADV_BLA_BACKBONE_GW_UNSYNCED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_SYNCED: backbone is consider to be in
++ * sync. traffic can be forwarded
++ */
++ BATADV_BLA_BACKBONE_GW_SYNCED,
++};
++
+ /**
+ * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN
+ */
+@@ -1840,16 +1867,12 @@ struct batadv_bla_backbone_gw {
+ /**
+ * @wait_periods: grace time for bridge forward delays and bla group
+ * forming at bootup phase - no bcast traffic is formwared until it has
+- * elapsed
++ * elapsed. Must only be access with num_requests_lock.
+ */
+- atomic_t wait_periods;
++ u8 wait_periods;
+
+- /**
+- * @request_sent: if this bool is set to true we are out of sync with
+- * this backbone gateway - no bcast traffic is formwared until the
+- * situation was resolved
+- */
+- atomic_t request_sent;
++ /** @state: sync state. Must only be access with num_requests_lock. */
++ enum batadv_bla_backbone_gw_state state;
+
+ /** @crc: crc16 checksum over all claims */
+ u16 crc;
+--
+2.53.0
+
--- /dev/null
+From fd95b14c518fe2e2e38b20e176c79bd03bca87f7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:16:21 +0200
+Subject: batman-adv: bla: avoid NULL-ptr deref for claim via dropped interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f80d3d98d2ff78d9e2fe5d68b1f45948c4f7bd24 upstream.
+
+Without rtnl_lock held, a hardif might be retrieved as primary interface of
+a meshif, but then (while operating on this interface) getting decoupled
+from the mesh interface. In this case, the meshif still exists but the
+pointer from the primary hardif to the meshif is set to NULL.
+
+The mesh_iface must be checked first to be non-NULL before continuing to
+send an ARP request using meshif.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Reported-by: Ido Schimmel <idosch@nvidia.com>
+Reported-by: syzbot+9fdcc9f05a98a540b816@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=9fdcc9f05a98a540b816
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index d8305961b59bd1..d01c636024ce15 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -357,12 +357,14 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, u8 *mac,
+ sizeof(local_claim_dest));
+ local_claim_dest.type = claimtype;
+
+- soft_iface = primary_if->soft_iface;
++ soft_iface = READ_ONCE(primary_if->soft_iface);
++ if (!soft_iface)
++ goto out;
+
+ skb = arp_create(ARPOP_REPLY, ETH_P_ARP,
+ /* IP DST: 0.0.0.0 */
+ zeroip,
+- primary_if->soft_iface,
++ soft_iface,
+ /* IP SRC: 0.0.0.0 */
+ zeroip,
+ /* Ethernet DST: Broadcast */
+--
+2.53.0
+
--- /dev/null
+From dd3ad9e8a19ef319d3cc55b36ac980390f6bbb41 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:15:29 +0200
+Subject: batman-adv: iv: recover OGM scheduling after forward packet error
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit aa3153bd139a6c48667dcd02608d3b2c80bff02c upstream.
+
+When batadv_iv_ogm_schedule_buff() fails to allocate and queue a forward
+packet for OGM transmission, the work item that drives periodic OGM
+scheduling is never re-armed. This silently halts transmission of the
+node's own OGMs on the affected interface — only OGMs from other peers
+continue to be aggregated and forwarded.
+
+Fix this by tracking whether batadv_iv_ogm_queue_add() (and transitively
+batadv_iv_ogm_aggregate_new()) successfully scheduled a forward packet.
+When scheduling fails, batadv_iv_ogm_schedule_buff() falls back to queuing
+a dedicated recovery work item (reschedule_work) that fires after one
+originator interval and calls batadv_iv_ogm_schedule() again.
+
+Cc: stable@kernel.org
+Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 76 +++++++++++++++++++++++++++----------
+ net/batman-adv/types.h | 3 ++
+ 2 files changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 9cd2fa751c21ef..93c2c5f6facc0d 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -225,6 +225,8 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ hard_iface->bat_iv.ogm_buff = NULL;
+
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
++
++ cancel_delayed_work_sync(&hard_iface->bat_iv.reschedule_work);
+ }
+
+ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface)
+@@ -530,8 +532,10 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet,
+ * @if_incoming: interface where the packet was received
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
++static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ int packet_len, unsigned long send_time,
+ bool direct_link,
+ struct batadv_hard_iface *if_incoming,
+@@ -555,13 +559,13 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+
+ skb = netdev_alloc_skb_ip_align(NULL, skb_size);
+ if (!skb)
+- return;
++ return false;
+
+ forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing,
+ queue_left, bat_priv, skb);
+ if (!forw_packet_aggr) {
+ kfree_skb(skb);
+- return;
++ return false;
+ }
+
+ forw_packet_aggr->skb->priority = TC_PRIO_CONTROL;
+@@ -583,6 +587,8 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ batadv_iv_send_outstanding_bat_ogm_packet);
+
+ batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
++
++ return true;
+ }
+
+ /* aggregate a new packet into the existing ogm packet */
+@@ -612,8 +618,10 @@ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr,
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
+ * @send_time: timestamp (jiffies) when the packet is to be sent
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
++static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ unsigned char *packet_buff,
+ int packet_len,
+ struct batadv_hard_iface *if_incoming,
+@@ -665,14 +673,16 @@ static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ if (!own_packet && atomic_read(&bat_priv->aggregated_ogms))
+ send_time += max_aggregation_jiffies;
+
+- batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
+- send_time, direct_link,
+- if_incoming, if_outgoing,
+- own_packet);
++ return batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
++ send_time, direct_link,
++ if_incoming, if_outgoing,
++ own_packet);
+ } else {
+ batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff,
+ packet_len, direct_link);
+ spin_unlock_bh(&bat_priv->forw_bat_list_lock);
++
++ return true;
+ }
+ }
+
+@@ -784,6 +794,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ bool reschedule = false;
++ bool scheduled;
+ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+@@ -812,11 +824,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ ogm_buff_len,
+ BATADV_OGM_HLEN);
+ if (ret < 0) {
+- /* OGMs must be queued even when the buffer allocation for
+- * TVLVs failed. just fall back to the non-TVLV version
+- */
+- ret = 0;
+- *ogm_buff_len = BATADV_OGM_HLEN;
++ reschedule = true;
++ goto out;
+ }
+
+ tvlv_len = ret;
+@@ -838,8 +847,11 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ /* OGMs from secondary interfaces are only scheduled on their
+ * respective interfaces.
+ */
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
+- hard_iface, hard_iface, 1, send_time);
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
++ hard_iface, hard_iface, 1, send_time);
++ if (!scheduled)
++ reschedule = true;
++
+ goto out;
+ }
+
+@@ -854,15 +866,28 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ if (!kref_get_unless_zero(&tmp_hard_iface->refcount))
+ continue;
+
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
+- *ogm_buff_len, hard_iface,
+- tmp_hard_iface, 1, send_time);
+-
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
++ *ogm_buff_len, hard_iface,
++ tmp_hard_iface, 1, send_time);
+ batadv_hardif_put(tmp_hard_iface);
++
++ if (!scheduled && tmp_hard_iface == hard_iface)
++ reschedule = true;
+ }
+ rcu_read_unlock();
+
+ out:
++ if (reschedule) {
++ /* there was a failure scheduling the own forward packet.
++ * as result, the batadv_iv_send_outstanding_bat_ogm_packet()
++ * work item is no longer scheduled. it is therefore necessary
++ * to reschedule it manually
++ */
++ queue_delayed_work(batadv_event_workqueue,
++ &hard_iface->bat_iv.reschedule_work,
++ msecs_to_jiffies(atomic_read(&bat_priv->orig_interval)));
++ }
++
+ if (primary_if)
+ batadv_hardif_put(primary_if);
+ }
+@@ -878,6 +903,17 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface)
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
+ }
+
++static void batadv_iv_ogm_reschedule(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct batadv_hard_iface *hard_iface;
++
++ hard_iface = container_of(delayed_work,
++ struct batadv_hard_iface,
++ bat_iv.reschedule_work);
++ batadv_iv_ogm_schedule(hard_iface);
++}
++
+ /**
+ * batadv_iv_orig_ifinfo_sum() - Get bcast_own sum for originator over interface
+ * @orig_node: originator which reproadcasted the OGMs directly
+@@ -2443,6 +2479,8 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1,
+
+ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface)
+ {
++ INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule);
++
+ /* begin scheduling originator messages on that interface */
+ batadv_iv_ogm_schedule(hard_iface);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index f7e5a8f7570a31..288fe330946fb4 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -83,6 +83,9 @@ struct batadv_hard_iface_bat_iv {
+ /** @ogm_seqno: OGM sequence number - used to identify each OGM */
+ atomic_t ogm_seqno;
+
++ /** @reschedule_work: recover OGM schedule after schedule error */
++ struct delayed_work reschedule_work;
++
+ /** @ogm_buff_mutex: lock protecting ogm_buff and ogm_buff_len */
+ struct mutex ogm_buff_mutex;
+ };
+--
+2.53.0
+
--- /dev/null
+From 94794c40fade445b66016a69112e2625ec7a7a40 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:14:56 +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: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 59 ++++++++++++++++++++++++---------------
+ 1 file changed, 36 insertions(+), 23 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index b93cb86129769c..ebefad5822eea0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -253,6 +253,7 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason,
+ * 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
+@@ -260,7 +261,8 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason,
+ * 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;
+
+@@ -269,6 +271,9 @@ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+ 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
+@@ -284,12 +289,33 @@ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+ return tp_vars;
+ }
+
++/**
++ * 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
+@@ -299,7 +325,7 @@ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+ */
+ 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;
+
+@@ -311,6 +337,9 @@ batadv_tp_list_find_session(struct batadv_priv *bat_priv, const u8 *dst,
+ 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
+@@ -654,13 +683,10 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+
+ /* 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;
+
+@@ -972,10 +998,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ 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,
+@@ -1094,18 +1118,14 @@ void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
+ 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);
+@@ -1367,7 +1387,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_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;
+
+@@ -1435,7 +1455,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ } 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",
+@@ -1444,13 +1464,6 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ }
+
+- 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
+--
+2.53.0
+
--- /dev/null
+From 4fc9090dfc3ef3ab7277309e85bc2eea8197ce7e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:14:34 +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")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 40 ++++++++++++++++++++++++---------------
+ net/batman-adv/types.h | 10 +++++-----
+ 2 files changed, 30 insertions(+), 20 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 578da029a7b3d8..b93cb86129769c 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -402,11 +402,14 @@ static void batadv_tp_sender_cleanup(struct batadv_priv *bat_priv,
+ 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",
+@@ -419,7 +422,7 @@ static void batadv_tp_sender_end(struct batadv_priv *bat_priv,
+ 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,
+@@ -435,10 +438,18 @@ static void batadv_tp_sender_end(struct batadv_priv *bat_priv,
+ 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;
+ }
+
+ /**
+@@ -468,7 +479,7 @@ static void batadv_tp_reset_sender_timer(struct batadv_tp_vars *tp_vars)
+ /* 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;
+
+@@ -488,7 +499,7 @@ static void batadv_tp_sender_timeout(struct timer_list *t)
+ 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 */
+@@ -650,7 +661,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ 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.. */
+@@ -819,21 +830,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;
+ }
+
+@@ -852,7 +863,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;
+@@ -875,8 +886,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;
+ }
+
+@@ -997,7 +1007,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ 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;
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 288fe330946fb4..aee90f613e13c5 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1407,11 +1407,11 @@ 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;
+-
+- /** @reason: reason for a stopped session */
+- enum batadv_tp_meter_reason reason;
++ /**
++ * @send_result: 0 when sending is ongoing and otherwise
++ * enum batadv_tp_meter_reason
++ */
++ atomic_t send_result;
+
+ /** @finish_work: work item for the finishing procedure */
+ struct delayed_work finish_work;
+--
+2.53.0
+
--- /dev/null
+From f8cdd1e62a3eb30fe9cef4199a1df26dc2003afd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:12:08 +0200
+Subject: batman-adv: tt: avoid empty VLAN responses
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit fa1bd704940b5bcbc32c0b28db9167405c8ee5e0 upstream.
+
+The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
+sending inconsistent TT TLVLs") added checks to the local (direct) TT
+response code. But the response can also be done indirectly by another node
+using the global TT state. To avoid such inconsistency states reported in
+the original fix, also avoid sending empty VLANs for replies from the
+global TT state.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 18eeeb80378dca..483121a85cf3fc 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -848,17 +848,19 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ s32 *tt_len)
+ {
+ u16 num_vlan = 0;
+- u16 num_entries = 0;
+ u16 tvlv_len = 0;
+ unsigned int change_offset;
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_orig_node_vlan *vlan;
++ u16 total_entries = 0;
+ u8 *tt_change_ptr;
++ int vlan_entries;
+
+ spin_lock_bh(&orig_node->vlan_list_lock);
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ total_entries += vlan_entries;
+ num_vlan++;
+- num_entries += atomic_read(&vlan->tt.num_entries);
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -866,7 +868,7 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+
+ /* if tt_len is negative, allocate the space needed by the full table */
+ if (*tt_len < 0)
+- *tt_len = batadv_tt_len(num_entries);
++ *tt_len = batadv_tt_len(total_entries);
+
+ if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+ *tt_len = 0;
+@@ -887,14 +889,27 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ if (vlan_entries < 1)
++ continue;
++
+ tt_vlan->vid = htons(vlan->vid);
+ tt_vlan->crc = htonl(vlan->tt.crc);
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 68abc66755f098051bd9559c3d5b7b443c390d85 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 23:06:04 +0200
+Subject: batman-adv: tt: fix TOCTOU race for reported vlans
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 94d27005016be15ffc638b2ecbc4d58805ad7b48 upstream.
+
+The local TT based TVLV is generated by first checking the number of VLANs
+which have at least one TT entry. A new buffer with the correct size for
+the VLANs is then allocated. Only then, the list of VLANs s used to fill
+the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
+is held. But the actual number of TT entries of each VLAN can still
+increase during this time - just not the number of VLANs in the list.
+
+But the prefilter used in the buffer size calculation might still cause an
+increase of the number of VLANs which need to be stored. Simply because a
+VLAN might now suddenly have at least one entry when it had none in the
+pre-alloc check - and then needs to occupy space which was not allocated.
+
+It is better to overestimate the buffer size at the beginning and then fill
+the buffer only with the VLANs which are not empty.
+
+Cc: stable@kernel.org
+Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 73f1ab4f008c4f..18eeeb80378dca 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -939,11 +939,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+- if (vlan_entries < 1)
+- continue;
+-
+- num_vlan++;
+ total_entries += vlan_entries;
++ num_vlan++;
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -967,6 +964,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+@@ -977,8 +975,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From f6c337bd4cd1b2ec09241b059ddae725aeab7b2c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:56:05 +0200
+Subject: batman-adv: tvlv: abort OGM send on tvlv append failure
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 501368506563e151b322c8c3f228b796e615b90d upstream.
+
+batadv_tvlv_container_ogm_append() could fail in two ways: a memory
+allocation failure when resizing the packet buffer, or the tvlv data
+exceeding U16_MAX bytes. In both cases the function previously returned the
+old (now stale) tvlv_value_len rather than signalling an error, causing the
+OGM/OGM2 send path to transmit a packet whose TVLV length field no longer
+matched the actual buffer contents. And because it also didn't fill in the
+new TVLV data, sending either uninitialized or corrupted data on the wire.
+
+All errors in batadv_tvlv_container_ogm_append() must be forwarded to the
+caller. And the caller must abort the send of the OGM2. For B.A.T.M.A.N.
+IV, it is currently not allowed to abort the send. The non-TVLV part of the
+OGM must be queued up instead.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 16 +++++++++++++---
+ net/batman-adv/bat_v_ogm.c | 26 ++++++++++++++------------
+ net/batman-adv/tvlv.c | 17 ++++++++++++-----
+ net/batman-adv/tvlv.h | 2 +-
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index f35665a4045123..9cd2fa751c21ef 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -784,6 +784,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+
+@@ -807,9 +808,18 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
+- ogm_buff_len,
+- BATADV_OGM_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM_HLEN);
++ if (ret < 0) {
++ /* OGMs must be queued even when the buffer allocation for
++ * TVLVs failed. just fall back to the non-TVLV version
++ */
++ ret = 0;
++ *ogm_buff_len = BATADV_OGM_HLEN;
++ }
++
++ tvlv_len = ret;
+ }
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff);
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 5f75bcf64ae56c..939aa4b303ad98 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -272,9 +272,9 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_ogm2_packet *ogm_packet;
+ struct sk_buff *skb, *skb_tmp;
+- unsigned char *ogm_buff;
+- int ogm_buff_len;
+- u16 tvlv_len = 0;
++ unsigned char **ogm_buff;
++ int *ogm_buff_len;
++ u16 tvlv_len;
+ int ret;
+
+ lockdep_assert_held(&bat_priv->bat_v.ogm_buff_mutex);
+@@ -282,25 +282,27 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING)
+ goto out;
+
+- ogm_buff = bat_priv->bat_v.ogm_buff;
+- ogm_buff_len = bat_priv->bat_v.ogm_buff_len;
++ ogm_buff = &bat_priv->bat_v.ogm_buff;
++ ogm_buff_len = &bat_priv->bat_v.ogm_buff_len;
++
+ /* tt changes have to be committed before the tvlv data is
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, &ogm_buff,
+- &ogm_buff_len,
+- BATADV_OGM2_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM2_HLEN);
++ if (ret < 0)
++ goto reschedule;
+
+- bat_priv->bat_v.ogm_buff = ogm_buff;
+- bat_priv->bat_v.ogm_buff_len = ogm_buff_len;
++ tvlv_len = ret;
+
+- skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + ogm_buff_len);
++ skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + *ogm_buff_len);
+ if (!skb)
+ goto reschedule;
+
+ skb_reserve(skb, ETH_HLEN);
+- skb_put_data(skb, ogm_buff, ogm_buff_len);
++ skb_put_data(skb, *ogm_buff, *ogm_buff_len);
+
+ ogm_packet = (struct batadv_ogm2_packet *)skb->data;
+ ogm_packet->seqno = htonl(atomic_read(&bat_priv->bat_v.ogm_seqno));
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 99fc48efde5431..75f7ea827ed9dd 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -7,6 +7,7 @@
+ #include "main.h"
+
+ #include <linux/byteorder/generic.h>
++#include <linux/errno.h>
+ #include <linux/etherdevice.h>
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+@@ -306,9 +307,10 @@ static bool batadv_tvlv_realloc_packet_buff(unsigned char **packet_buff,
+ * The ogm packet might be enlarged or shrunk depending on the current size
+ * and the size of the to-be-appended tvlv containers.
+ *
+- * Return: size of all appended tvlv containers in bytes.
++ * Return: size of all appended tvlv containers in bytes (max U16_MAX), negative
++ * if operation failed
+ */
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len)
+ {
+@@ -316,6 +318,7 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_len;
+ void *tvlv_value;
++ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+@@ -323,9 +326,12 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+-
+- if (!ret)
++ if (!ret) {
++ tvlv_len_ret = -ENOMEM;
+ goto end;
++ }
++
++ tvlv_len_ret = tvlv_value_len;
+
+ if (!tvlv_value_len)
+ goto end;
+@@ -344,7 +350,8 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ end:
+ spin_unlock_bh(&bat_priv->tvlv.container_list_lock);
+- return tvlv_value_len;
++
++ return tvlv_len_ret;
+ }
+
+ /**
+diff --git a/net/batman-adv/tvlv.h b/net/batman-adv/tvlv.h
+index d509d00c7a23e6..4823f4963df5bd 100644
+--- a/net/batman-adv/tvlv.h
++++ b/net/batman-adv/tvlv.h
+@@ -15,7 +15,7 @@
+ void batadv_tvlv_container_register(struct batadv_priv *bat_priv,
+ u8 type, u8 version,
+ void *tvlv_value, u16 tvlv_value_len);
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len);
+ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv,
+--
+2.53.0
+
--- /dev/null
+From 1eb789040cee7f7b55911ba95c226511331006b3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:13:55 +0200
+Subject: batman-adv: tvlv: reject oversized TVLV packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f50487e3566358b2b982b7801945e858c78ad9ab upstream.
+
+batadv_tvlv_container_ogm_append() builds a TVLV packet section from
+the tvlv.container_list. The total size of this section is computed by
+batadv_tvlv_container_list_size(), which sums the sizes of all registered
+containers.
+
+The return type and accumulator in batadv_tvlv_container_list_size() were
+u16. If the accumulated size exceeds U16_MAX, the value wraps around,
+causing the subsequent allocation in batadv_tvlv_container_ogm_append()
+to be undersized. The memcpy-style copy that follows would then write
+beyond the end of the allocated buffer, corrupting kernel memory.
+
+Fix this by widening the return type of batadv_tvlv_container_list_size()
+to size_t. In batadv_tvlv_container_ogm_append(), check the computed length
+against U16_MAX before proceeding, and bail out as if the allocation had
+failed when the limit is exceeded.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 75f7ea827ed9dd..8da8184a2ebdfc 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -13,6 +13,7 @@
+ #include <linux/if_ether.h>
+ #include <linux/kernel.h>
+ #include <linux/kref.h>
++#include <linux/limits.h>
+ #include <linux/list.h>
+ #include <linux/lockdep.h>
+ #include <linux/netdevice.h>
+@@ -160,10 +161,10 @@ batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version)
+ *
+ * Return: size of all currently registered tvlv containers in bytes.
+ */
+-static u16 batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
++static size_t batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
+ {
+ struct batadv_tvlv_container *tvlv;
+- u16 tvlv_len = 0;
++ size_t tvlv_len = 0;
+
+ lockdep_assert_held(&bat_priv->tvlv.container_list_lock);
+
+@@ -316,13 +317,17 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_container *tvlv;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+- u16 tvlv_value_len;
++ size_t tvlv_value_len;
+ void *tvlv_value;
+ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+ tvlv_value_len = batadv_tvlv_container_list_size(bat_priv);
++ if (tvlv_value_len > U16_MAX) {
++ tvlv_len_ret = -E2BIG;
++ goto end;
++ }
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+--
+2.53.0
+
--- /dev/null
+From a128c818ba38abae4cd7923931eb42c718b37b55 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:36:39 +0200
+Subject: batman-adv: v: stop OGMv2 on disabled interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f8ce8b8331a1bc44ad4905886a482214d428b253 upstream.
+
+When a batadv_hard_iface is disabled, its mesh_iface pointer is set to
+NULL. However, batadv_v_ogm_send_meshif() may still dispatch OGMs via
+batadv_v_ogm_queue_on_if() for interfaces that have since lost their
+mesh_iface association. This results in a NULL pointer dereference when
+batadv_v_ogm_queue_on_if() unconditionally calls netdev_priv() on the
+now NULL hard_iface->mesh_iface to retrieve the batadv_priv.
+
+It is necessary to ensure that the batadv_v_ogm_queue_on_if() checks that
+it is using the same mesh_iface for which batadv_v_ogm_send_meshif() was
+called.
+
+Cc: stable@kernel.org
+Fixes: 0da0035942d4 ("batman-adv: OGMv2 - add basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v_ogm.c | 33 +++++++++++++++++++++------------
+ 1 file changed, 21 insertions(+), 12 deletions(-)
+
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index d43fc72af9a994..5f75bcf64ae56c 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -116,14 +116,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
+
+ /**
+ * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to send
+ * @hard_iface: the interface to use to send the OGM
+ */
+-static void batadv_v_ogm_send_to_if(struct sk_buff *skb,
++static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+-
+ if (hard_iface->if_status != BATADV_IF_ACTIVE) {
+ kfree_skb(skb);
+ return;
+@@ -190,6 +190,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+
+ /**
+ * batadv_v_ogm_aggr_send() - flush & send aggregation queue
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @hard_iface: the interface with the aggregation queue to flush
+ *
+ * Aggregates all OGMv2 packets currently in the aggregation queue into a
+@@ -199,7 +200,8 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+ *
+ * Caller needs to hold the hard_iface->bat_v.aggr_list.lock.
+ */
+-static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
++static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv,
++ struct batadv_hard_iface *hard_iface)
+ {
+ unsigned int aggr_len = hard_iface->bat_v.aggr_len;
+ struct sk_buff *skb_aggr;
+@@ -229,27 +231,32 @@ static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
+ consume_skb(skb);
+ }
+
+- batadv_v_ogm_send_to_if(skb_aggr, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface);
+ }
+
+ /**
+ * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to queue
+ * @hard_iface: the interface to queue the OGM on
+ */
+-static void batadv_v_ogm_queue_on_if(struct sk_buff *skb,
++static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
++ if (hard_iface->soft_iface != bat_priv->soft_iface) {
++ kfree_skb(skb);
++ return;
++ }
+
+ if (!atomic_read(&bat_priv->aggregated_ogms)) {
+- batadv_v_ogm_send_to_if(skb, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface);
+ return;
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
+@@ -348,7 +355,7 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ break;
+ }
+
+- batadv_v_ogm_queue_on_if(skb_tmp, hard_iface);
++ batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface);
+ batadv_hardif_put(hard_iface);
+ }
+ rcu_read_unlock();
+@@ -388,12 +395,14 @@ void batadv_v_ogm_aggr_work(struct work_struct *work)
+ {
+ struct batadv_hard_iface_bat_v *batv;
+ struct batadv_hard_iface *hard_iface;
++ struct batadv_priv *bat_priv;
+
+ batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work);
+ hard_iface = container_of(batv, struct batadv_hard_iface, bat_v);
++ bat_priv = netdev_priv(hard_iface->soft_iface);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+
+ batadv_v_ogm_start_queue_timer(hard_iface);
+@@ -583,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv,
+ if_outgoing->net_dev->name, ntohl(ogm_forward->throughput),
+ ogm_forward->ttl, if_incoming->net_dev->name);
+
+- batadv_v_ogm_queue_on_if(skb, if_outgoing);
++ batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing);
+
+ out:
+ if (orig_ifinfo)
+--
+2.53.0
+
--- /dev/null
+From 0836321027771a29176a7821e079363ff849be0d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:11:37 +0200
+Subject: RDMA/rxe: Fix double free in rxe_srq_from_init
+
+From: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+
+commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream.
+
+In rxe_srq_from_init(), the queue pointer 'q' is assigned to
+'srq->rq.queue' before copying the SRQ number to user space.
+If copy_to_user() fails, the function calls rxe_queue_cleanup()
+to free the queue, but leaves the now-invalid pointer in
+'srq->rq.queue'.
+
+The caller of rxe_srq_from_init() (rxe_create_srq) eventually
+calls rxe_srq_cleanup() upon receiving the error, which triggers
+a second rxe_queue_cleanup() on the same memory, leading to a
+double free.
+
+The call trace looks like this:
+ kmem_cache_free+0x.../0x...
+ rxe_queue_cleanup+0x1a/0x30 [rdma_rxe]
+ rxe_srq_cleanup+0x42/0x60 [rdma_rxe]
+ rxe_elem_release+0x31/0x70 [rdma_rxe]
+ rxe_create_srq+0x12b/0x1a0 [rdma_rxe]
+ ib_create_srq_user+0x9a/0x150 [ib_core]
+
+Fix this by moving 'srq->rq.queue = q' after copy_to_user.
+
+Fixes: aae0484e15f0 ("IB/rxe: avoid srq memory leak")
+Signed-off-by: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+Link: https://patch.msgid.link/20260112015412.29458-1-jiashengjiangcool@gmail.com
+Reviewed-by: Zhu Yanjun <yanjun.Zhu@linux.dev>
+Signed-off-by: Leon Romanovsky <leon@kernel.org>
+[bwh: Backported to 5.10: There was no assignment to init->attr.max_wr
+ here; don't add it]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index 41b0d1e11bafdb..4e523d91e7dcb1 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -98,8 +98,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ return -ENOMEM;
+ }
+
+- srq->rq.queue = q;
+-
+ err = do_mmap_info(rxe, uresp ? &uresp->mi : NULL, udata, q->buf,
+ q->buf_size, &q->ip);
+ if (err) {
+@@ -116,6 +114,8 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
++ srq->rq.queue = q;
++
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 2652c19f60f717d8cda243411cb78fb177179e77 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:06:43 +0200
+Subject: Revert "RDMA/rxe: Fix double free in rxe_srq_from_init"
+
+From: Ben Hutchings <benh@debian.org>
+
+This reverts commit 22b8c23a3b92d023614bb00896fe364b2c1a31d3, which
+was commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream. The
+backported version did not move but duplicated the problematic
+assignment, so it did not fix the bug. A proper backport will follow.
+
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index 9d9baca2694999..41b0d1e11bafdb 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -116,9 +116,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
+- srq->rq.queue = q;
+- init->attr.max_wr = srq->rq.max_wr;
+-
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From e25b37a2f6297fe641e3e33f9ab115732c71603a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:29:47 +0200
+Subject: selftests: forwarding: lib: Add helpers for checksum handling
+
+From: Petr Machata <petrm@nvidia.com>
+
+commit 952e0ee38c7215c45192d8c899acd1830873f28b upstream.
+
+In order to generate IGMPv3 and MLDv2 packets on the fly, we will need
+helpers to calculate the packet checksum.
+
+The approach presented in this patch revolves around payload templates
+for mausezahn. These are mausezahn-like payload strings (01:23:45:...)
+with possibly one 2-byte sequence replaced with the word PAYLOAD. The
+main function is payload_template_calc_checksum(), which calculates
+RFC 1071 checksum of the message. There are further helpers to then
+convert the checksum to the payload format, and to expand it.
+
+For IPv6, MLDv2 message checksum is computed using a pseudoheader that
+differs from the header used in the payload itself. The fact that the
+two messages are different means that the checksum needs to be
+returned as a separate quantity, instead of being expanded in-place in
+the payload itself. Furthermore, the pseudoheader includes a length of
+the message. Much like the checksum, this needs to be expanded in
+mausezahn format. And likewise for number of addresses for (S,G)
+entries. Thus we have several places where a computed quantity needs
+to be presented in the payload format. Add a helper u16_to_bytes(),
+which will be used in all these cases.
+
+Signed-off-by: Petr Machata <petrm@nvidia.com>
+Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 02cb2e6bacbb ("selftests: forwarding: vxlan_bridge_1d: fix test failure with br_netfilter enabled")
+[bwh: Backported to 5.10: adjust context]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/net/forwarding/lib.sh | 56 +++++++++++++++++++
+ 1 file changed, 56 insertions(+)
+
+diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh
+index dfb41db7fbe481..2825c779ef30e0 100644
+--- a/tools/testing/selftests/net/forwarding/lib.sh
++++ b/tools/testing/selftests/net/forwarding/lib.sh
+@@ -1298,3 +1298,59 @@ tcpdump_show()
+ {
+ tcpdump -e -n -r $capfile 2>&1
+ }
++
++u16_to_bytes()
++{
++ local u16=$1; shift
++
++ printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
++}
++
++# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
++# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
++# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
++# stands for 00:00.
++payload_template_calc_checksum()
++{
++ local payload=$1; shift
++
++ (
++ # Set input radix.
++ echo "16i"
++ # Push zero for the initial checksum.
++ echo 0
++
++ # Pad the payload with a terminating 00: in case we get an odd
++ # number of bytes.
++ echo "${payload%:}:00:" |
++ sed 's/CHECKSUM/00:00/g' |
++ tr '[:lower:]' '[:upper:]' |
++ # Add the word to the checksum.
++ sed 's/\(..\):\(..\):/\1\2+\n/g' |
++ # Strip the extra odd byte we pushed if left unconverted.
++ sed 's/\(..\):$//'
++
++ echo "10000 ~ +" # Calculate and add carry.
++ echo "FFFF r - p" # Bit-flip and print.
++ ) |
++ dc |
++ tr '[:upper:]' '[:lower:]'
++}
++
++payload_template_expand_checksum()
++{
++ local payload=$1; shift
++ local checksum=$1; shift
++
++ local ckbytes=$(u16_to_bytes $checksum)
++
++ echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
++}
++
++payload_template_nbytes()
++{
++ local payload=$1; shift
++
++ payload_template_expand_checksum "${payload%:}" 0 |
++ sed 's/:/\n/g' | wc -l
++}
+--
+2.53.0
+
bluetooth-l2cap-clear-chan-ident-on-ecred-reconfigur.patch
bluetooth-l2cap-fix-possible-crash-on-l2cap_ecred_co.patch
sctp-fix-race-between-sctp_wait_for_connect-and-peel.patch
+batman-adv-v-stop-ogmv2-on-disabled-interface.patch
+batman-adv-tvlv-abort-ogm-send-on-tvlv-append-failur.patch
+batman-adv-bla-avoid-null-ptr-deref-for-claim-via-dr.patch
+batman-adv-tvlv-reject-oversized-tvlv-packets.patch
+batman-adv-iv-recover-ogm-scheduling-after-forward-p.patch
+batman-adv-tp_meter-fix-race-condition-in-send-error.patch
+batman-adv-tp_meter-avoid-role-confusion-in-tp_list.patch
+selftests-forwarding-lib-add-helpers-for-checksum-ha.patch
+batman-adv-tt-fix-toctou-race-for-reported-vlans.patch
+batman-adv-tt-avoid-empty-vlan-responses.patch
+batman-adv-bla-avoid-double-decrement-of-bla.num_req.patch
+revert-rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch
+rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch-26331
--- /dev/null
+From daa64c8a65b7eece74a1a8c4c33018b4a7ffb742 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:08:08 +0200
+Subject: batman-adv: bla: avoid double decrement of bla.num_requests
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 83ab69bd12b80f6ea169c8bea6977701b53a043d upstream.
+
+The bla.num_requests is increased when no request_sent was in progress. And
+it is decremented in various places (announcement was received, backbone is
+purged, periodic work). But the check if the request_sent is actually set
+to a specific state and the atomic_dec/_inc are not safe because they are
+not atomic (TOCTOU) and multiple such code portions can run concurrently.
+
+At the same time, it is necessary to modify request_sent (state) and
+bla.num_requests atomically. Otherwise batadv_bla_send_request() might set
+request_sent to 1 and is interrupted. batadv_handle_announce() can then
+set request_sent back to 0 and decrement num_requests before
+batadv_bla_send_request() incremented it.
+
+The two operations must therefore be locked. And since state (request_sent)
+and wait_periods are only accessed inside this lock, they can be converted
+to simpler datatypes. And to avoid that the bla.num_requests is touched by
+a parallel running context with a valid backbone_gw reference after
+batadv_bla_purge_backbone_gw() ran, a third state "stopped" is required to
+correctly signal that a backbone_gw is in the state of being cleaned up.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 51 ++++++++++++++++++--------
+ net/batman-adv/soft-interface.c | 1 +
+ net/batman-adv/types.h | 39 ++++++++++++++++----
+ 3 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index d8ead0eb71da06..452e78fd70c039 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -515,8 +515,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+- atomic_set(&entry->request_sent, 0);
+- atomic_set(&entry->wait_periods, 0);
++ entry->state = BATADV_BLA_BACKBONE_GW_SYNCED;
++ entry->wait_periods = 0;
+ ether_addr_copy(entry->orig, orig);
+ INIT_WORK(&entry->report_work, batadv_bla_loopdetect_report);
+ kref_init(&entry->refcount);
+@@ -545,9 +545,13 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ batadv_bla_send_announce(bat_priv, entry);
+
+ /* this will be decreased in the worker thread */
+- atomic_inc(&entry->request_sent);
+- atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS);
+- atomic_inc(&bat_priv->bla.num_requests);
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (entry->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ entry->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
++ entry->wait_periods = BATADV_BLA_WAIT_PERIODS;
++ atomic_inc(&bat_priv->bla.num_requests);
++ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ return entry;
+@@ -650,10 +654,12 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw)
+ backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST);
+
+ /* no local broadcasts should be sent or received, for now. */
+- if (!atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
+ atomic_inc(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 1);
+ }
++ spin_unlock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
+ }
+
+ /**
+@@ -874,10 +880,12 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ /* if we have sent a request and the crc was OK,
+ * we can allow traffic again.
+ */
+- if (atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
+ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ batadv_backbone_gw_put(backbone_gw);
+@@ -1256,9 +1264,13 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ purged = true;
+
+ /* don't wait for the pending request anymore */
+- if (atomic_read(&backbone_gw->request_sent))
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED)
+ atomic_dec(&bat_priv->bla.num_requests);
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_STOPPED;
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
++
+ batadv_bla_del_backbone_claims(backbone_gw);
+
+ hlist_del_rcu(&backbone_gw->hash_entry);
+@@ -1509,7 +1521,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ batadv_bla_send_loopdetect(bat_priv,
+ backbone_gw);
+
+- /* request_sent is only set after creation to avoid
++ /* state is only set to unsynced after creation to avoid
+ * problems when we are not yet known as backbone gw
+ * in the backbone.
+ *
+@@ -1518,14 +1530,21 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ * some grace time.
+ */
+
+- if (atomic_read(&backbone_gw->request_sent) == 0)
+- continue;
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state != BATADV_BLA_BACKBONE_GW_UNSYNCED)
++ goto unlock_next;
+
+- if (!atomic_dec_and_test(&backbone_gw->wait_periods))
+- continue;
++ if (backbone_gw->wait_periods > 0)
++ backbone_gw->wait_periods--;
++
++ if (backbone_gw->wait_periods > 0)
++ goto unlock_next;
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
++
++unlock_next:
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+ rcu_read_unlock();
+ }
+diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
+index bff86ccadc2cc2..b46480ec7bd1d0 100644
+--- a/net/batman-adv/soft-interface.c
++++ b/net/batman-adv/soft-interface.c
+@@ -785,6 +785,7 @@ static int batadv_softif_init_late(struct net_device *dev)
+ atomic_set(&bat_priv->tt.ogm_append_cnt, 0);
+ #ifdef CONFIG_BATMAN_ADV_BLA
+ atomic_set(&bat_priv->bla.num_requests, 0);
++ spin_lock_init(&bat_priv->bla.num_requests_lock);
+ #endif
+ atomic_set(&bat_priv->tp_num, 0);
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index ff40fc9e36f66c..6b64e6b7bf80ca 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1026,6 +1026,12 @@ struct batadv_priv_bla {
+ /** @num_requests: number of bla requests in flight */
+ atomic_t num_requests;
+
++ /**
++ * @num_requests_lock: locks update num_requests +
++ * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
++ */
++ spinlock_t num_requests_lock;
++
+ /**
+ * @claim_hash: hash table containing mesh nodes this host has claimed
+ */
+@@ -1787,6 +1793,27 @@ struct batadv_socket_packet {
+
+ #ifdef CONFIG_BATMAN_ADV_BLA
+
++enum batadv_bla_backbone_gw_state {
++ /**
++ * @BATADV_BLA_BACKBONE_GW_STOPPED: backbone gw is being removed
++ * and it must not longer work on requests
++ */
++ BATADV_BLA_BACKBONE_GW_STOPPED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_UNSYNCED: backbone was detected out
++ * of sync and a request was send. No traffic is forwarded until the
++ * situation is resolved
++ */
++ BATADV_BLA_BACKBONE_GW_UNSYNCED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_SYNCED: backbone is consider to be in
++ * sync. traffic can be forwarded
++ */
++ BATADV_BLA_BACKBONE_GW_SYNCED,
++};
++
+ /**
+ * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN
+ */
+@@ -1812,16 +1839,12 @@ struct batadv_bla_backbone_gw {
+ /**
+ * @wait_periods: grace time for bridge forward delays and bla group
+ * forming at bootup phase - no bcast traffic is formwared until it has
+- * elapsed
++ * elapsed. Must only be access with num_requests_lock.
+ */
+- atomic_t wait_periods;
++ u8 wait_periods;
+
+- /**
+- * @request_sent: if this bool is set to true we are out of sync with
+- * this backbone gateway - no bcast traffic is formwared until the
+- * situation was resolved
+- */
+- atomic_t request_sent;
++ /** @state: sync state. Must only be access with num_requests_lock. */
++ enum batadv_bla_backbone_gw_state state;
+
+ /** @crc: crc16 checksum over all claims */
+ u16 crc;
+--
+2.53.0
+
--- /dev/null
+From a8197f02cd3ecacbf8778f9e5051db51d2227e6e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:08:49 +0200
+Subject: batman-adv: bla: avoid NULL-ptr deref for claim via dropped interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f80d3d98d2ff78d9e2fe5d68b1f45948c4f7bd24 upstream.
+
+Without rtnl_lock held, a hardif might be retrieved as primary interface of
+a meshif, but then (while operating on this interface) getting decoupled
+from the mesh interface. In this case, the meshif still exists but the
+pointer from the primary hardif to the meshif is set to NULL.
+
+The mesh_iface must be checked first to be non-NULL before continuing to
+send an ARP request using meshif.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Reported-by: Ido Schimmel <idosch@nvidia.com>
+Reported-by: syzbot+9fdcc9f05a98a540b816@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=9fdcc9f05a98a540b816
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index f768a4e0bc0f49..d8ead0eb71da06 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -355,12 +355,14 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, u8 *mac,
+ sizeof(local_claim_dest));
+ local_claim_dest.type = claimtype;
+
+- soft_iface = primary_if->soft_iface;
++ soft_iface = READ_ONCE(primary_if->soft_iface);
++ if (!soft_iface)
++ goto out;
+
+ skb = arp_create(ARPOP_REPLY, ETH_P_ARP,
+ /* IP DST: 0.0.0.0 */
+ zeroip,
+- primary_if->soft_iface,
++ soft_iface,
+ /* IP SRC: 0.0.0.0 */
+ zeroip,
+ /* Ethernet DST: Broadcast */
+--
+2.53.0
+
--- /dev/null
+From 66d82dbc14c35035b0c1b0e98619cf9ba3c01510 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:07:24 +0200
+Subject: batman-adv: iv: recover OGM scheduling after forward packet error
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit aa3153bd139a6c48667dcd02608d3b2c80bff02c upstream.
+
+When batadv_iv_ogm_schedule_buff() fails to allocate and queue a forward
+packet for OGM transmission, the work item that drives periodic OGM
+scheduling is never re-armed. This silently halts transmission of the
+node's own OGMs on the affected interface — only OGMs from other peers
+continue to be aggregated and forwarded.
+
+Fix this by tracking whether batadv_iv_ogm_queue_add() (and transitively
+batadv_iv_ogm_aggregate_new()) successfully scheduled a forward packet.
+When scheduling fails, batadv_iv_ogm_schedule_buff() falls back to queuing
+a dedicated recovery work item (reschedule_work) that fires after one
+originator interval and calls batadv_iv_ogm_schedule() again.
+
+Cc: stable@kernel.org
+Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 76 +++++++++++++++++++++++++++----------
+ net/batman-adv/types.h | 3 ++
+ 2 files changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index bc8685ffcb7100..669f77eed073a0 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -224,6 +224,8 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ hard_iface->bat_iv.ogm_buff = NULL;
+
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
++
++ cancel_delayed_work_sync(&hard_iface->bat_iv.reschedule_work);
+ }
+
+ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface)
+@@ -528,8 +530,10 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet,
+ * @if_incoming: interface where the packet was received
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
++static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ int packet_len, unsigned long send_time,
+ bool direct_link,
+ struct batadv_hard_iface *if_incoming,
+@@ -553,13 +557,13 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+
+ skb = netdev_alloc_skb_ip_align(NULL, skb_size);
+ if (!skb)
+- return;
++ return false;
+
+ forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing,
+ queue_left, bat_priv, skb);
+ if (!forw_packet_aggr) {
+ kfree_skb(skb);
+- return;
++ return false;
+ }
+
+ forw_packet_aggr->skb->priority = TC_PRIO_CONTROL;
+@@ -581,6 +585,8 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ batadv_iv_send_outstanding_bat_ogm_packet);
+
+ batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
++
++ return true;
+ }
+
+ /* aggregate a new packet into the existing ogm packet */
+@@ -610,8 +616,10 @@ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr,
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
+ * @send_time: timestamp (jiffies) when the packet is to be sent
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
++static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ unsigned char *packet_buff,
+ int packet_len,
+ struct batadv_hard_iface *if_incoming,
+@@ -663,14 +671,16 @@ static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ if (!own_packet && atomic_read(&bat_priv->aggregated_ogms))
+ send_time += max_aggregation_jiffies;
+
+- batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
+- send_time, direct_link,
+- if_incoming, if_outgoing,
+- own_packet);
++ return batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
++ send_time, direct_link,
++ if_incoming, if_outgoing,
++ own_packet);
+ } else {
+ batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff,
+ packet_len, direct_link);
+ spin_unlock_bh(&bat_priv->forw_bat_list_lock);
++
++ return true;
+ }
+ }
+
+@@ -782,6 +792,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ bool reschedule = false;
++ bool scheduled;
+ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+@@ -810,11 +822,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ ogm_buff_len,
+ BATADV_OGM_HLEN);
+ if (ret < 0) {
+- /* OGMs must be queued even when the buffer allocation for
+- * TVLVs failed. just fall back to the non-TVLV version
+- */
+- ret = 0;
+- *ogm_buff_len = BATADV_OGM_HLEN;
++ reschedule = true;
++ goto out;
+ }
+
+ tvlv_len = ret;
+@@ -836,8 +845,11 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ /* OGMs from secondary interfaces are only scheduled on their
+ * respective interfaces.
+ */
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
+- hard_iface, hard_iface, 1, send_time);
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
++ hard_iface, hard_iface, 1, send_time);
++ if (!scheduled)
++ reschedule = true;
++
+ goto out;
+ }
+
+@@ -852,15 +864,28 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ if (!kref_get_unless_zero(&tmp_hard_iface->refcount))
+ continue;
+
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
+- *ogm_buff_len, hard_iface,
+- tmp_hard_iface, 1, send_time);
+-
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
++ *ogm_buff_len, hard_iface,
++ tmp_hard_iface, 1, send_time);
+ batadv_hardif_put(tmp_hard_iface);
++
++ if (!scheduled && tmp_hard_iface == hard_iface)
++ reschedule = true;
+ }
+ rcu_read_unlock();
+
+ out:
++ if (reschedule) {
++ /* there was a failure scheduling the own forward packet.
++ * as result, the batadv_iv_send_outstanding_bat_ogm_packet()
++ * work item is no longer scheduled. it is therefore necessary
++ * to reschedule it manually
++ */
++ queue_delayed_work(batadv_event_workqueue,
++ &hard_iface->bat_iv.reschedule_work,
++ msecs_to_jiffies(atomic_read(&bat_priv->orig_interval)));
++ }
++
+ batadv_hardif_put(primary_if);
+ }
+
+@@ -875,6 +900,17 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface)
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
+ }
+
++static void batadv_iv_ogm_reschedule(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct batadv_hard_iface *hard_iface;
++
++ hard_iface = container_of(delayed_work,
++ struct batadv_hard_iface,
++ bat_iv.reschedule_work);
++ batadv_iv_ogm_schedule(hard_iface);
++}
++
+ /**
+ * batadv_iv_orig_ifinfo_sum() - Get bcast_own sum for originator over interface
+ * @orig_node: originator which reproadcasted the OGMs directly
+@@ -2278,6 +2314,8 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1,
+
+ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface)
+ {
++ INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule);
++
+ /* begin scheduling originator messages on that interface */
+ batadv_iv_ogm_schedule(hard_iface);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 19fd1c9adf4931..ff40fc9e36f66c 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -82,6 +82,9 @@ struct batadv_hard_iface_bat_iv {
+ /** @ogm_seqno: OGM sequence number - used to identify each OGM */
+ atomic_t ogm_seqno;
+
++ /** @reschedule_work: recover OGM schedule after schedule error */
++ struct delayed_work reschedule_work;
++
+ /** @ogm_buff_mutex: lock protecting ogm_buff and ogm_buff_len */
+ struct mutex ogm_buff_mutex;
+ };
+--
+2.53.0
+
--- /dev/null
+From 7b75765536dbc4a9a0778f10897cc9f9c8e21a5e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:27:23 +0200
+Subject: batman-adv: tp_meter: directly shut down timer on cleanup
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d5487249a81ea658717614009c8f46acc5b7101a upstream.
+
+batadv_tp_sender_cleanup() was calling timer_delete_sync() followed by
+timer_delete() to guard against the timer handler re-arming itself between
+the two calls. This double-deletion hack relied on the sending status being
+set to 0 to suppress re-arming.
+
+Replace both calls with a single timer_shutdown_sync(). This function both
+waits for any running timer callback to complete (like timer_delete_sync())
+and permanently disarms the timer so it cannot be re-armed afterwards,
+making re-arming prevention unconditional and self-documenting.
+
+The re-arming property is also required because otherwise:
+
+1. context 0 (batadv_tp_recv_ack()) checks in
+ batadv_tp_reset_sender_timer() if sending is still 1 -> it is
+2. context 1 changes in batadv_tp_sender_shutdown() sending to 0 and in
+ this process forces the kthread to stop timer in
+ batadv_tp_sender_cleanup()
+3. context 0 continues in batadv_tp_reset_sender_timer() and rearms the
+ timer -> but the reference for it is already gone
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ adapt pre-hunk to old del_timer* names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 464fca0ca8ac28..0eaeffba4b4ffe 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -384,13 +384,7 @@ static void batadv_tp_sender_cleanup(struct batadv_priv *bat_priv,
+ atomic_dec(&tp_vars->bat_priv->tp_num);
+
+ /* kill the timer and remove its reference */
+- del_timer_sync(&tp_vars->timer);
+- /* the worker might have rearmed itself therefore we kill it again. Note
+- * that if the worker should run again before invoking the following
+- * del_timer(), it would not re-arm itself once again because the status
+- * is OFF now
+- */
+- del_timer(&tp_vars->timer);
++ timer_shutdown_sync(&tp_vars->timer);
+ batadv_tp_vars_put(tp_vars);
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 1efdcd4a97c096e0812055453c0d9182de3090ab Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:02:51 +0200
+Subject: batman-adv: tt: avoid empty VLAN responses
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit fa1bd704940b5bcbc32c0b28db9167405c8ee5e0 upstream.
+
+The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
+sending inconsistent TT TLVLs") added checks to the local (direct) TT
+response code. But the response can also be done indirectly by another node
+using the global TT state. To avoid such inconsistency states reported in
+the original fix, also avoid sending empty VLANs for replies from the
+global TT state.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 11c39a9ab90e46..e4d55b27f2551b 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -843,17 +843,19 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ s32 *tt_len)
+ {
+ u16 num_vlan = 0;
+- u16 num_entries = 0;
+ u16 tvlv_len = 0;
+ unsigned int change_offset;
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_orig_node_vlan *vlan;
++ u16 total_entries = 0;
+ u8 *tt_change_ptr;
++ int vlan_entries;
+
+ spin_lock_bh(&orig_node->vlan_list_lock);
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ total_entries += vlan_entries;
+ num_vlan++;
+- num_entries += atomic_read(&vlan->tt.num_entries);
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -861,7 +863,7 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+
+ /* if tt_len is negative, allocate the space needed by the full table */
+ if (*tt_len < 0)
+- *tt_len = batadv_tt_len(num_entries);
++ *tt_len = batadv_tt_len(total_entries);
+
+ if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+ *tt_len = 0;
+@@ -882,14 +884,27 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ if (vlan_entries < 1)
++ continue;
++
+ tt_vlan->vid = htons(vlan->vid);
+ tt_vlan->crc = htonl(vlan->tt.crc);
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 3aae4099e4f746a570e865fa8bb96f34889a2f00 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 23:04:23 +0200
+Subject: batman-adv: tt: fix TOCTOU race for reported vlans
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 94d27005016be15ffc638b2ecbc4d58805ad7b48 upstream.
+
+The local TT based TVLV is generated by first checking the number of VLANs
+which have at least one TT entry. A new buffer with the correct size for
+the VLANs is then allocated. Only then, the list of VLANs s used to fill
+the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
+is held. But the actual number of TT entries of each VLAN can still
+increase during this time - just not the number of VLANs in the list.
+
+But the prefilter used in the buffer size calculation might still cause an
+increase of the number of VLANs which need to be stored. Simply because a
+VLAN might now suddenly have at least one entry when it had none in the
+pre-alloc check - and then needs to occupy space which was not allocated.
+
+It is better to overestimate the buffer size at the beginning and then fill
+the buffer only with the VLANs which are not empty.
+
+Cc: stable@kernel.org
+Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 76cf40d859908a..11c39a9ab90e46 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -934,11 +934,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+- if (vlan_entries < 1)
+- continue;
+-
+- num_vlan++;
+ total_entries += vlan_entries;
++ num_vlan++;
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -962,6 +959,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+@@ -972,8 +970,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From f500a77ad17a1fac80aea37f4ab5f19476ccf530 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:53:56 +0200
+Subject: batman-adv: tvlv: abort OGM send on tvlv append failure
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 501368506563e151b322c8c3f228b796e615b90d upstream.
+
+batadv_tvlv_container_ogm_append() could fail in two ways: a memory
+allocation failure when resizing the packet buffer, or the tvlv data
+exceeding U16_MAX bytes. In both cases the function previously returned the
+old (now stale) tvlv_value_len rather than signalling an error, causing the
+OGM/OGM2 send path to transmit a packet whose TVLV length field no longer
+matched the actual buffer contents. And because it also didn't fill in the
+new TVLV data, sending either uninitialized or corrupted data on the wire.
+
+All errors in batadv_tvlv_container_ogm_append() must be forwarded to the
+caller. And the caller must abort the send of the OGM2. For B.A.T.M.A.N.
+IV, it is currently not allowed to abort the send. The non-TVLV part of the
+OGM must be queued up instead.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 16 +++++++++++++---
+ net/batman-adv/bat_v_ogm.c | 26 ++++++++++++++------------
+ net/batman-adv/tvlv.c | 17 ++++++++++++-----
+ net/batman-adv/tvlv.h | 2 +-
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 58c18e22603d12..bc8685ffcb7100 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -782,6 +782,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+
+@@ -805,9 +806,18 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
+- ogm_buff_len,
+- BATADV_OGM_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM_HLEN);
++ if (ret < 0) {
++ /* OGMs must be queued even when the buffer allocation for
++ * TVLVs failed. just fall back to the non-TVLV version
++ */
++ ret = 0;
++ *ogm_buff_len = BATADV_OGM_HLEN;
++ }
++
++ tvlv_len = ret;
+ }
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff);
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 74295915653336..63337e02cf2f11 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -272,9 +272,9 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_ogm2_packet *ogm_packet;
+ struct sk_buff *skb, *skb_tmp;
+- unsigned char *ogm_buff;
+- int ogm_buff_len;
+- u16 tvlv_len = 0;
++ unsigned char **ogm_buff;
++ int *ogm_buff_len;
++ u16 tvlv_len;
+ int ret;
+
+ lockdep_assert_held(&bat_priv->bat_v.ogm_buff_mutex);
+@@ -282,25 +282,27 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING)
+ goto out;
+
+- ogm_buff = bat_priv->bat_v.ogm_buff;
+- ogm_buff_len = bat_priv->bat_v.ogm_buff_len;
++ ogm_buff = &bat_priv->bat_v.ogm_buff;
++ ogm_buff_len = &bat_priv->bat_v.ogm_buff_len;
++
+ /* tt changes have to be committed before the tvlv data is
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, &ogm_buff,
+- &ogm_buff_len,
+- BATADV_OGM2_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM2_HLEN);
++ if (ret < 0)
++ goto reschedule;
+
+- bat_priv->bat_v.ogm_buff = ogm_buff;
+- bat_priv->bat_v.ogm_buff_len = ogm_buff_len;
++ tvlv_len = ret;
+
+- skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + ogm_buff_len);
++ skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + *ogm_buff_len);
+ if (!skb)
+ goto reschedule;
+
+ skb_reserve(skb, ETH_HLEN);
+- skb_put_data(skb, ogm_buff, ogm_buff_len);
++ skb_put_data(skb, *ogm_buff, *ogm_buff_len);
+
+ ogm_packet = (struct batadv_ogm2_packet *)skb->data;
+ ogm_packet->seqno = htonl(atomic_read(&bat_priv->bat_v.ogm_seqno));
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 992773376e51d4..4812137439708f 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -7,6 +7,7 @@
+ #include "main.h"
+
+ #include <linux/byteorder/generic.h>
++#include <linux/errno.h>
+ #include <linux/etherdevice.h>
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+@@ -306,9 +307,10 @@ static bool batadv_tvlv_realloc_packet_buff(unsigned char **packet_buff,
+ * The ogm packet might be enlarged or shrunk depending on the current size
+ * and the size of the to-be-appended tvlv containers.
+ *
+- * Return: size of all appended tvlv containers in bytes.
++ * Return: size of all appended tvlv containers in bytes (max U16_MAX), negative
++ * if operation failed
+ */
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len)
+ {
+@@ -316,6 +318,7 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_len;
+ void *tvlv_value;
++ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+@@ -323,9 +326,12 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+-
+- if (!ret)
++ if (!ret) {
++ tvlv_len_ret = -ENOMEM;
+ goto end;
++ }
++
++ tvlv_len_ret = tvlv_value_len;
+
+ if (!tvlv_value_len)
+ goto end;
+@@ -344,7 +350,8 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ end:
+ spin_unlock_bh(&bat_priv->tvlv.container_list_lock);
+- return tvlv_value_len;
++
++ return tvlv_len_ret;
+ }
+
+ /**
+diff --git a/net/batman-adv/tvlv.h b/net/batman-adv/tvlv.h
+index 54f2a35653d0f1..ffbc93f78688e5 100644
+--- a/net/batman-adv/tvlv.h
++++ b/net/batman-adv/tvlv.h
+@@ -15,7 +15,7 @@
+ void batadv_tvlv_container_register(struct batadv_priv *bat_priv,
+ u8 type, u8 version,
+ void *tvlv_value, u16 tvlv_value_len);
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len);
+ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv,
+--
+2.53.0
+
--- /dev/null
+From 85c934e513f72a0b78cd05213c98097a8436ee01 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 22:04:47 +0200
+Subject: batman-adv: tvlv: reject oversized TVLV packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f50487e3566358b2b982b7801945e858c78ad9ab upstream.
+
+batadv_tvlv_container_ogm_append() builds a TVLV packet section from
+the tvlv.container_list. The total size of this section is computed by
+batadv_tvlv_container_list_size(), which sums the sizes of all registered
+containers.
+
+The return type and accumulator in batadv_tvlv_container_list_size() were
+u16. If the accumulated size exceeds U16_MAX, the value wraps around,
+causing the subsequent allocation in batadv_tvlv_container_ogm_append()
+to be undersized. The memcpy-style copy that follows would then write
+beyond the end of the allocated buffer, corrupting kernel memory.
+
+Fix this by widening the return type of batadv_tvlv_container_list_size()
+to size_t. In batadv_tvlv_container_ogm_append(), check the computed length
+against U16_MAX before proceeding, and bail out as if the allocation had
+failed when the limit is exceeded.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 4812137439708f..de0c139426839c 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -13,6 +13,7 @@
+ #include <linux/if_ether.h>
+ #include <linux/kernel.h>
+ #include <linux/kref.h>
++#include <linux/limits.h>
+ #include <linux/list.h>
+ #include <linux/lockdep.h>
+ #include <linux/netdevice.h>
+@@ -160,10 +161,10 @@ batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version)
+ *
+ * Return: size of all currently registered tvlv containers in bytes.
+ */
+-static u16 batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
++static size_t batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
+ {
+ struct batadv_tvlv_container *tvlv;
+- u16 tvlv_len = 0;
++ size_t tvlv_len = 0;
+
+ lockdep_assert_held(&bat_priv->tvlv.container_list_lock);
+
+@@ -316,13 +317,17 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_container *tvlv;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+- u16 tvlv_value_len;
++ size_t tvlv_value_len;
+ void *tvlv_value;
+ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+ tvlv_value_len = batadv_tvlv_container_list_size(bat_priv);
++ if (tvlv_value_len > U16_MAX) {
++ tvlv_len_ret = -E2BIG;
++ goto end;
++ }
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+--
+2.53.0
+
--- /dev/null
+From 8d7c7294774177317c2d949c9be471421273f9e1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:34:43 +0200
+Subject: batman-adv: v: stop OGMv2 on disabled interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f8ce8b8331a1bc44ad4905886a482214d428b253 upstream.
+
+When a batadv_hard_iface is disabled, its mesh_iface pointer is set to
+NULL. However, batadv_v_ogm_send_meshif() may still dispatch OGMs via
+batadv_v_ogm_queue_on_if() for interfaces that have since lost their
+mesh_iface association. This results in a NULL pointer dereference when
+batadv_v_ogm_queue_on_if() unconditionally calls netdev_priv() on the
+now NULL hard_iface->mesh_iface to retrieve the batadv_priv.
+
+It is necessary to ensure that the batadv_v_ogm_queue_on_if() checks that
+it is using the same mesh_iface for which batadv_v_ogm_send_meshif() was
+called.
+
+Cc: stable@kernel.org
+Fixes: 0da0035942d4 ("batman-adv: OGMv2 - add basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v_ogm.c | 33 +++++++++++++++++++++------------
+ 1 file changed, 21 insertions(+), 12 deletions(-)
+
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index c357cf72396ebe..74295915653336 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -116,14 +116,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
+
+ /**
+ * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to send
+ * @hard_iface: the interface to use to send the OGM
+ */
+-static void batadv_v_ogm_send_to_if(struct sk_buff *skb,
++static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+-
+ if (hard_iface->if_status != BATADV_IF_ACTIVE) {
+ kfree_skb(skb);
+ return;
+@@ -190,6 +190,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+
+ /**
+ * batadv_v_ogm_aggr_send() - flush & send aggregation queue
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @hard_iface: the interface with the aggregation queue to flush
+ *
+ * Aggregates all OGMv2 packets currently in the aggregation queue into a
+@@ -199,7 +200,8 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+ *
+ * Caller needs to hold the hard_iface->bat_v.aggr_list.lock.
+ */
+-static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
++static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv,
++ struct batadv_hard_iface *hard_iface)
+ {
+ unsigned int aggr_len = hard_iface->bat_v.aggr_len;
+ struct sk_buff *skb_aggr;
+@@ -229,27 +231,32 @@ static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
+ consume_skb(skb);
+ }
+
+- batadv_v_ogm_send_to_if(skb_aggr, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface);
+ }
+
+ /**
+ * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to queue
+ * @hard_iface: the interface to queue the OGM on
+ */
+-static void batadv_v_ogm_queue_on_if(struct sk_buff *skb,
++static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
++ if (hard_iface->soft_iface != bat_priv->soft_iface) {
++ kfree_skb(skb);
++ return;
++ }
+
+ if (!atomic_read(&bat_priv->aggregated_ogms)) {
+- batadv_v_ogm_send_to_if(skb, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface);
+ return;
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
+@@ -348,7 +355,7 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ break;
+ }
+
+- batadv_v_ogm_queue_on_if(skb_tmp, hard_iface);
++ batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface);
+ batadv_hardif_put(hard_iface);
+ }
+ rcu_read_unlock();
+@@ -388,12 +395,14 @@ void batadv_v_ogm_aggr_work(struct work_struct *work)
+ {
+ struct batadv_hard_iface_bat_v *batv;
+ struct batadv_hard_iface *hard_iface;
++ struct batadv_priv *bat_priv;
+
+ batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work);
+ hard_iface = container_of(batv, struct batadv_hard_iface, bat_v);
++ bat_priv = netdev_priv(hard_iface->soft_iface);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+
+ batadv_v_ogm_start_queue_timer(hard_iface);
+@@ -583,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv,
+ if_outgoing->net_dev->name, ntohl(ogm_forward->throughput),
+ ogm_forward->ttl, if_incoming->net_dev->name);
+
+- batadv_v_ogm_queue_on_if(skb, if_outgoing);
++ batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing);
+
+ out:
+ batadv_orig_ifinfo_put(orig_ifinfo);
+--
+2.53.0
+
--- /dev/null
+From 19f0eb99335b3e44794620982a3312ab002e0f0c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:03:16 +0300
+Subject: drm/dp: Add eDP 1.5 bit definition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Suraj Kandpal <suraj.kandpal@intel.com>
+
+commit 5dfc37a6b77bf6beedbd30d70184b54e1a08ccac upstream.
+
+Add the eDP revision bit value for 1.5.
+
+Spec: eDPv1.5 Table 16-5
+Signed-off-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Reviewed-by: Arun R Murthy <arun.r.murthy@intel.com>
+Tested-by: Ben Kao <ben.kao@intel.com>
+Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Link: https://patchwork.freedesktop.org/patch/msgid/20250206063253.2827017-2-suraj.kandpal@intel.com
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/drm/drm_dp_helper.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
+index 9c7949ebc159eb..01422c2078f7db 100644
+--- a/include/drm/drm_dp_helper.h
++++ b/include/drm/drm_dp_helper.h
+@@ -942,6 +942,7 @@ struct drm_panel;
+ # define DP_EDP_14 0x03
+ # define DP_EDP_14a 0x04 /* eDP 1.4a */
+ # define DP_EDP_14b 0x05 /* eDP 1.4b */
++# define DP_EDP_15 0x06 /* eDP 1.5 */
+
+ #define DP_EDP_GENERAL_CAP_1 0x701
+ # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0)
+--
+2.53.0
+
--- /dev/null
+From f639114e72baf50818faad11165ad9a1e2c437c5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:03:14 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From 47d33a851e1598adf99cbb626e0f5fc46c990871 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:03:17 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 27 +++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index aa00e062a2a6aa..fca55ae3ffe5bd 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -824,6 +824,30 @@ static bool psr2_granularity_check(struct intel_dp *intel_dp,
+ return true;
+ }
+
++static bool apply_scanline_indication_wa(struct intel_dp *intel_dp,
++ struct intel_crtc_state *crtc_state)
++{
++ u8 early_scanline_support = intel_dp->intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
+ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+ struct intel_crtc_state *crtc_state)
+ {
+@@ -844,7 +868,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(intel_dp, crtc_state);
+ }
+
+ static bool _compute_psr2_wake_times(struct intel_dp *intel_dp,
+--
+2.53.0
+
--- /dev/null
+From 5799d01879ad52437f710dfeb3d0029965551a9d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:03:15 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index 90e055f0569940..ef4c280d2cb630 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -1554,6 +1554,7 @@ struct intel_dp {
+ u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE];
+ u8 fec_capable;
+ u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+ /* source rates */
+ int num_source_rates;
+ const int *source_rates;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 93d2fd4cd16b7c..aa00e062a2a6aa 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -30,6 +30,7 @@
+ #include "intel_atomic.h"
+ #include "intel_de.h"
+ #include "intel_display_types.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_hdmi.h"
+ #include "intel_psr.h"
+@@ -363,6 +364,12 @@ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
+ intel_dp_get_su_granularity(intel_dp);
+ }
+ }
++
++ if (intel_dp->psr.sink_psr2_support)
++ drm_dp_dpcd_read(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &intel_dp->intel_wa_dpcd,
++ sizeof(intel_dp->intel_wa_dpcd));
+ }
+
+ static void hsw_psr_setup_aux(struct intel_dp *intel_dp)
+--
+2.53.0
+
--- /dev/null
+From 664eabc5fb730c778018eea31e50440c07787ed6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:12:21 +0200
+Subject: RDMA/rxe: Fix double free in rxe_srq_from_init
+
+From: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+
+commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream.
+
+In rxe_srq_from_init(), the queue pointer 'q' is assigned to
+'srq->rq.queue' before copying the SRQ number to user space.
+If copy_to_user() fails, the function calls rxe_queue_cleanup()
+to free the queue, but leaves the now-invalid pointer in
+'srq->rq.queue'.
+
+The caller of rxe_srq_from_init() (rxe_create_srq) eventually
+calls rxe_srq_cleanup() upon receiving the error, which triggers
+a second rxe_queue_cleanup() on the same memory, leading to a
+double free.
+
+The call trace looks like this:
+ kmem_cache_free+0x.../0x...
+ rxe_queue_cleanup+0x1a/0x30 [rdma_rxe]
+ rxe_srq_cleanup+0x42/0x60 [rdma_rxe]
+ rxe_elem_release+0x31/0x70 [rdma_rxe]
+ rxe_create_srq+0x12b/0x1a0 [rdma_rxe]
+ ib_create_srq_user+0x9a/0x150 [ib_core]
+
+Fix this by moving 'srq->rq.queue = q' after copy_to_user.
+
+Fixes: aae0484e15f0 ("IB/rxe: avoid srq memory leak")
+Signed-off-by: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+Link: https://patch.msgid.link/20260112015412.29458-1-jiashengjiangcool@gmail.com
+Reviewed-by: Zhu Yanjun <yanjun.Zhu@linux.dev>
+Signed-off-by: Leon Romanovsky <leon@kernel.org>
+[bwh: Backported to 5.15: There was no assignment to init->attr.max_wr
+ here; don't add it]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index eb1c4c3b3a7865..595d4e7b91d0b8 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -100,8 +100,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ return -ENOMEM;
+ }
+
+- srq->rq.queue = q;
+-
+ err = do_mmap_info(rxe, uresp ? &uresp->mi : NULL, udata, q->buf,
+ q->buf_size, &q->ip);
+ if (err) {
+@@ -118,6 +116,8 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
++ srq->rq.queue = q;
++
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 67f2ca027d973a97363336e1534dda681a13ccc6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:07:48 +0200
+Subject: Revert "RDMA/rxe: Fix double free in rxe_srq_from_init"
+
+From: Ben Hutchings <benh@debian.org>
+
+This reverts commit af5956243018918130d52c9f671efdb40bab3366, which
+was commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream. The
+backported version did not move but duplicated the problematic
+assignment, so it did not fix the bug. A proper backport will follow.
+
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index 05ae3d183b21d1..eb1c4c3b3a7865 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -118,9 +118,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
+- srq->rq.queue = q;
+- init->attr.max_wr = srq->rq.max_wr;
+-
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 7b3084888360b002704e9af39ce89f70c519fd7c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:33:11 +0200
+Subject: selftests: forwarding: lib: Add helpers for checksum handling
+
+From: Petr Machata <petrm@nvidia.com>
+
+commit 952e0ee38c7215c45192d8c899acd1830873f28b upstream.
+
+In order to generate IGMPv3 and MLDv2 packets on the fly, we will need
+helpers to calculate the packet checksum.
+
+The approach presented in this patch revolves around payload templates
+for mausezahn. These are mausezahn-like payload strings (01:23:45:...)
+with possibly one 2-byte sequence replaced with the word PAYLOAD. The
+main function is payload_template_calc_checksum(), which calculates
+RFC 1071 checksum of the message. There are further helpers to then
+convert the checksum to the payload format, and to expand it.
+
+For IPv6, MLDv2 message checksum is computed using a pseudoheader that
+differs from the header used in the payload itself. The fact that the
+two messages are different means that the checksum needs to be
+returned as a separate quantity, instead of being expanded in-place in
+the payload itself. Furthermore, the pseudoheader includes a length of
+the message. Much like the checksum, this needs to be expanded in
+mausezahn format. And likewise for number of addresses for (S,G)
+entries. Thus we have several places where a computed quantity needs
+to be presented in the payload format. Add a helper u16_to_bytes(),
+which will be used in all these cases.
+
+Signed-off-by: Petr Machata <petrm@nvidia.com>
+Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 02cb2e6bacbb ("selftests: forwarding: vxlan_bridge_1d: fix test failure with br_netfilter enabled")
+[bwh: Backported to 5.15: adjust context]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/net/forwarding/lib.sh | 56 +++++++++++++++++++
+ 1 file changed, 56 insertions(+)
+
+diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh
+index 83e8f9466d6273..c570d8f65a0cec 100644
+--- a/tools/testing/selftests/net/forwarding/lib.sh
++++ b/tools/testing/selftests/net/forwarding/lib.sh
+@@ -1491,3 +1491,59 @@ brmcast_check_sg_state()
+ check_err_fail $should_fail $? "Entry $src has blocked flag"
+ done
+ }
++
++u16_to_bytes()
++{
++ local u16=$1; shift
++
++ printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
++}
++
++# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
++# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
++# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
++# stands for 00:00.
++payload_template_calc_checksum()
++{
++ local payload=$1; shift
++
++ (
++ # Set input radix.
++ echo "16i"
++ # Push zero for the initial checksum.
++ echo 0
++
++ # Pad the payload with a terminating 00: in case we get an odd
++ # number of bytes.
++ echo "${payload%:}:00:" |
++ sed 's/CHECKSUM/00:00/g' |
++ tr '[:lower:]' '[:upper:]' |
++ # Add the word to the checksum.
++ sed 's/\(..\):\(..\):/\1\2+\n/g' |
++ # Strip the extra odd byte we pushed if left unconverted.
++ sed 's/\(..\):$//'
++
++ echo "10000 ~ +" # Calculate and add carry.
++ echo "FFFF r - p" # Bit-flip and print.
++ ) |
++ dc |
++ tr '[:upper:]' '[:lower:]'
++}
++
++payload_template_expand_checksum()
++{
++ local payload=$1; shift
++ local checksum=$1; shift
++
++ local ckbytes=$(u16_to_bytes $checksum)
++
++ echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
++}
++
++payload_template_nbytes()
++{
++ local payload=$1; shift
++
++ payload_template_expand_checksum "${payload%:}" 0 |
++ sed 's/:/\n/g' | wc -l
++}
+--
+2.53.0
+
bluetooth-l2cap-fix-possible-crash-on-l2cap_ecred_co.patch
gpio-rockchip-convert-bank-clk-to-devm_clk_get_enabl.patch
sctp-fix-race-between-sctp_wait_for_connect-and-peel.patch
+batman-adv-v-stop-ogmv2-on-disabled-interface.patch
+batman-adv-tvlv-abort-ogm-send-on-tvlv-append-failur.patch
+batman-adv-bla-avoid-null-ptr-deref-for-claim-via-dr.patch
+batman-adv-tvlv-reject-oversized-tvlv-packets.patch
+batman-adv-iv-recover-ogm-scheduling-after-forward-p.patch
+selftests-forwarding-lib-add-helpers-for-checksum-ha.patch
+batman-adv-tp_meter-directly-shut-down-timer-on-clea.patch
+batman-adv-tt-fix-toctou-race-for-reported-vlans.patch
+batman-adv-tt-avoid-empty-vlan-responses.patch
+batman-adv-bla-avoid-double-decrement-of-bla.num_req.patch
+wifi-brcmfmac-fix-use-after-free-when-rescheduling-b.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-dp-add-edp-1.5-bit-definition.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch
+revert-rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch
+rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch-82
--- /dev/null
+From 4e6cc40ea75661a3abf4b56f56c1d7c9a57a5ff9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:54:31 +0800
+Subject: wifi: brcmfmac: fix use-after-free when rescheduling
+ brcmf_btcoex_info work
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Duoming Zhou <duoming@zju.edu.cn>
+
+[ Upstream commit 9cb83d4be0b9b697eae93d321e0da999f9cdfcfc ]
+
+The brcmf_btcoex_detach() only shuts down the btcoex timer, if the
+flag timer_on is false. However, the brcmf_btcoex_timerfunc(), which
+runs as timer handler, sets timer_on to false. This creates critical
+race conditions:
+
+1.If brcmf_btcoex_detach() is called while brcmf_btcoex_timerfunc()
+is executing, it may observe timer_on as false and skip the call to
+timer_shutdown_sync().
+
+2.The brcmf_btcoex_timerfunc() may then reschedule the brcmf_btcoex_info
+worker after the cancel_work_sync() has been executed, resulting in
+use-after-free bugs.
+
+The use-after-free bugs occur in two distinct scenarios, depending on
+the timing of when the brcmf_btcoex_info struct is freed relative to
+the execution of its worker thread.
+
+Scenario 1: Freed before the worker is scheduled
+
+The brcmf_btcoex_info is deallocated before the worker is scheduled.
+A race condition can occur when schedule_work(&bt_local->work) is
+called after the target memory has been freed. The sequence of events
+is detailed below:
+
+CPU0 | CPU1
+brcmf_btcoex_detach | brcmf_btcoex_timerfunc
+ | bt_local->timer_on = false;
+ if (cfg->btcoex->timer_on) |
+ ... |
+ cancel_work_sync(); |
+ ... |
+ kfree(cfg->btcoex); // FREE |
+ | schedule_work(&bt_local->work); // USE
+
+Scenario 2: Freed after the worker is scheduled
+
+The brcmf_btcoex_info is freed after the worker has been scheduled
+but before or during its execution. In this case, statements within
+the brcmf_btcoex_handler() — such as the container_of macro and
+subsequent dereferences of the brcmf_btcoex_info object will cause
+a use-after-free access. The following timeline illustrates this
+scenario:
+
+CPU0 | CPU1
+brcmf_btcoex_detach | brcmf_btcoex_timerfunc
+ | bt_local->timer_on = false;
+ if (cfg->btcoex->timer_on) |
+ ... |
+ cancel_work_sync(); |
+ ... | schedule_work(); // Reschedule
+ |
+ kfree(cfg->btcoex); // FREE | brcmf_btcoex_handler() // Worker
+ /* | btci = container_of(....); // USE
+ The kfree() above could | ...
+ also occur at any point | btci-> // USE
+ during the worker's execution|
+ */ |
+
+To resolve the race conditions, drop the conditional check and call
+timer_shutdown_sync() directly. It can deactivate the timer reliably,
+regardless of its current state. Once stopped, the timer_on state is
+then set to false.
+
+Fixes: 61730d4dfffc ("brcmfmac: support critical protocol API for DHCP")
+Acked-by: Arend van Spriel <arend.vanspriel@broadcom.com>
+Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
+Link: https://patch.msgid.link/20250822050839.4413-1-duoming@zju.edu.cn
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Robert Garcia <rob_garcia@163.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c
+index f9f18ff451ea7c..f46e4090021777 100644
+--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c
++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c
+@@ -392,10 +392,8 @@ void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg)
+ if (!cfg->btcoex)
+ return;
+
+- if (cfg->btcoex->timer_on) {
+- cfg->btcoex->timer_on = false;
+- del_timer_sync(&cfg->btcoex->timer);
+- }
++ del_timer_sync(&cfg->btcoex->timer);
++ cfg->btcoex->timer_on = false;
+
+ cancel_work_sync(&cfg->btcoex->work);
+
+--
+2.53.0
+
--- /dev/null
+From 68a8ce4e3c644d0a83009441df3fa637ed068bac Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:57:07 +0200
+Subject: batman-adv: bla: avoid double decrement of bla.num_requests
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 83ab69bd12b80f6ea169c8bea6977701b53a043d upstream.
+
+The bla.num_requests is increased when no request_sent was in progress. And
+it is decremented in various places (announcement was received, backbone is
+purged, periodic work). But the check if the request_sent is actually set
+to a specific state and the atomic_dec/_inc are not safe because they are
+not atomic (TOCTOU) and multiple such code portions can run concurrently.
+
+At the same time, it is necessary to modify request_sent (state) and
+bla.num_requests atomically. Otherwise batadv_bla_send_request() might set
+request_sent to 1 and is interrupted. batadv_handle_announce() can then
+set request_sent back to 0 and decrement num_requests before
+batadv_bla_send_request() incremented it.
+
+The two operations must therefore be locked. And since state (request_sent)
+and wait_periods are only accessed inside this lock, they can be converted
+to simpler datatypes. And to avoid that the bla.num_requests is touched by
+a parallel running context with a valid backbone_gw reference after
+batadv_bla_purge_backbone_gw() ran, a third state "stopped" is required to
+correctly signal that a backbone_gw is in the state of being cleaned up.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 51 ++++++++++++++++++--------
+ net/batman-adv/soft-interface.c | 1 +
+ net/batman-adv/types.h | 39 ++++++++++++++++----
+ 3 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 76d8c91c156a3b..cfb1eb25c6ac4d 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -516,8 +516,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+- atomic_set(&entry->request_sent, 0);
+- atomic_set(&entry->wait_periods, 0);
++ entry->state = BATADV_BLA_BACKBONE_GW_SYNCED;
++ entry->wait_periods = 0;
+ ether_addr_copy(entry->orig, orig);
+ INIT_WORK(&entry->report_work, batadv_bla_loopdetect_report);
+ kref_init(&entry->refcount);
+@@ -546,9 +546,13 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ batadv_bla_send_announce(bat_priv, entry);
+
+ /* this will be decreased in the worker thread */
+- atomic_inc(&entry->request_sent);
+- atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS);
+- atomic_inc(&bat_priv->bla.num_requests);
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (entry->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ entry->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
++ entry->wait_periods = BATADV_BLA_WAIT_PERIODS;
++ atomic_inc(&bat_priv->bla.num_requests);
++ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ return entry;
+@@ -651,10 +655,12 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw)
+ backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST);
+
+ /* no local broadcasts should be sent or received, for now. */
+- if (!atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
+ atomic_inc(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 1);
+ }
++ spin_unlock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
+ }
+
+ /**
+@@ -875,10 +881,12 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ /* if we have sent a request and the crc was OK,
+ * we can allow traffic again.
+ */
+- if (atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
+ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ batadv_backbone_gw_put(backbone_gw);
+@@ -1257,9 +1265,13 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ purged = true;
+
+ /* don't wait for the pending request anymore */
+- if (atomic_read(&backbone_gw->request_sent))
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED)
+ atomic_dec(&bat_priv->bla.num_requests);
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_STOPPED;
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
++
+ batadv_bla_del_backbone_claims(backbone_gw);
+
+ hlist_del_rcu(&backbone_gw->hash_entry);
+@@ -1510,7 +1522,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ batadv_bla_send_loopdetect(bat_priv,
+ backbone_gw);
+
+- /* request_sent is only set after creation to avoid
++ /* state is only set to unsynced after creation to avoid
+ * problems when we are not yet known as backbone gw
+ * in the backbone.
+ *
+@@ -1519,14 +1531,21 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ * some grace time.
+ */
+
+- if (atomic_read(&backbone_gw->request_sent) == 0)
+- continue;
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state != BATADV_BLA_BACKBONE_GW_UNSYNCED)
++ goto unlock_next;
+
+- if (!atomic_dec_and_test(&backbone_gw->wait_periods))
+- continue;
++ if (backbone_gw->wait_periods > 0)
++ backbone_gw->wait_periods--;
++
++ if (backbone_gw->wait_periods > 0)
++ goto unlock_next;
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
++
++unlock_next:
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+ rcu_read_unlock();
+ }
+diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
+index d7b525a495e451..c0549abcae7a64 100644
+--- a/net/batman-adv/soft-interface.c
++++ b/net/batman-adv/soft-interface.c
+@@ -785,6 +785,7 @@ static int batadv_softif_init_late(struct net_device *dev)
+ atomic_set(&bat_priv->tt.ogm_append_cnt, 0);
+ #ifdef CONFIG_BATMAN_ADV_BLA
+ atomic_set(&bat_priv->bla.num_requests, 0);
++ spin_lock_init(&bat_priv->bla.num_requests_lock);
+ #endif
+ atomic_set(&bat_priv->tp_num, 0);
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 41959a4eea7d3f..12fbcc89581adf 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1026,6 +1026,12 @@ struct batadv_priv_bla {
+ /** @num_requests: number of bla requests in flight */
+ atomic_t num_requests;
+
++ /**
++ * @num_requests_lock: locks update num_requests +
++ * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
++ */
++ spinlock_t num_requests_lock;
++
+ /**
+ * @claim_hash: hash table containing mesh nodes this host has claimed
+ */
+@@ -1748,6 +1754,27 @@ struct batadv_priv {
+
+ #ifdef CONFIG_BATMAN_ADV_BLA
+
++enum batadv_bla_backbone_gw_state {
++ /**
++ * @BATADV_BLA_BACKBONE_GW_STOPPED: backbone gw is being removed
++ * and it must not longer work on requests
++ */
++ BATADV_BLA_BACKBONE_GW_STOPPED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_UNSYNCED: backbone was detected out
++ * of sync and a request was send. No traffic is forwarded until the
++ * situation is resolved
++ */
++ BATADV_BLA_BACKBONE_GW_UNSYNCED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_SYNCED: backbone is consider to be in
++ * sync. traffic can be forwarded
++ */
++ BATADV_BLA_BACKBONE_GW_SYNCED,
++};
++
+ /**
+ * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN
+ */
+@@ -1773,16 +1800,12 @@ struct batadv_bla_backbone_gw {
+ /**
+ * @wait_periods: grace time for bridge forward delays and bla group
+ * forming at bootup phase - no bcast traffic is formwared until it has
+- * elapsed
++ * elapsed. Must only be access with num_requests_lock.
+ */
+- atomic_t wait_periods;
++ u8 wait_periods;
+
+- /**
+- * @request_sent: if this bool is set to true we are out of sync with
+- * this backbone gateway - no bcast traffic is formwared until the
+- * situation was resolved
+- */
+- atomic_t request_sent;
++ /** @state: sync state. Must only be access with num_requests_lock. */
++ enum batadv_bla_backbone_gw_state state;
+
+ /** @crc: crc16 checksum over all claims */
+ u16 crc;
+--
+2.53.0
+
--- /dev/null
+From a1ca0972a60493b83816b12870b9ea3cfea52191 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:57:36 +0200
+Subject: batman-adv: bla: avoid NULL-ptr deref for claim via dropped interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f80d3d98d2ff78d9e2fe5d68b1f45948c4f7bd24 upstream.
+
+Without rtnl_lock held, a hardif might be retrieved as primary interface of
+a meshif, but then (while operating on this interface) getting decoupled
+from the mesh interface. In this case, the meshif still exists but the
+pointer from the primary hardif to the meshif is set to NULL.
+
+The mesh_iface must be checked first to be non-NULL before continuing to
+send an ARP request using meshif.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Reported-by: Ido Schimmel <idosch@nvidia.com>
+Reported-by: syzbot+9fdcc9f05a98a540b816@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=9fdcc9f05a98a540b816
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index f614709e6cda74..76d8c91c156a3b 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -356,12 +356,14 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac,
+ sizeof(local_claim_dest));
+ local_claim_dest.type = claimtype;
+
+- soft_iface = primary_if->soft_iface;
++ soft_iface = READ_ONCE(primary_if->soft_iface);
++ if (!soft_iface)
++ goto out;
+
+ skb = arp_create(ARPOP_REPLY, ETH_P_ARP,
+ /* IP DST: 0.0.0.0 */
+ zeroip,
+- primary_if->soft_iface,
++ soft_iface,
+ /* IP SRC: 0.0.0.0 */
+ zeroip,
+ /* Ethernet DST: Broadcast */
+--
+2.53.0
+
--- /dev/null
+From f7dc6d9db4eaf933b61e16c80e22cde0030c4be7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:56:18 +0200
+Subject: batman-adv: iv: recover OGM scheduling after forward packet error
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit aa3153bd139a6c48667dcd02608d3b2c80bff02c upstream.
+
+When batadv_iv_ogm_schedule_buff() fails to allocate and queue a forward
+packet for OGM transmission, the work item that drives periodic OGM
+scheduling is never re-armed. This silently halts transmission of the
+node's own OGMs on the affected interface — only OGMs from other peers
+continue to be aggregated and forwarded.
+
+Fix this by tracking whether batadv_iv_ogm_queue_add() (and transitively
+batadv_iv_ogm_aggregate_new()) successfully scheduled a forward packet.
+When scheduling fails, batadv_iv_ogm_schedule_buff() falls back to queuing
+a dedicated recovery work item (reschedule_work) that fires after one
+originator interval and calls batadv_iv_ogm_schedule() again.
+
+Cc: stable@kernel.org
+Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 76 +++++++++++++++++++++++++++----------
+ net/batman-adv/types.h | 3 ++
+ 2 files changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index c93d30462054ce..c52e3b82889868 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -224,6 +224,8 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ hard_iface->bat_iv.ogm_buff = NULL;
+
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
++
++ cancel_delayed_work_sync(&hard_iface->bat_iv.reschedule_work);
+ }
+
+ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface)
+@@ -528,8 +530,10 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet,
+ * @if_incoming: interface where the packet was received
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
++static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ int packet_len, unsigned long send_time,
+ bool direct_link,
+ struct batadv_hard_iface *if_incoming,
+@@ -553,13 +557,13 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+
+ skb = netdev_alloc_skb_ip_align(NULL, skb_size);
+ if (!skb)
+- return;
++ return false;
+
+ forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing,
+ queue_left, bat_priv, skb);
+ if (!forw_packet_aggr) {
+ kfree_skb(skb);
+- return;
++ return false;
+ }
+
+ forw_packet_aggr->skb->priority = TC_PRIO_CONTROL;
+@@ -581,6 +585,8 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ batadv_iv_send_outstanding_bat_ogm_packet);
+
+ batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
++
++ return true;
+ }
+
+ /* aggregate a new packet into the existing ogm packet */
+@@ -610,8 +616,10 @@ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr,
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
+ * @send_time: timestamp (jiffies) when the packet is to be sent
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
++static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ unsigned char *packet_buff,
+ int packet_len,
+ struct batadv_hard_iface *if_incoming,
+@@ -663,14 +671,16 @@ static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ if (!own_packet && atomic_read(&bat_priv->aggregated_ogms))
+ send_time += max_aggregation_jiffies;
+
+- batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
+- send_time, direct_link,
+- if_incoming, if_outgoing,
+- own_packet);
++ return batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
++ send_time, direct_link,
++ if_incoming, if_outgoing,
++ own_packet);
+ } else {
+ batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff,
+ packet_len, direct_link);
+ spin_unlock_bh(&bat_priv->forw_bat_list_lock);
++
++ return true;
+ }
+ }
+
+@@ -782,6 +792,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ bool reschedule = false;
++ bool scheduled;
+ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+@@ -810,11 +822,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ ogm_buff_len,
+ BATADV_OGM_HLEN);
+ if (ret < 0) {
+- /* OGMs must be queued even when the buffer allocation for
+- * TVLVs failed. just fall back to the non-TVLV version
+- */
+- ret = 0;
+- *ogm_buff_len = BATADV_OGM_HLEN;
++ reschedule = true;
++ goto out;
+ }
+
+ tvlv_len = ret;
+@@ -836,8 +845,11 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ /* OGMs from secondary interfaces are only scheduled on their
+ * respective interfaces.
+ */
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
+- hard_iface, hard_iface, 1, send_time);
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
++ hard_iface, hard_iface, 1, send_time);
++ if (!scheduled)
++ reschedule = true;
++
+ goto out;
+ }
+
+@@ -852,15 +864,28 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ if (!kref_get_unless_zero(&tmp_hard_iface->refcount))
+ continue;
+
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
+- *ogm_buff_len, hard_iface,
+- tmp_hard_iface, 1, send_time);
+-
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
++ *ogm_buff_len, hard_iface,
++ tmp_hard_iface, 1, send_time);
+ batadv_hardif_put(tmp_hard_iface);
++
++ if (!scheduled && tmp_hard_iface == hard_iface)
++ reschedule = true;
+ }
+ rcu_read_unlock();
+
+ out:
++ if (reschedule) {
++ /* there was a failure scheduling the own forward packet.
++ * as result, the batadv_iv_send_outstanding_bat_ogm_packet()
++ * work item is no longer scheduled. it is therefore necessary
++ * to reschedule it manually
++ */
++ queue_delayed_work(batadv_event_workqueue,
++ &hard_iface->bat_iv.reschedule_work,
++ msecs_to_jiffies(atomic_read(&bat_priv->orig_interval)));
++ }
++
+ batadv_hardif_put(primary_if);
+ }
+
+@@ -875,6 +900,17 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface)
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
+ }
+
++static void batadv_iv_ogm_reschedule(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct batadv_hard_iface *hard_iface;
++
++ hard_iface = container_of(delayed_work,
++ struct batadv_hard_iface,
++ bat_iv.reschedule_work);
++ batadv_iv_ogm_schedule(hard_iface);
++}
++
+ /**
+ * batadv_iv_orig_ifinfo_sum() - Get bcast_own sum for originator over interface
+ * @orig_node: originator which reproadcasted the OGMs directly
+@@ -2278,6 +2314,8 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1,
+
+ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface)
+ {
++ INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule);
++
+ /* begin scheduling originator messages on that interface */
+ batadv_iv_ogm_schedule(hard_iface);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index afd71f061c409a..41959a4eea7d3f 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -82,6 +82,9 @@ struct batadv_hard_iface_bat_iv {
+ /** @ogm_seqno: OGM sequence number - used to identify each OGM */
+ atomic_t ogm_seqno;
+
++ /** @reschedule_work: recover OGM schedule after schedule error */
++ struct delayed_work reschedule_work;
++
+ /** @ogm_buff_mutex: lock protecting ogm_buff and ogm_buff_len */
+ struct mutex ogm_buff_mutex;
+ };
+--
+2.53.0
+
--- /dev/null
+From 19af649dbcd2e1b375b1041cb1d79e1133664527 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:25:20 +0200
+Subject: batman-adv: tp_meter: directly shut down timer on cleanup
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d5487249a81ea658717614009c8f46acc5b7101a upstream.
+
+batadv_tp_sender_cleanup() was calling timer_delete_sync() followed by
+timer_delete() to guard against the timer handler re-arming itself between
+the two calls. This double-deletion hack relied on the sending status being
+set to 0 to suppress re-arming.
+
+Replace both calls with a single timer_shutdown_sync(). This function both
+waits for any running timer callback to complete (like timer_delete_sync())
+and permanently disarms the timer so it cannot be re-armed afterwards,
+making re-arming prevention unconditional and self-documenting.
+
+The re-arming property is also required because otherwise:
+
+1. context 0 (batadv_tp_recv_ack()) checks in
+ batadv_tp_reset_sender_timer() if sending is still 1 -> it is
+2. context 1 changes in batadv_tp_sender_shutdown() sending to 0 and in
+ this process forces the kthread to stop timer in
+ batadv_tp_sender_cleanup()
+3. context 0 continues in batadv_tp_reset_sender_timer() and rearms the
+ timer -> but the reference for it is already gone
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ adapt pre-hunk to old del_timer* names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 61e6cb5bce8ec5..707f05aa14791f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -384,13 +384,7 @@ static void batadv_tp_sender_cleanup(struct batadv_priv *bat_priv,
+ atomic_dec(&tp_vars->bat_priv->tp_num);
+
+ /* kill the timer and remove its reference */
+- del_timer_sync(&tp_vars->timer);
+- /* the worker might have rearmed itself therefore we kill it again. Note
+- * that if the worker should run again before invoking the following
+- * del_timer(), it would not re-arm itself once again because the status
+- * is OFF now
+- */
+- del_timer(&tp_vars->timer);
++ timer_shutdown_sync(&tp_vars->timer);
+ batadv_tp_vars_put(tp_vars);
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 9d0b6f2b04a199e7db832b5a18664c2445697373 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:44:38 +0200
+Subject: batman-adv: tt: avoid empty VLAN responses
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit fa1bd704940b5bcbc32c0b28db9167405c8ee5e0 upstream.
+
+The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
+sending inconsistent TT TLVLs") added checks to the local (direct) TT
+response code. But the response can also be done indirectly by another node
+using the global TT state. To avoid such inconsistency states reported in
+the original fix, also avoid sending empty VLANs for replies from the
+global TT state.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index ac6c05971348d8..e0503c8f24c353 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -843,17 +843,19 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ s32 *tt_len)
+ {
+ u16 num_vlan = 0;
+- u16 num_entries = 0;
+ u16 tvlv_len = 0;
+ unsigned int change_offset;
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_orig_node_vlan *vlan;
++ u16 total_entries = 0;
+ u8 *tt_change_ptr;
++ int vlan_entries;
+
+ spin_lock_bh(&orig_node->vlan_list_lock);
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ total_entries += vlan_entries;
+ num_vlan++;
+- num_entries += atomic_read(&vlan->tt.num_entries);
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -861,7 +863,7 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+
+ /* if tt_len is negative, allocate the space needed by the full table */
+ if (*tt_len < 0)
+- *tt_len = batadv_tt_len(num_entries);
++ *tt_len = batadv_tt_len(total_entries);
+
+ if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+ *tt_len = 0;
+@@ -882,14 +884,27 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ if (vlan_entries < 1)
++ continue;
++
+ tt_vlan->vid = htons(vlan->vid);
+ tt_vlan->crc = htonl(vlan->tt.crc);
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From b79a409c07b9c294e6342ce42bdf10431a60cdc3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 23:02:13 +0200
+Subject: batman-adv: tt: fix TOCTOU race for reported vlans
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 94d27005016be15ffc638b2ecbc4d58805ad7b48 upstream.
+
+The local TT based TVLV is generated by first checking the number of VLANs
+which have at least one TT entry. A new buffer with the correct size for
+the VLANs is then allocated. Only then, the list of VLANs s used to fill
+the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
+is held. But the actual number of TT entries of each VLAN can still
+increase during this time - just not the number of VLANs in the list.
+
+But the prefilter used in the buffer size calculation might still cause an
+increase of the number of VLANs which need to be stored. Simply because a
+VLAN might now suddenly have at least one entry when it had none in the
+pre-alloc check - and then needs to occupy space which was not allocated.
+
+It is better to overestimate the buffer size at the beginning and then fill
+the buffer only with the VLANs which are not empty.
+
+Cc: stable@kernel.org
+Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 6c40d690ac6c3a..ac6c05971348d8 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -934,11 +934,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+- if (vlan_entries < 1)
+- continue;
+-
+- num_vlan++;
+ total_entries += vlan_entries;
++ num_vlan++;
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -964,6 +961,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+@@ -974,8 +972,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From aaa2b73ec93daa8afe658d29061aa9a3da688234 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:43:08 +0200
+Subject: batman-adv: tt: reject oversized local TVLV buffers
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 1e9fab756f8395096d5bba7be0c373c4c8f5d165 upstream.
+
+The commit 3a359bf5c61d ("batman-adv: reject oversized global TT response
+buffers") added a check to ensure that a global return buffer size can be
+stored in an u16. The same buffer handling also exists for the local data
+buffer but was not touched.
+
+A similar check should be also be in place for the local TVLV buffer. It
+doesn't have the similar attack surface because it is only generated from
+locally discovered MAC addresses but the dynamic nature could still cause
+temporarily to large buffers.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index f1e93a60482038..6c40d690ac6c3a 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -924,12 +924,12 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_softif_vlan *vlan;
++ size_t change_offset;
+ u16 num_vlan = 0;
+ u16 vlan_entries = 0;
+ u16 total_entries = 0;
+ u16 tvlv_len;
+ u8 *tt_change_ptr;
+- int change_offset;
+
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+@@ -948,8 +948,10 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ if (*tt_len < 0)
+ *tt_len = batadv_tt_len(total_entries);
+
+- tvlv_len = *tt_len;
+- tvlv_len += change_offset;
++ if (check_add_overflow(*tt_len, change_offset, &tvlv_len)) {
++ tvlv_len = 0;
++ goto out;
++ }
+
+ *tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
+ if (!*tt_data) {
+--
+2.53.0
+
--- /dev/null
+From 904f40570779f3c8e51cfcf7c6ca46ab95084ea4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:51:13 +0200
+Subject: batman-adv: tvlv: abort OGM send on tvlv append failure
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 501368506563e151b322c8c3f228b796e615b90d upstream.
+
+batadv_tvlv_container_ogm_append() could fail in two ways: a memory
+allocation failure when resizing the packet buffer, or the tvlv data
+exceeding U16_MAX bytes. In both cases the function previously returned the
+old (now stale) tvlv_value_len rather than signalling an error, causing the
+OGM/OGM2 send path to transmit a packet whose TVLV length field no longer
+matched the actual buffer contents. And because it also didn't fill in the
+new TVLV data, sending either uninitialized or corrupted data on the wire.
+
+All errors in batadv_tvlv_container_ogm_append() must be forwarded to the
+caller. And the caller must abort the send of the OGM2. For B.A.T.M.A.N.
+IV, it is currently not allowed to abort the send. The non-TVLV part of the
+OGM must be queued up instead.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 16 +++++++++++++---
+ net/batman-adv/bat_v_ogm.c | 26 ++++++++++++++------------
+ net/batman-adv/tvlv.c | 17 ++++++++++++-----
+ net/batman-adv/tvlv.h | 2 +-
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 495196b741040e..c93d30462054ce 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -782,6 +782,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+
+@@ -805,9 +806,18 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
+- ogm_buff_len,
+- BATADV_OGM_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM_HLEN);
++ if (ret < 0) {
++ /* OGMs must be queued even when the buffer allocation for
++ * TVLVs failed. just fall back to the non-TVLV version
++ */
++ ret = 0;
++ *ogm_buff_len = BATADV_OGM_HLEN;
++ }
++
++ tvlv_len = ret;
+ }
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff);
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 24beb06f7c332a..57d0c6862f449c 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -272,9 +272,9 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_ogm2_packet *ogm_packet;
+ struct sk_buff *skb, *skb_tmp;
+- unsigned char *ogm_buff;
+- int ogm_buff_len;
+- u16 tvlv_len = 0;
++ unsigned char **ogm_buff;
++ int *ogm_buff_len;
++ u16 tvlv_len;
+ int ret;
+
+ lockdep_assert_held(&bat_priv->bat_v.ogm_buff_mutex);
+@@ -282,25 +282,27 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING)
+ goto out;
+
+- ogm_buff = bat_priv->bat_v.ogm_buff;
+- ogm_buff_len = bat_priv->bat_v.ogm_buff_len;
++ ogm_buff = &bat_priv->bat_v.ogm_buff;
++ ogm_buff_len = &bat_priv->bat_v.ogm_buff_len;
++
+ /* tt changes have to be committed before the tvlv data is
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, &ogm_buff,
+- &ogm_buff_len,
+- BATADV_OGM2_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM2_HLEN);
++ if (ret < 0)
++ goto reschedule;
+
+- bat_priv->bat_v.ogm_buff = ogm_buff;
+- bat_priv->bat_v.ogm_buff_len = ogm_buff_len;
++ tvlv_len = ret;
+
+- skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + ogm_buff_len);
++ skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + *ogm_buff_len);
+ if (!skb)
+ goto reschedule;
+
+ skb_reserve(skb, ETH_HLEN);
+- skb_put_data(skb, ogm_buff, ogm_buff_len);
++ skb_put_data(skb, *ogm_buff, *ogm_buff_len);
+
+ ogm_packet = (struct batadv_ogm2_packet *)skb->data;
+ ogm_packet->seqno = htonl(atomic_read(&bat_priv->bat_v.ogm_seqno));
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 7ec2e234388454..83e20b0be747b6 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -8,6 +8,7 @@
+
+ #include <linux/byteorder/generic.h>
+ #include <linux/container_of.h>
++#include <linux/errno.h>
+ #include <linux/etherdevice.h>
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+@@ -306,9 +307,10 @@ static bool batadv_tvlv_realloc_packet_buff(unsigned char **packet_buff,
+ * The ogm packet might be enlarged or shrunk depending on the current size
+ * and the size of the to-be-appended tvlv containers.
+ *
+- * Return: size of all appended tvlv containers in bytes.
++ * Return: size of all appended tvlv containers in bytes (max U16_MAX), negative
++ * if operation failed
+ */
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len)
+ {
+@@ -316,6 +318,7 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_len;
+ void *tvlv_value;
++ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+@@ -323,9 +326,12 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+-
+- if (!ret)
++ if (!ret) {
++ tvlv_len_ret = -ENOMEM;
+ goto end;
++ }
++
++ tvlv_len_ret = tvlv_value_len;
+
+ if (!tvlv_value_len)
+ goto end;
+@@ -344,7 +350,8 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ end:
+ spin_unlock_bh(&bat_priv->tvlv.container_list_lock);
+- return tvlv_value_len;
++
++ return tvlv_len_ret;
+ }
+
+ /**
+diff --git a/net/batman-adv/tvlv.h b/net/batman-adv/tvlv.h
+index 4cf8af00fc11a0..485b2a6070994b 100644
+--- a/net/batman-adv/tvlv.h
++++ b/net/batman-adv/tvlv.h
+@@ -15,7 +15,7 @@
+ void batadv_tvlv_container_register(struct batadv_priv *bat_priv,
+ u8 type, u8 version,
+ void *tvlv_value, u16 tvlv_value_len);
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len);
+ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv,
+--
+2.53.0
+
--- /dev/null
+From 6d2cb253dc1b123413640e284d211f8c1fb70d2e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 21:47:23 +0200
+Subject: batman-adv: tvlv: reject oversized TVLV packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f50487e3566358b2b982b7801945e858c78ad9ab upstream.
+
+batadv_tvlv_container_ogm_append() builds a TVLV packet section from
+the tvlv.container_list. The total size of this section is computed by
+batadv_tvlv_container_list_size(), which sums the sizes of all registered
+containers.
+
+The return type and accumulator in batadv_tvlv_container_list_size() were
+u16. If the accumulated size exceeds U16_MAX, the value wraps around,
+causing the subsequent allocation in batadv_tvlv_container_ogm_append()
+to be undersized. The memcpy-style copy that follows would then write
+beyond the end of the allocated buffer, corrupting kernel memory.
+
+Fix this by widening the return type of batadv_tvlv_container_list_size()
+to size_t. In batadv_tvlv_container_ogm_append(), check the computed length
+against U16_MAX before proceeding, and bail out as if the allocation had
+failed when the limit is exceeded.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 83e20b0be747b6..99e5e8518dcc9a 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -13,6 +13,7 @@
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+ #include <linux/kref.h>
++#include <linux/limits.h>
+ #include <linux/list.h>
+ #include <linux/lockdep.h>
+ #include <linux/netdevice.h>
+@@ -160,10 +161,10 @@ batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version)
+ *
+ * Return: size of all currently registered tvlv containers in bytes.
+ */
+-static u16 batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
++static size_t batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
+ {
+ struct batadv_tvlv_container *tvlv;
+- u16 tvlv_len = 0;
++ size_t tvlv_len = 0;
+
+ lockdep_assert_held(&bat_priv->tvlv.container_list_lock);
+
+@@ -316,13 +317,17 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_container *tvlv;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+- u16 tvlv_value_len;
++ size_t tvlv_value_len;
+ void *tvlv_value;
+ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+ tvlv_value_len = batadv_tvlv_container_list_size(bat_priv);
++ if (tvlv_value_len > U16_MAX) {
++ tvlv_len_ret = -E2BIG;
++ goto end;
++ }
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+--
+2.53.0
+
--- /dev/null
+From 3d84d70d95aa09f24375c45f8fbfc7f0b503ae44 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:32:39 +0200
+Subject: batman-adv: v: stop OGMv2 on disabled interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f8ce8b8331a1bc44ad4905886a482214d428b253 upstream.
+
+When a batadv_hard_iface is disabled, its mesh_iface pointer is set to
+NULL. However, batadv_v_ogm_send_meshif() may still dispatch OGMs via
+batadv_v_ogm_queue_on_if() for interfaces that have since lost their
+mesh_iface association. This results in a NULL pointer dereference when
+batadv_v_ogm_queue_on_if() unconditionally calls netdev_priv() on the
+now NULL hard_iface->mesh_iface to retrieve the batadv_priv.
+
+It is necessary to ensure that the batadv_v_ogm_queue_on_if() checks that
+it is using the same mesh_iface for which batadv_v_ogm_send_meshif() was
+called.
+
+Cc: stable@kernel.org
+Fixes: 0da0035942d4 ("batman-adv: OGMv2 - add basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v_ogm.c | 33 +++++++++++++++++++++------------
+ 1 file changed, 21 insertions(+), 12 deletions(-)
+
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index deef817b28f0ae..24beb06f7c332a 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -116,14 +116,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
+
+ /**
+ * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to send
+ * @hard_iface: the interface to use to send the OGM
+ */
+-static void batadv_v_ogm_send_to_if(struct sk_buff *skb,
++static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+-
+ if (hard_iface->if_status != BATADV_IF_ACTIVE) {
+ kfree_skb(skb);
+ return;
+@@ -190,6 +190,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+
+ /**
+ * batadv_v_ogm_aggr_send() - flush & send aggregation queue
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @hard_iface: the interface with the aggregation queue to flush
+ *
+ * Aggregates all OGMv2 packets currently in the aggregation queue into a
+@@ -199,7 +200,8 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+ *
+ * Caller needs to hold the hard_iface->bat_v.aggr_list.lock.
+ */
+-static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
++static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv,
++ struct batadv_hard_iface *hard_iface)
+ {
+ unsigned int aggr_len = hard_iface->bat_v.aggr_len;
+ struct sk_buff *skb_aggr;
+@@ -229,27 +231,32 @@ static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
+ consume_skb(skb);
+ }
+
+- batadv_v_ogm_send_to_if(skb_aggr, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface);
+ }
+
+ /**
+ * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to queue
+ * @hard_iface: the interface to queue the OGM on
+ */
+-static void batadv_v_ogm_queue_on_if(struct sk_buff *skb,
++static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
++ if (hard_iface->soft_iface != bat_priv->soft_iface) {
++ kfree_skb(skb);
++ return;
++ }
+
+ if (!atomic_read(&bat_priv->aggregated_ogms)) {
+- batadv_v_ogm_send_to_if(skb, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface);
+ return;
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
+@@ -348,7 +355,7 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ break;
+ }
+
+- batadv_v_ogm_queue_on_if(skb_tmp, hard_iface);
++ batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface);
+ batadv_hardif_put(hard_iface);
+ }
+ rcu_read_unlock();
+@@ -388,12 +395,14 @@ void batadv_v_ogm_aggr_work(struct work_struct *work)
+ {
+ struct batadv_hard_iface_bat_v *batv;
+ struct batadv_hard_iface *hard_iface;
++ struct batadv_priv *bat_priv;
+
+ batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work);
+ hard_iface = container_of(batv, struct batadv_hard_iface, bat_v);
++ bat_priv = netdev_priv(hard_iface->soft_iface);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+
+ batadv_v_ogm_start_queue_timer(hard_iface);
+@@ -583,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv,
+ if_outgoing->net_dev->name, ntohl(ogm_forward->throughput),
+ ogm_forward->ttl, if_incoming->net_dev->name);
+
+- batadv_v_ogm_queue_on_if(skb, if_outgoing);
++ batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing);
+
+ out:
+ batadv_orig_ifinfo_put(orig_ifinfo);
+--
+2.53.0
+
--- /dev/null
+From 1f78df8b5f9c9ee8cbd8fb72f79a3630a0938941 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:17 +0200
+Subject: bpf: Fix a few selftest failures due to llvm18 change
+
+From: Yonghong Song <yonghong.song@linux.dev>
+
+[ Upstream commit b16904fd9f01b580db357ef2b1cc9e86d89576c2 ]
+
+With latest upstream llvm18, the following test cases failed:
+
+ $ ./test_progs -j
+ #13/2 bpf_cookie/multi_kprobe_link_api:FAIL
+ #13/3 bpf_cookie/multi_kprobe_attach_api:FAIL
+ #13 bpf_cookie:FAIL
+ #77 fentry_fexit:FAIL
+ #78/1 fentry_test/fentry:FAIL
+ #78 fentry_test:FAIL
+ #82/1 fexit_test/fexit:FAIL
+ #82 fexit_test:FAIL
+ #112/1 kprobe_multi_test/skel_api:FAIL
+ #112/2 kprobe_multi_test/link_api_addrs:FAIL
+ [...]
+ #112 kprobe_multi_test:FAIL
+ #356/17 test_global_funcs/global_func17:FAIL
+ #356 test_global_funcs:FAIL
+
+Further analysis shows llvm upstream patch [1] is responsible for the above
+failures. For example, for function bpf_fentry_test7() in net/bpf/test_run.c,
+without [1], the asm code is:
+
+ 0000000000000400 <bpf_fentry_test7>:
+ 400: f3 0f 1e fa endbr64
+ 404: e8 00 00 00 00 callq 0x409 <bpf_fentry_test7+0x9>
+ 409: 48 89 f8 movq %rdi, %rax
+ 40c: c3 retq
+ 40d: 0f 1f 00 nopl (%rax)
+
+... and with [1], the asm code is:
+
+ 0000000000005d20 <bpf_fentry_test7.specialized.1>:
+ 5d20: e8 00 00 00 00 callq 0x5d25 <bpf_fentry_test7.specialized.1+0x5>
+ 5d25: c3 retq
+
+... and <bpf_fentry_test7.specialized.1> is called instead of <bpf_fentry_test7>
+and this caused test failures for #13/#77 etc. except #356.
+
+For test case #356/17, with [1] (progs/test_global_func17.c)), the main prog
+looks like:
+
+ 0000000000000000 <global_func17>:
+ 0: b4 00 00 00 2a 00 00 00 w0 = 0x2a
+ 1: 95 00 00 00 00 00 00 00 exit
+
+... which passed verification while the test itself expects a verification
+failure.
+
+Let us add 'barrier_var' style asm code in both places to prevent function
+specialization which caused selftests failure.
+
+ [1] https://github.com/llvm/llvm-project/pull/72903
+
+Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Link: https://lore.kernel.org/bpf/20231127050342.1945270-1-yonghong.song@linux.dev
+[ Note: The change to test_run.c conflicted and was dropped. The related
+ tests are not failing anyway. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/progs/test_global_func17.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func17.c b/tools/testing/selftests/bpf/progs/test_global_func17.c
+index a32e11c7d933ee..5de44b09e8ec17 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func17.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func17.c
+@@ -5,6 +5,7 @@
+
+ __noinline int foo(int *p)
+ {
++ barrier_var(p);
+ return p ? (*p = 42) : 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 4fc18faf30e836829a43e90153d8dd7ee4d5a561 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:41:56 +0300
+Subject: drm/dp: Add eDP 1.5 bit definition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Suraj Kandpal <suraj.kandpal@intel.com>
+
+commit 5dfc37a6b77bf6beedbd30d70184b54e1a08ccac upstream.
+
+Add the eDP revision bit value for 1.5.
+
+Spec: eDPv1.5 Table 16-5
+Signed-off-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Reviewed-by: Arun R Murthy <arun.r.murthy@intel.com>
+Tested-by: Ben Kao <ben.kao@intel.com>
+Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Link: https://patchwork.freedesktop.org/patch/msgid/20250206063253.2827017-2-suraj.kandpal@intel.com
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/drm/display/drm_dp.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
+index b235d6833e27d9..7d3700490b68f0 100644
+--- a/include/drm/display/drm_dp.h
++++ b/include/drm/display/drm_dp.h
+@@ -955,6 +955,7 @@
+ # define DP_EDP_14 0x03
+ # define DP_EDP_14a 0x04 /* eDP 1.4a */
+ # define DP_EDP_14b 0x05 /* eDP 1.4b */
++# define DP_EDP_15 0x06 /* eDP 1.5 */
+
+ #define DP_EDP_GENERAL_CAP_1 0x701
+ # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0)
+--
+2.53.0
+
--- /dev/null
+From de285df3961bfcee727a1db556a0809c1eda0703 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:41:58 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From 58a274e4fbb6d4e252a1ae8d2778587dcf3df8f5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:41:55 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 27 +++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 01fadf300ff679..3c5553b21fa894 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -818,6 +818,30 @@ static bool psr2_granularity_check(struct intel_dp *intel_dp,
+ return true;
+ }
+
++static bool apply_scanline_indication_wa(struct intel_dp *intel_dp,
++ struct intel_crtc_state *crtc_state)
++{
++ u8 early_scanline_support = intel_dp->intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
+ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+ struct intel_crtc_state *crtc_state)
+ {
+@@ -839,7 +863,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(intel_dp, crtc_state);
+ }
+
+ static bool _compute_psr2_wake_times(struct intel_dp *intel_dp,
+--
+2.53.0
+
--- /dev/null
+From 67a94aeaa3005fbf324321fe756ad27611534263 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 11:41:57 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index a8bf91a21cb246..a26c082bdc3289 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -1637,6 +1637,7 @@ struct intel_dp {
+ u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE];
+ u8 fec_capable;
+ u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+ /* source rates */
+ int num_source_rates;
+ const int *source_rates;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index a465b192931085..01fadf300ff679 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -31,6 +31,7 @@
+ #include "intel_crtc.h"
+ #include "intel_de.h"
+ #include "intel_display_types.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_hdmi.h"
+ #include "intel_psr.h"
+@@ -388,6 +389,12 @@ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
+ intel_dp_get_su_granularity(intel_dp);
+ }
+ }
++
++ if (intel_dp->psr.sink_psr2_support)
++ drm_dp_dpcd_read(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &intel_dp->intel_wa_dpcd,
++ sizeof(intel_dp->intel_wa_dpcd));
+ }
+
+ static void intel_psr_enable_sink(struct intel_dp *intel_dp)
+--
+2.53.0
+
--- /dev/null
+From 0492d9ae159d6151c3e09cd2d80ebf2f33eaa129 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:02:30 +0800
+Subject: mm/page_alloc: clear page->private in free_pages_prepare()
+
+From: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+
+[ Upstream commit ac1ea219590c09572ed5992dc233bbf7bb70fef9 ]
+
+Several subsystems (slub, shmem, ttm, etc.) use page->private but don't
+clear it before freeing pages. When these pages are later allocated as
+high-order pages and split via split_page(), tail pages retain stale
+page->private values.
+
+This causes a use-after-free in the swap subsystem. The swap code uses
+page->private to track swap count continuations, assuming freshly
+allocated pages have page->private == 0. When stale values are present,
+swap_count_continued() incorrectly assumes the continuation list is valid
+and iterates over uninitialized page->lru containing LIST_POISON values,
+causing a crash:
+
+ KASAN: maybe wild-memory-access in range [0xdead000000000100-0xdead000000000107]
+ RIP: 0010:__do_sys_swapoff+0x1151/0x1860
+
+Fix this by clearing page->private in free_pages_prepare(), ensuring all
+freed pages have clean state regardless of previous use.
+
+Link: https://lkml.kernel.org/r/20260207173615.146159-1-mikhail.v.gavrilov@gmail.com
+Fixes: 3b8000ae185c ("mm/vmalloc: huge vmalloc backing pages should be split rather than compound")
+Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+Suggested-by: Zi Yan <ziy@nvidia.com>
+Acked-by: Zi Yan <ziy@nvidia.com>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Reviewed-by: Vlastimil Babka <vbabka@suse.cz>
+Cc: Brendan Jackman <jackmanb@google.com>
+Cc: Chris Li <chrisl@kernel.org>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Kairui Song <ryncsn@gmail.com>
+Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+[backport: context only]
+Signed-off-by: Li Wang <li.wang@windriver.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ mm/page_alloc.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mm/page_alloc.c b/mm/page_alloc.c
+index 4c17b9ceff8099..93c63b5fad2889 100644
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -1483,6 +1483,7 @@ static __always_inline bool free_pages_prepare(struct page *page,
+
+ page_cpupid_reset_last(page);
+ page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
++ page->private = 0;
+ reset_page_owner(page, order);
+ page_table_check_free(page, order);
+
+--
+2.53.0
+
--- /dev/null
+From 180d118cc965bf89a41abdc5925f9a749af9d486 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Mar 2023 01:10:12 +0000
+Subject: net/packet: convert po->has_vnet_hdr to an atomic flag
+
+From: Eric Dumazet <edumazet@google.com>
+
+[ Upstream commit 50d935eafee292fc432d5ac8c8715a6492961abc ]
+
+po->has_vnet_hdr can be read locklessly.
+
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 2c054e17d9d4 ("net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/packet/af_packet.c | 19 ++++++++++---------
+ net/packet/diag.c | 2 +-
+ net/packet/internal.h | 2 +-
+ 3 files changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 490bfec158035e..50d9618d85f3c7 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -2357,7 +2357,7 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
+ netoff = TPACKET_ALIGN(po->tp_hdrlen +
+ (maclen < 16 ? 16 : maclen)) +
+ po->tp_reserve;
+- if (po->has_vnet_hdr) {
++ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+ netoff += sizeof(struct virtio_net_hdr);
+ do_vnet = true;
+ }
+@@ -2831,7 +2831,8 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ size_max = po->tx_ring.frame_size
+ - (po->tp_hdrlen - sizeof(struct sockaddr_ll));
+
+- if ((size_max > dev->mtu + reserve + VLAN_HLEN) && !po->has_vnet_hdr)
++ if ((size_max > dev->mtu + reserve + VLAN_HLEN) &&
++ !packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR))
+ size_max = dev->mtu + reserve + VLAN_HLEN;
+
+ timeo = sock_sndtimeo(&po->sk, msg->msg_flags & MSG_DONTWAIT);
+@@ -2866,7 +2867,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ status = TP_STATUS_SEND_REQUEST;
+ hlen = LL_RESERVED_SPACE(dev);
+ tlen = dev->needed_tailroom;
+- if (po->has_vnet_hdr) {
++ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+ vnet_hdr = data;
+ data += sizeof(*vnet_hdr);
+ tp_len -= sizeof(*vnet_hdr);
+@@ -2894,7 +2895,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ addr, hlen, copylen, &sockc);
+ if (likely(tp_len >= 0) &&
+ tp_len > dev->mtu + reserve &&
+- !po->has_vnet_hdr &&
++ !packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR) &&
+ !packet_extra_vlan_len_allowed(dev, skb))
+ tp_len = -EMSGSIZE;
+
+@@ -2913,7 +2914,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ }
+ }
+
+- if (po->has_vnet_hdr) {
++ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+ if (virtio_net_hdr_to_skb(skb, vnet_hdr, vio_le())) {
+ tp_len = -EINVAL;
+ goto tpacket_error;
+@@ -3041,7 +3042,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
+
+ if (sock->type == SOCK_RAW)
+ reserve = dev->hard_header_len;
+- if (po->has_vnet_hdr) {
++ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+ err = packet_snd_vnet_parse(msg, &len, &vnet_hdr);
+ if (err)
+ goto out_unlock;
+@@ -3506,7 +3507,7 @@ static int packet_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
+
+ packet_rcv_try_clear_pressure(pkt_sk(sk));
+
+- if (pkt_sk(sk)->has_vnet_hdr) {
++ if (packet_sock_flag(pkt_sk(sk), PACKET_SOCK_HAS_VNET_HDR)) {
+ err = packet_rcv_vnet(msg, skb, &len);
+ if (err)
+ goto out_free;
+@@ -4002,7 +4003,7 @@ packet_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval,
+ if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
+ ret = -EBUSY;
+ } else {
+- po->has_vnet_hdr = !!val;
++ packet_sock_flag_set(po, PACKET_SOCK_HAS_VNET_HDR, val);
+ ret = 0;
+ }
+ release_sock(sk);
+@@ -4136,7 +4137,7 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
+ val = packet_sock_flag(po, PACKET_SOCK_ORIGDEV);
+ break;
+ case PACKET_VNET_HDR:
+- val = po->has_vnet_hdr;
++ val = packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR);
+ break;
+ case PACKET_VERSION:
+ val = po->tp_version;
+diff --git a/net/packet/diag.c b/net/packet/diag.c
+index 677d442cd930fe..a3bd91dba43945 100644
+--- a/net/packet/diag.c
++++ b/net/packet/diag.c
+@@ -27,7 +27,7 @@ static int pdiag_put_info(const struct packet_sock *po, struct sk_buff *nlskb)
+ pinfo.pdi_flags |= PDI_AUXDATA;
+ if (packet_sock_flag(po, PACKET_SOCK_ORIGDEV))
+ pinfo.pdi_flags |= PDI_ORIGDEV;
+- if (po->has_vnet_hdr)
++ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR))
+ pinfo.pdi_flags |= PDI_VNETHDR;
+ if (packet_sock_flag(po, PACKET_SOCK_TP_LOSS))
+ pinfo.pdi_flags |= PDI_LOSS;
+diff --git a/net/packet/internal.h b/net/packet/internal.h
+index 82a997824e5733..0956e4a934492d 100644
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -118,7 +118,6 @@ struct packet_sock {
+ struct mutex pg_vec_lock;
+ unsigned long flags;
+ unsigned int running; /* bind_lock must be held */
+- unsigned int has_vnet_hdr:1; /* writer must hold sock lock */
+ int pressure;
+ int ifindex; /* bound device */
+ __be16 num;
+@@ -146,6 +145,7 @@ enum packet_sock_flags {
+ PACKET_SOCK_AUXDATA,
+ PACKET_SOCK_TX_HAS_OFF,
+ PACKET_SOCK_TP_LOSS,
++ PACKET_SOCK_HAS_VNET_HDR,
+ };
+
+ static inline void packet_sock_flag_set(struct packet_sock *po,
+--
+2.53.0
+
--- /dev/null
+From 13efc9420cf09d14d84d4fa00f016fbc8fd92933 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Mar 2023 01:10:13 +0000
+Subject: net/packet: convert po->running to an atomic flag
+
+From: Eric Dumazet <edumazet@google.com>
+
+[ Upstream commit 61edf479818e63978cabd243b82ca80f8948a313 ]
+
+Instead of consuming 32 bits for po->running, use
+one available bit in po->flags.
+
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 2c054e17d9d4 ("net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/packet/af_packet.c | 20 ++++++++++----------
+ net/packet/diag.c | 2 +-
+ net/packet/internal.h | 2 +-
+ 3 files changed, 12 insertions(+), 12 deletions(-)
+
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 50d9618d85f3c7..36347814ec7ceb 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -340,14 +340,14 @@ static void __register_prot_hook(struct sock *sk)
+ {
+ struct packet_sock *po = pkt_sk(sk);
+
+- if (!po->running) {
++ if (!packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
+ if (po->fanout)
+ __fanout_link(sk, po);
+ else
+ dev_add_pack(&po->prot_hook);
+
+ sock_hold(sk);
+- po->running = 1;
++ packet_sock_flag_set(po, PACKET_SOCK_RUNNING, 1);
+ }
+ }
+
+@@ -369,7 +369,7 @@ static void __unregister_prot_hook(struct sock *sk, bool sync)
+
+ lockdep_assert_held_once(&po->bind_lock);
+
+- po->running = 0;
++ packet_sock_flag_set(po, PACKET_SOCK_RUNNING, 0);
+
+ if (po->fanout)
+ __fanout_unlink(sk, po);
+@@ -389,7 +389,7 @@ static void unregister_prot_hook(struct sock *sk, bool sync)
+ {
+ struct packet_sock *po = pkt_sk(sk);
+
+- if (po->running)
++ if (packet_sock_flag(po, PACKET_SOCK_RUNNING))
+ __unregister_prot_hook(sk, sync);
+ }
+
+@@ -1834,7 +1834,7 @@ static int fanout_add(struct sock *sk, struct fanout_args *args)
+ err = -EINVAL;
+
+ spin_lock(&po->bind_lock);
+- if (po->running &&
++ if (packet_sock_flag(po, PACKET_SOCK_RUNNING) &&
+ match->type == type &&
+ match->prot_hook.type == po->prot_hook.type &&
+ match->prot_hook.dev == po->prot_hook.dev) {
+@@ -3277,7 +3277,7 @@ static int packet_do_bind(struct sock *sk, const char *name, int ifindex,
+
+ if (need_rehook) {
+ dev_hold(dev);
+- if (po->running) {
++ if (packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
+ rcu_read_unlock();
+ /* prevents packet_notifier() from calling
+ * register_prot_hook()
+@@ -3290,7 +3290,7 @@ static int packet_do_bind(struct sock *sk, const char *name, int ifindex,
+ dev->ifindex);
+ }
+
+- BUG_ON(po->running);
++ BUG_ON(packet_sock_flag(po, PACKET_SOCK_RUNNING));
+ WRITE_ONCE(po->num, proto);
+ po->prot_hook.type = proto;
+
+@@ -4230,7 +4230,7 @@ static int packet_notifier(struct notifier_block *this,
+ case NETDEV_DOWN:
+ if (dev->ifindex == po->ifindex) {
+ spin_lock(&po->bind_lock);
+- if (po->running) {
++ if (packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
+ __unregister_prot_hook(sk, false);
+ sk->sk_err = ENETDOWN;
+ if (!sock_flag(sk, SOCK_DEAD))
+@@ -4541,7 +4541,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
+
+ /* Detach socket from network */
+ spin_lock(&po->bind_lock);
+- was_running = po->running;
++ was_running = packet_sock_flag(po, PACKET_SOCK_RUNNING);
+ num = po->num;
+ WRITE_ONCE(po->num, 0);
+ if (was_running)
+@@ -4752,7 +4752,7 @@ static int packet_seq_show(struct seq_file *seq, void *v)
+ s->sk_type,
+ ntohs(READ_ONCE(po->num)),
+ READ_ONCE(po->ifindex),
+- po->running,
++ packet_sock_flag(po, PACKET_SOCK_RUNNING),
+ atomic_read(&s->sk_rmem_alloc),
+ from_kuid_munged(seq_user_ns(seq), sock_i_uid(s)),
+ sock_i_ino(s));
+diff --git a/net/packet/diag.c b/net/packet/diag.c
+index a3bd91dba43945..cd30cc619c6b45 100644
+--- a/net/packet/diag.c
++++ b/net/packet/diag.c
+@@ -21,7 +21,7 @@ static int pdiag_put_info(const struct packet_sock *po, struct sk_buff *nlskb)
+ pinfo.pdi_tstamp = po->tp_tstamp;
+
+ pinfo.pdi_flags = 0;
+- if (po->running)
++ if (packet_sock_flag(po, PACKET_SOCK_RUNNING))
+ pinfo.pdi_flags |= PDI_RUNNING;
+ if (packet_sock_flag(po, PACKET_SOCK_AUXDATA))
+ pinfo.pdi_flags |= PDI_AUXDATA;
+diff --git a/net/packet/internal.h b/net/packet/internal.h
+index 0956e4a934492d..9e50bf06131f29 100644
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -117,7 +117,6 @@ struct packet_sock {
+ spinlock_t bind_lock;
+ struct mutex pg_vec_lock;
+ unsigned long flags;
+- unsigned int running; /* bind_lock must be held */
+ int pressure;
+ int ifindex; /* bound device */
+ __be16 num;
+@@ -146,6 +145,7 @@ enum packet_sock_flags {
+ PACKET_SOCK_TX_HAS_OFF,
+ PACKET_SOCK_TP_LOSS,
+ PACKET_SOCK_HAS_VNET_HDR,
++ PACKET_SOCK_RUNNING,
+ };
+
+ static inline void packet_sock_flag_set(struct packet_sock *po,
+--
+2.53.0
+
--- /dev/null
+From 77ae60fdc1c3953fa714875b4114a83a77382503 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Mar 2023 01:10:11 +0000
+Subject: net/packet: convert po->tp_loss to an atomic flag
+
+From: Eric Dumazet <edumazet@google.com>
+
+[ Upstream commit 164bddace2e03f6005e650cb88f101a66ebdc05a ]
+
+tp_loss can be read locklessly.
+
+Convert it to an atomic flag to avoid races.
+
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 2c054e17d9d4 ("net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/packet/af_packet.c | 6 +++---
+ net/packet/diag.c | 2 +-
+ net/packet/internal.h | 4 ++--
+ 3 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 1ceb8f765114b3..490bfec158035e 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -2900,7 +2900,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+
+ if (unlikely(tp_len < 0)) {
+ tpacket_error:
+- if (po->tp_loss) {
++ if (packet_sock_flag(po, PACKET_SOCK_TP_LOSS)) {
+ __packet_set_status(po, ph,
+ TP_STATUS_AVAILABLE);
+ packet_increment_head(&po->tx_ring);
+@@ -3957,7 +3957,7 @@ packet_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval,
+ if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
+ ret = -EBUSY;
+ } else {
+- po->tp_loss = !!val;
++ packet_sock_flag_set(po, PACKET_SOCK_TP_LOSS, val);
+ ret = 0;
+ }
+ release_sock(sk);
+@@ -4166,7 +4166,7 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
+ val = po->tp_reserve;
+ break;
+ case PACKET_LOSS:
+- val = po->tp_loss;
++ val = packet_sock_flag(po, PACKET_SOCK_TP_LOSS);
+ break;
+ case PACKET_TIMESTAMP:
+ val = po->tp_tstamp;
+diff --git a/net/packet/diag.c b/net/packet/diag.c
+index 057ee37bd0766c..677d442cd930fe 100644
+--- a/net/packet/diag.c
++++ b/net/packet/diag.c
+@@ -29,7 +29,7 @@ static int pdiag_put_info(const struct packet_sock *po, struct sk_buff *nlskb)
+ pinfo.pdi_flags |= PDI_ORIGDEV;
+ if (po->has_vnet_hdr)
+ pinfo.pdi_flags |= PDI_VNETHDR;
+- if (po->tp_loss)
++ if (packet_sock_flag(po, PACKET_SOCK_TP_LOSS))
+ pinfo.pdi_flags |= PDI_LOSS;
+
+ return nla_put(nlskb, PACKET_DIAG_INFO, sizeof(pinfo), &pinfo);
+diff --git a/net/packet/internal.h b/net/packet/internal.h
+index 31bac09a687233..82a997824e5733 100644
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -118,8 +118,7 @@ struct packet_sock {
+ struct mutex pg_vec_lock;
+ unsigned long flags;
+ unsigned int running; /* bind_lock must be held */
+- unsigned int has_vnet_hdr:1, /* writer must hold sock lock */
+- tp_loss:1;
++ unsigned int has_vnet_hdr:1; /* writer must hold sock lock */
+ int pressure;
+ int ifindex; /* bound device */
+ __be16 num;
+@@ -146,6 +145,7 @@ enum packet_sock_flags {
+ PACKET_SOCK_ORIGDEV,
+ PACKET_SOCK_AUXDATA,
+ PACKET_SOCK_TX_HAS_OFF,
++ PACKET_SOCK_TP_LOSS,
+ };
+
+ static inline void packet_sock_flag_set(struct packet_sock *po,
+--
+2.53.0
+
--- /dev/null
+From 7f5cb358e765ae78093a336a4e03a1c85474e9db Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Mar 2023 01:10:10 +0000
+Subject: net/packet: convert po->tp_tx_has_off to an atomic flag
+
+From: Eric Dumazet <edumazet@google.com>
+
+[ Upstream commit 7438344660fa55b33b8234c1797c886eb73667a7 ]
+
+This is to use existing space in po->flags, and reclaim
+the storage used by the non atomic bit fields.
+
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 2c054e17d9d4 ("net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/packet/af_packet.c | 6 +++---
+ net/packet/internal.h | 4 ++--
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 502d2f6de18a29..1ceb8f765114b3 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -2723,7 +2723,7 @@ static int tpacket_parse_header(struct packet_sock *po, void *frame,
+ return -EMSGSIZE;
+ }
+
+- if (unlikely(po->tp_tx_has_off)) {
++ if (unlikely(packet_sock_flag(po, PACKET_SOCK_TX_HAS_OFF))) {
+ int off_min, off_max;
+
+ off_min = po->tp_hdrlen - sizeof(struct sockaddr_ll);
+@@ -4064,7 +4064,7 @@ packet_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval,
+
+ lock_sock(sk);
+ if (!po->rx_ring.pg_vec && !po->tx_ring.pg_vec)
+- po->tp_tx_has_off = !!val;
++ packet_sock_flag_set(po, PACKET_SOCK_TX_HAS_OFF, val);
+
+ release_sock(sk);
+ return 0;
+@@ -4191,7 +4191,7 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
+ lv = sizeof(rstats);
+ break;
+ case PACKET_TX_HAS_OFF:
+- val = po->tp_tx_has_off;
++ val = packet_sock_flag(po, PACKET_SOCK_TX_HAS_OFF);
+ break;
+ case PACKET_QDISC_BYPASS:
+ val = packet_use_direct_xmit(po);
+diff --git a/net/packet/internal.h b/net/packet/internal.h
+index b2edfe6fc8e770..31bac09a687233 100644
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -119,8 +119,7 @@ struct packet_sock {
+ unsigned long flags;
+ unsigned int running; /* bind_lock must be held */
+ unsigned int has_vnet_hdr:1, /* writer must hold sock lock */
+- tp_loss:1,
+- tp_tx_has_off:1;
++ tp_loss:1;
+ int pressure;
+ int ifindex; /* bound device */
+ __be16 num;
+@@ -146,6 +145,7 @@ static inline struct packet_sock *pkt_sk(struct sock *sk)
+ enum packet_sock_flags {
+ PACKET_SOCK_ORIGDEV,
+ PACKET_SOCK_AUXDATA,
++ PACKET_SOCK_TX_HAS_OFF,
+ };
+
+ static inline void packet_sock_flag_set(struct packet_sock *po,
+--
+2.53.0
+
--- /dev/null
+From d224d8234c6d43a6c634e3671b057f508601244f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Apr 2026 19:20:06 +0800
+Subject: net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()
+
+From: Bingquan Chen <patzilla007@gmail.com>
+
+[ Upstream commit 2c054e17d9d41f1020376806c7f750834ced4dc5 ]
+
+In tpacket_snd(), when PACKET_VNET_HDR is enabled, vnet_hdr points
+directly into the mmap'd TX ring buffer shared with userspace. The
+kernel validates the header via __packet_snd_vnet_parse() but then
+re-reads all fields later in virtio_net_hdr_to_skb(). A concurrent
+userspace thread can modify the vnet_hdr fields between validation
+and use, bypassing all safety checks.
+
+The non-TPACKET path (packet_snd()) already correctly copies vnet_hdr
+to a stack-local variable. All other vnet_hdr consumers in the kernel
+(tun.c, tap.c, virtio_net.c) also use stack copies. The TPACKET TX
+path is the only caller of virtio_net_hdr_to_skb() that reads directly
+from user-controlled shared memory.
+
+Fix this by copying vnet_hdr from the mmap'd ring buffer to a
+stack-local variable before validation and use, consistent with the
+approach used in packet_snd() and all other callers.
+
+Fixes: 1d036d25e560 ("packet: tpacket_snd gso and checksum offload")
+Signed-off-by: Bingquan Chen <patzilla007@gmail.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Link: https://patch.msgid.link/20260418112006.78823-1-patzilla007@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/packet/af_packet.c | 25 +++++++++++++++----------
+ 1 file changed, 15 insertions(+), 10 deletions(-)
+
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 36347814ec7ceb..f3850784d66404 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -2767,7 +2767,8 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ {
+ struct sk_buff *skb = NULL;
+ struct net_device *dev;
+- struct virtio_net_hdr *vnet_hdr = NULL;
++ struct virtio_net_hdr vnet_hdr;
++ bool has_vnet_hdr = false;
+ struct sockcm_cookie sockc;
+ __be16 proto;
+ int err, reserve = 0;
+@@ -2868,16 +2869,20 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ hlen = LL_RESERVED_SPACE(dev);
+ tlen = dev->needed_tailroom;
+ if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+- vnet_hdr = data;
+- data += sizeof(*vnet_hdr);
+- tp_len -= sizeof(*vnet_hdr);
+- if (tp_len < 0 ||
+- __packet_snd_vnet_parse(vnet_hdr, tp_len)) {
++ data += sizeof(vnet_hdr);
++ tp_len -= sizeof(vnet_hdr);
++ if (tp_len < 0) {
++ tp_len = -EINVAL;
++ goto tpacket_error;
++ }
++ memcpy(&vnet_hdr, data - sizeof(vnet_hdr), sizeof(vnet_hdr));
++ if (__packet_snd_vnet_parse(&vnet_hdr, tp_len)) {
+ tp_len = -EINVAL;
+ goto tpacket_error;
+ }
+ copylen = __virtio16_to_cpu(vio_le(),
+- vnet_hdr->hdr_len);
++ vnet_hdr.hdr_len);
++ has_vnet_hdr = true;
+ }
+ copylen = max_t(int, copylen, dev->hard_header_len);
+ skb = sock_alloc_send_skb(&po->sk,
+@@ -2914,12 +2919,12 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
+ }
+ }
+
+- if (packet_sock_flag(po, PACKET_SOCK_HAS_VNET_HDR)) {
+- if (virtio_net_hdr_to_skb(skb, vnet_hdr, vio_le())) {
++ if (has_vnet_hdr) {
++ if (virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le())) {
+ tp_len = -EINVAL;
+ goto tpacket_error;
+ }
+- virtio_net_hdr_set_proto(skb, vnet_hdr);
++ virtio_net_hdr_set_proto(skb, &vnet_hdr);
+ }
+
+ skb->destructor = tpacket_destruct_skb;
+--
+2.53.0
+
--- /dev/null
+From acb45424624dbd089eb23e89603533941407cff5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:13:39 +0200
+Subject: RDMA/rxe: Fix double free in rxe_srq_from_init
+
+From: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+
+commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream.
+
+In rxe_srq_from_init(), the queue pointer 'q' is assigned to
+'srq->rq.queue' before copying the SRQ number to user space.
+If copy_to_user() fails, the function calls rxe_queue_cleanup()
+to free the queue, but leaves the now-invalid pointer in
+'srq->rq.queue'.
+
+The caller of rxe_srq_from_init() (rxe_create_srq) eventually
+calls rxe_srq_cleanup() upon receiving the error, which triggers
+a second rxe_queue_cleanup() on the same memory, leading to a
+double free.
+
+The call trace looks like this:
+ kmem_cache_free+0x.../0x...
+ rxe_queue_cleanup+0x1a/0x30 [rdma_rxe]
+ rxe_srq_cleanup+0x42/0x60 [rdma_rxe]
+ rxe_elem_release+0x31/0x70 [rdma_rxe]
+ rxe_create_srq+0x12b/0x1a0 [rdma_rxe]
+ ib_create_srq_user+0x9a/0x150 [ib_core]
+
+Fix this by moving 'srq->rq.queue = q' after copy_to_user.
+
+Fixes: aae0484e15f0 ("IB/rxe: avoid srq memory leak")
+Signed-off-by: Jiasheng Jiang <jiashengjiangcool@gmail.com>
+Link: https://patch.msgid.link/20260112015412.29458-1-jiashengjiangcool@gmail.com
+Reviewed-by: Zhu Yanjun <yanjun.Zhu@linux.dev>
+Signed-off-by: Leon Romanovsky <leon@kernel.org>
+[bwh: Backported to 6.1: There was no assignment to init->attr.max_wr
+ here; don't add it]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index 02b39498c370d2..038a9cd55413e0 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -69,8 +69,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ return -ENOMEM;
+ }
+
+- srq->rq.queue = q;
+-
+ err = do_mmap_info(rxe, uresp ? &uresp->mi : NULL, udata, q->buf,
+ q->buf_size, &q->ip);
+ if (err) {
+@@ -87,6 +85,8 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
++ srq->rq.queue = q;
++
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From a3c06ee57522a4446b12cdab414e52d175fad96a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:13:17 +0200
+Subject: Revert "RDMA/rxe: Fix double free in rxe_srq_from_init"
+
+From: Ben Hutchings <benh@debian.org>
+
+This reverts commit d286f0d4e3ad3caf5f0e673cdad7bf89bf37d947, which
+was commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 upstream. The
+backported version did not move but duplicated the problematic
+assignment, so it did not fix the bug. A proper backport will follow.
+
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/infiniband/sw/rxe/rxe_srq.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c
+index 115ff5428f6cfb..02b39498c370d2 100644
+--- a/drivers/infiniband/sw/rxe/rxe_srq.c
++++ b/drivers/infiniband/sw/rxe/rxe_srq.c
+@@ -87,9 +87,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq,
+ }
+ }
+
+- srq->rq.queue = q;
+- init->attr.max_wr = srq->rq.max_wr;
+-
+ return 0;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 7b922c862481a1e71cc830831d0d5cfe080a4e4f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:46 +0200
+Subject: Revert "selftests/bpf: Add a cgroup prog
+ bpf_get_ns_current_pid_tgid() test"
+
+From: Paul Chaignon <paul.chaignon@gmail.com>
+
+This reverts commit 4d8fb7ed7a55 ("selftests/bpf: Add a cgroup prog
+bpf_get_ns_current_pid_tgid() test").
+
+That commit should have never been backported to 6.1 because it
+introduces a test for a feature that isn't supported:
+bpf_get_ns_current_pid_tgid() cannot be called from cgroup BPF programs
+in 6.1.
+
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ .../bpf/prog_tests/ns_current_pid_tgid.c | 73 -------------------
+ .../bpf/progs/test_ns_current_pid_tgid.c | 7 --
+ 2 files changed, 80 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/prog_tests/ns_current_pid_tgid.c b/tools/testing/selftests/bpf/prog_tests/ns_current_pid_tgid.c
+index 2c57ceede095eb..a84c41862ff8c9 100644
+--- a/tools/testing/selftests/bpf/prog_tests/ns_current_pid_tgid.c
++++ b/tools/testing/selftests/bpf/prog_tests/ns_current_pid_tgid.c
+@@ -12,7 +12,6 @@
+ #include <sys/wait.h>
+ #include <sys/mount.h>
+ #include <fcntl.h>
+-#include "network_helpers.h"
+
+ #define STACK_SIZE (1024 * 1024)
+ static char child_stack[STACK_SIZE];
+@@ -75,50 +74,6 @@ static int test_current_pid_tgid_tp(void *args)
+ return ret;
+ }
+
+-static int test_current_pid_tgid_cgrp(void *args)
+-{
+- struct test_ns_current_pid_tgid__bss *bss;
+- struct test_ns_current_pid_tgid *skel;
+- int server_fd = -1, ret = -1, err;
+- int cgroup_fd = *(int *)args;
+- pid_t tgid, pid;
+-
+- skel = test_ns_current_pid_tgid__open();
+- if (!ASSERT_OK_PTR(skel, "test_ns_current_pid_tgid__open"))
+- return ret;
+-
+- bpf_program__set_autoload(skel->progs.cgroup_bind4, true);
+-
+- err = test_ns_current_pid_tgid__load(skel);
+- if (!ASSERT_OK(err, "test_ns_current_pid_tgid__load"))
+- goto cleanup;
+-
+- bss = skel->bss;
+- if (get_pid_tgid(&pid, &tgid, bss))
+- goto cleanup;
+-
+- skel->links.cgroup_bind4 = bpf_program__attach_cgroup(
+- skel->progs.cgroup_bind4, cgroup_fd);
+- if (!ASSERT_OK_PTR(skel->links.cgroup_bind4, "bpf_program__attach_cgroup"))
+- goto cleanup;
+-
+- server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
+- if (!ASSERT_GE(server_fd, 0, "start_server"))
+- goto cleanup;
+-
+- if (!ASSERT_EQ(bss->user_pid, pid, "pid"))
+- goto cleanup;
+- if (!ASSERT_EQ(bss->user_tgid, tgid, "tgid"))
+- goto cleanup;
+- ret = 0;
+-
+-cleanup:
+- if (server_fd >= 0)
+- close(server_fd);
+- test_ns_current_pid_tgid__destroy(skel);
+- return ret;
+-}
+-
+ static void test_ns_current_pid_tgid_new_ns(int (*fn)(void *), void *arg)
+ {
+ int wstatus;
+@@ -140,25 +95,6 @@ static void test_ns_current_pid_tgid_new_ns(int (*fn)(void *), void *arg)
+ return;
+ }
+
+-static void test_in_netns(int (*fn)(void *), void *arg)
+-{
+- struct nstoken *nstoken = NULL;
+-
+- SYS(cleanup, "ip netns add ns_current_pid_tgid");
+- SYS(cleanup, "ip -net ns_current_pid_tgid link set dev lo up");
+-
+- nstoken = open_netns("ns_current_pid_tgid");
+- if (!ASSERT_OK_PTR(nstoken, "open_netns"))
+- goto cleanup;
+-
+- test_ns_current_pid_tgid_new_ns(fn, arg);
+-
+-cleanup:
+- if (nstoken)
+- close_netns(nstoken);
+- SYS_NOFAIL("ip netns del ns_current_pid_tgid");
+-}
+-
+ /* TODO: use a different tracepoint */
+ void serial_test_ns_current_pid_tgid(void)
+ {
+@@ -166,13 +102,4 @@ void serial_test_ns_current_pid_tgid(void)
+ test_current_pid_tgid_tp(NULL);
+ if (test__start_subtest("new_ns_tp"))
+ test_ns_current_pid_tgid_new_ns(test_current_pid_tgid_tp, NULL);
+- if (test__start_subtest("new_ns_cgrp")) {
+- int cgroup_fd = -1;
+-
+- cgroup_fd = test__join_cgroup("/sock_addr");
+- if (ASSERT_GE(cgroup_fd, 0, "join_cgroup")) {
+- test_in_netns(test_current_pid_tgid_cgrp, &cgroup_fd);
+- close(cgroup_fd);
+- }
+- }
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_ns_current_pid_tgid.c b/tools/testing/selftests/bpf/progs/test_ns_current_pid_tgid.c
+index d0010e698f6688..aa3ec7ca16d9b6 100644
+--- a/tools/testing/selftests/bpf/progs/test_ns_current_pid_tgid.c
++++ b/tools/testing/selftests/bpf/progs/test_ns_current_pid_tgid.c
+@@ -28,11 +28,4 @@ int tp_handler(const void *ctx)
+ return 0;
+ }
+
+-SEC("?cgroup/bind4")
+-int cgroup_bind4(struct bpf_sock_addr *ctx)
+-{
+- get_pid_tgid();
+- return 1;
+-}
+-
+ char _license[] SEC("license") = "GPL";
+--
+2.53.0
+
--- /dev/null
+From 5cebc9d33436dcf0e359610bba98f970eca386cf Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:40:57 +0200
+Subject: Revert "selftests/bpf: Add tests for _opts variants of
+ bpf_*_get_fd_by_id()"
+
+From: Paul Chaignon <paul.chaignon@gmail.com>
+
+This reverts commit 45108a7b4866 ("selftests/bpf: Add tests for _opts
+variants of bpf_*_get_fd_by_id()"). As explained in the previous patch,
+it introduces a new selftest for a feature that doesn't exist in 6.1. It
+was backported as a stable-dep of a1914d146622 ("selftests/bpf:
+Workaround strict bpf_lsm return value check"), also reverted in the
+previous patch.
+
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/DENYLIST.s390x | 1 -
+ .../bpf/prog_tests/libbpf_get_fd_by_id_opts.c | 87 -------------------
+ .../bpf/progs/test_libbpf_get_fd_by_id_opts.c | 36 --------
+ 3 files changed, 124 deletions(-)
+ delete mode 100644 tools/testing/selftests/bpf/prog_tests/libbpf_get_fd_by_id_opts.c
+ delete mode 100644 tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
+
+diff --git a/tools/testing/selftests/bpf/DENYLIST.s390x b/tools/testing/selftests/bpf/DENYLIST.s390x
+index beef1232a47aeb..0fb03b8047d535 100644
+--- a/tools/testing/selftests/bpf/DENYLIST.s390x
++++ b/tools/testing/selftests/bpf/DENYLIST.s390x
+@@ -76,4 +76,3 @@ lookup_key # JIT does not support calling kernel f
+ verify_pkcs7_sig # JIT does not support calling kernel function (kfunc)
+ kfunc_dynptr_param # JIT does not support calling kernel function (kfunc)
+ deny_namespace # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
+-libbpf_get_fd_by_id_opts # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
+diff --git a/tools/testing/selftests/bpf/prog_tests/libbpf_get_fd_by_id_opts.c b/tools/testing/selftests/bpf/prog_tests/libbpf_get_fd_by_id_opts.c
+deleted file mode 100644
+index 25e5dfa9c315ce..00000000000000
+--- a/tools/testing/selftests/bpf/prog_tests/libbpf_get_fd_by_id_opts.c
++++ /dev/null
+@@ -1,87 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-
+-/*
+- * Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH
+- *
+- * Author: Roberto Sassu <roberto.sassu@huawei.com>
+- */
+-
+-#include <test_progs.h>
+-
+-#include "test_libbpf_get_fd_by_id_opts.skel.h"
+-
+-void test_libbpf_get_fd_by_id_opts(void)
+-{
+- struct test_libbpf_get_fd_by_id_opts *skel;
+- struct bpf_map_info info_m = {};
+- __u32 len = sizeof(info_m), value;
+- int ret, zero = 0, fd = -1;
+- LIBBPF_OPTS(bpf_get_fd_by_id_opts, fd_opts_rdonly,
+- .open_flags = BPF_F_RDONLY,
+- );
+-
+- skel = test_libbpf_get_fd_by_id_opts__open_and_load();
+- if (!ASSERT_OK_PTR(skel,
+- "test_libbpf_get_fd_by_id_opts__open_and_load"))
+- return;
+-
+- ret = test_libbpf_get_fd_by_id_opts__attach(skel);
+- if (!ASSERT_OK(ret, "test_libbpf_get_fd_by_id_opts__attach"))
+- goto close_prog;
+-
+- ret = bpf_obj_get_info_by_fd(bpf_map__fd(skel->maps.data_input),
+- &info_m, &len);
+- if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd"))
+- goto close_prog;
+-
+- fd = bpf_map_get_fd_by_id(info_m.id);
+- if (!ASSERT_LT(fd, 0, "bpf_map_get_fd_by_id"))
+- goto close_prog;
+-
+- fd = bpf_map_get_fd_by_id_opts(info_m.id, NULL);
+- if (!ASSERT_LT(fd, 0, "bpf_map_get_fd_by_id_opts"))
+- goto close_prog;
+-
+- fd = bpf_map_get_fd_by_id_opts(info_m.id, &fd_opts_rdonly);
+- if (!ASSERT_GE(fd, 0, "bpf_map_get_fd_by_id_opts"))
+- goto close_prog;
+-
+- /* Map lookup should work with read-only fd. */
+- ret = bpf_map_lookup_elem(fd, &zero, &value);
+- if (!ASSERT_OK(ret, "bpf_map_lookup_elem"))
+- goto close_prog;
+-
+- if (!ASSERT_EQ(value, 0, "map value mismatch"))
+- goto close_prog;
+-
+- /* Map update should not work with read-only fd. */
+- ret = bpf_map_update_elem(fd, &zero, &len, BPF_ANY);
+- if (!ASSERT_LT(ret, 0, "bpf_map_update_elem"))
+- goto close_prog;
+-
+- /* Map update should work with read-write fd. */
+- ret = bpf_map_update_elem(bpf_map__fd(skel->maps.data_input), &zero,
+- &len, BPF_ANY);
+- if (!ASSERT_OK(ret, "bpf_map_update_elem"))
+- goto close_prog;
+-
+- /* Prog get fd with opts set should not work (no kernel support). */
+- ret = bpf_prog_get_fd_by_id_opts(0, &fd_opts_rdonly);
+- if (!ASSERT_EQ(ret, -EINVAL, "bpf_prog_get_fd_by_id_opts"))
+- goto close_prog;
+-
+- /* Link get fd with opts set should not work (no kernel support). */
+- ret = bpf_link_get_fd_by_id_opts(0, &fd_opts_rdonly);
+- if (!ASSERT_EQ(ret, -EINVAL, "bpf_link_get_fd_by_id_opts"))
+- goto close_prog;
+-
+- /* BTF get fd with opts set should not work (no kernel support). */
+- ret = bpf_btf_get_fd_by_id_opts(0, &fd_opts_rdonly);
+- ASSERT_EQ(ret, -EINVAL, "bpf_btf_get_fd_by_id_opts");
+-
+-close_prog:
+- if (fd >= 0)
+- close(fd);
+-
+- test_libbpf_get_fd_by_id_opts__destroy(skel);
+-}
+diff --git a/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c b/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
+deleted file mode 100644
+index f5ac5f3e89196f..00000000000000
+--- a/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
++++ /dev/null
+@@ -1,36 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-
+-/*
+- * Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH
+- *
+- * Author: Roberto Sassu <roberto.sassu@huawei.com>
+- */
+-
+-#include "vmlinux.h"
+-#include <errno.h>
+-#include <bpf/bpf_helpers.h>
+-#include <bpf/bpf_tracing.h>
+-
+-/* From include/linux/mm.h. */
+-#define FMODE_WRITE 0x2
+-
+-struct {
+- __uint(type, BPF_MAP_TYPE_ARRAY);
+- __uint(max_entries, 1);
+- __type(key, __u32);
+- __type(value, __u32);
+-} data_input SEC(".maps");
+-
+-char _license[] SEC("license") = "GPL";
+-
+-SEC("lsm/bpf_map")
+-int BPF_PROG(check_access, struct bpf_map *map, fmode_t fmode)
+-{
+- if (map != (struct bpf_map *)&data_input)
+- return 0;
+-
+- if (fmode & FMODE_WRITE)
+- return -EACCES;
+-
+- return 0;
+-}
+--
+2.53.0
+
--- /dev/null
+From 7387948319daf8a9508c239821524a836e3cb6bb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:40:48 +0200
+Subject: Revert "selftests/bpf: Workaround strict bpf_lsm return value check."
+
+From: Paul Chaignon <paul.chaignon@gmail.com>
+
+This reverts commit a1914d146622 ("selftests/bpf: Workaround strict
+bpf_lsm return value check"). It seems it was picked up by mistake.
+
+It applies to a selftest that didn't exist in 6.1. The whole selftest
+was then backported as a stable-dep in commit 45108a7b4866
+("selftests/bpf: Add tests for _opts variants of bpf_*_get_fd_by_id()")
+(reverted as well in the next patch).
+
+The new selftest covers the bpf_*_get_fd_by_id structures. Those don't
+exist in 6.1 so the selftest shouldn't either.
+
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ .../testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c b/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
+index 568816307f7125..f5ac5f3e89196f 100644
+--- a/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
++++ b/tools/testing/selftests/bpf/progs/test_libbpf_get_fd_by_id_opts.c
+@@ -31,7 +31,6 @@ int BPF_PROG(check_access, struct bpf_map *map, fmode_t fmode)
+
+ if (fmode & FMODE_WRITE)
+ return -EACCES;
+- barrier();
+
+ return 0;
+ }
+--
+2.53.0
+
--- /dev/null
+From 87e3312c479bf46aa628049f486bb2068cd9b49a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:39:58 +0200
+Subject: selftests/bpf: add generic BPF program tester-loader
+
+From: Andrii Nakryiko <andrii@kernel.org>
+
+[ Upstream commit 537c3f66eac137a02ec50a40219d2da6597e5dc9 ]
+
+It's become a common pattern to have a collection of small BPF programs
+in one BPF object file, each representing one test case. On user-space
+side of such tests we maintain a table of program names and expected
+failure or success, along with optional expected verifier log message.
+
+This works, but each set of tests reimplement this mundane code over and
+over again, which is a waste of time for anyone trying to add a new set
+of tests. Furthermore, it's quite error prone as it's way too easy to miss
+some entries in these manually maintained test tables (as evidences by
+dynptr_fail tests, in which ringbuf_release_uninit_dynptr subtest was
+accidentally missed; this is fixed in next patch).
+
+So this patch implements generic test_loader, which accepts skeleton
+name and handles the rest of details: opens and loads BPF object file,
+making sure each program is tested in isolation. Optionally each test
+case can specify expected BPF verifier log message. In case of failure,
+tester makes sure to report verifier log, but it also reports verifier
+log in verbose mode unconditionally.
+
+Now, the interesting deviation from existing custom implementations is
+the use of btf_decl_tag attribute to specify expected-to-fail vs
+expected-to-succeed markers and, optionally, expected log message
+directly next to BPF program source code, eliminating the need to
+manually create and update table of tests.
+
+We define few macros wrapping btf_decl_tag with a convention that all
+values of btf_decl_tag start with "comment:" prefix, and then utilizing
+a very simple "just_some_text_tag" or "some_key_name=<value>" pattern to
+define things like expected success/failure, expected verifier message,
+extra verifier log level (if necessary). This approach is demonstrated
+by next patch in which two existing sets of failure tests are converted.
+
+Tester supports both expected-to-fail and expected-to-succeed programs,
+though this patch set didn't convert any existing expected-to-succeed
+programs yet, as existing tests couple BPF program loading with their
+further execution through attach or test_prog_run. One way to allow
+testing scenarios like this would be ability to specify custom callback,
+executed for each successfully loaded BPF program. This is left for
+follow up patches, after some more analysis of existing test cases.
+
+This test_loader is, hopefully, a start of a test_verifier-like runner,
+but integrated into test_progs infrastructure. It will allow much better
+"user experience" of defining low-level verification tests that can take
+advantage of all the libbpf-provided nicety features on BPF side: global
+variables, declarative maps, etc. All while having a choice of defining
+it in C or as BPF assembly (through __attribute__((naked)) functions and
+using embedded asm), depending on what makes most sense in each
+particular case. This will be explored in follow up patches as well.
+
+Acked-by: John Fastabend <john.fastabend@gmail.com>
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Link: https://lore.kernel.org/r/20221207201648.2990661-1-andrii@kernel.org
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Stable-dep-of: 95ebb376176c ("selftests/bpf: Convert test_global_funcs test to test_loader framework")
+[ Note: Minor conflict in Makefile. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/Makefile | 2 +-
+ tools/testing/selftests/bpf/progs/bpf_misc.h | 5 +
+ tools/testing/selftests/bpf/test_loader.c | 233 +++++++++++++++++++
+ tools/testing/selftests/bpf/test_progs.h | 33 +++
+ 4 files changed, 272 insertions(+), 1 deletion(-)
+ create mode 100644 tools/testing/selftests/bpf/test_loader.c
+
+diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
+index b09205d925114a..541f251036ae7b 100644
+--- a/tools/testing/selftests/bpf/Makefile
++++ b/tools/testing/selftests/bpf/Makefile
+@@ -519,7 +519,7 @@ TRUNNER_BPF_PROGS_DIR := progs
+ TRUNNER_EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c \
+ network_helpers.c testing_helpers.c \
+ btf_helpers.c flow_dissector_load.h \
+- cap_helpers.c
++ cap_helpers.c test_loader.c
+ TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko \
+ $(OUTPUT)/liburandom_read.so \
+ $(OUTPUT)/xdp_synproxy \
+diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
+index 5bb11fe595a439..4a01ea9113bfd7 100644
+--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
++++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
+@@ -2,6 +2,11 @@
+ #ifndef __BPF_MISC_H__
+ #define __BPF_MISC_H__
+
++#define __msg(msg) __attribute__((btf_decl_tag("comment:test_expect_msg=" msg)))
++#define __failure __attribute__((btf_decl_tag("comment:test_expect_failure")))
++#define __success __attribute__((btf_decl_tag("comment:test_expect_success")))
++#define __log_level(lvl) __attribute__((btf_decl_tag("comment:test_log_level="#lvl)))
++
+ #if defined(__TARGET_ARCH_x86)
+ #define SYSCALL_WRAPPER 1
+ #define SYS_PREFIX "__x64_"
+diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
+new file mode 100644
+index 00000000000000..679efb3aa785e3
+--- /dev/null
++++ b/tools/testing/selftests/bpf/test_loader.c
+@@ -0,0 +1,233 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
++#include <stdlib.h>
++#include <test_progs.h>
++#include <bpf/btf.h>
++
++#define str_has_pfx(str, pfx) \
++ (strncmp(str, pfx, __builtin_constant_p(pfx) ? sizeof(pfx) - 1 : strlen(pfx)) == 0)
++
++#define TEST_LOADER_LOG_BUF_SZ 1048576
++
++#define TEST_TAG_EXPECT_FAILURE "comment:test_expect_failure"
++#define TEST_TAG_EXPECT_SUCCESS "comment:test_expect_success"
++#define TEST_TAG_EXPECT_MSG_PFX "comment:test_expect_msg="
++#define TEST_TAG_LOG_LEVEL_PFX "comment:test_log_level="
++
++struct test_spec {
++ const char *name;
++ bool expect_failure;
++ const char *expect_msg;
++ int log_level;
++};
++
++static int tester_init(struct test_loader *tester)
++{
++ if (!tester->log_buf) {
++ tester->log_buf_sz = TEST_LOADER_LOG_BUF_SZ;
++ tester->log_buf = malloc(tester->log_buf_sz);
++ if (!ASSERT_OK_PTR(tester->log_buf, "tester_log_buf"))
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++void test_loader_fini(struct test_loader *tester)
++{
++ if (!tester)
++ return;
++
++ free(tester->log_buf);
++}
++
++static int parse_test_spec(struct test_loader *tester,
++ struct bpf_object *obj,
++ struct bpf_program *prog,
++ struct test_spec *spec)
++{
++ struct btf *btf;
++ int func_id, i;
++
++ memset(spec, 0, sizeof(*spec));
++
++ spec->name = bpf_program__name(prog);
++
++ btf = bpf_object__btf(obj);
++ if (!btf) {
++ ASSERT_FAIL("BPF object has no BTF");
++ return -EINVAL;
++ }
++
++ func_id = btf__find_by_name_kind(btf, spec->name, BTF_KIND_FUNC);
++ if (func_id < 0) {
++ ASSERT_FAIL("failed to find FUNC BTF type for '%s'", spec->name);
++ return -EINVAL;
++ }
++
++ for (i = 1; i < btf__type_cnt(btf); i++) {
++ const struct btf_type *t;
++ const char *s;
++
++ t = btf__type_by_id(btf, i);
++ if (!btf_is_decl_tag(t))
++ continue;
++
++ if (t->type != func_id || btf_decl_tag(t)->component_idx != -1)
++ continue;
++
++ s = btf__str_by_offset(btf, t->name_off);
++ if (strcmp(s, TEST_TAG_EXPECT_FAILURE) == 0) {
++ spec->expect_failure = true;
++ } else if (strcmp(s, TEST_TAG_EXPECT_SUCCESS) == 0) {
++ spec->expect_failure = false;
++ } else if (str_has_pfx(s, TEST_TAG_EXPECT_MSG_PFX)) {
++ spec->expect_msg = s + sizeof(TEST_TAG_EXPECT_MSG_PFX) - 1;
++ } else if (str_has_pfx(s, TEST_TAG_LOG_LEVEL_PFX)) {
++ errno = 0;
++ spec->log_level = strtol(s + sizeof(TEST_TAG_LOG_LEVEL_PFX) - 1, NULL, 0);
++ if (errno) {
++ ASSERT_FAIL("failed to parse test log level from '%s'", s);
++ return -EINVAL;
++ }
++ }
++ }
++
++ return 0;
++}
++
++static void prepare_case(struct test_loader *tester,
++ struct test_spec *spec,
++ struct bpf_object *obj,
++ struct bpf_program *prog)
++{
++ int min_log_level = 0;
++
++ if (env.verbosity > VERBOSE_NONE)
++ min_log_level = 1;
++ if (env.verbosity > VERBOSE_VERY)
++ min_log_level = 2;
++
++ bpf_program__set_log_buf(prog, tester->log_buf, tester->log_buf_sz);
++
++ /* Make sure we set at least minimal log level, unless test requirest
++ * even higher level already. Make sure to preserve independent log
++ * level 4 (verifier stats), though.
++ */
++ if ((spec->log_level & 3) < min_log_level)
++ bpf_program__set_log_level(prog, (spec->log_level & 4) | min_log_level);
++ else
++ bpf_program__set_log_level(prog, spec->log_level);
++
++ tester->log_buf[0] = '\0';
++}
++
++static void emit_verifier_log(const char *log_buf, bool force)
++{
++ if (!force && env.verbosity == VERBOSE_NONE)
++ return;
++ fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log_buf);
++}
++
++static void validate_case(struct test_loader *tester,
++ struct test_spec *spec,
++ struct bpf_object *obj,
++ struct bpf_program *prog,
++ int load_err)
++{
++ if (spec->expect_msg) {
++ char *match;
++
++ match = strstr(tester->log_buf, spec->expect_msg);
++ if (!ASSERT_OK_PTR(match, "expect_msg")) {
++ /* if we are in verbose mode, we've already emitted log */
++ if (env.verbosity == VERBOSE_NONE)
++ emit_verifier_log(tester->log_buf, true /*force*/);
++ fprintf(stderr, "EXPECTED MSG: '%s'\n", spec->expect_msg);
++ return;
++ }
++ }
++}
++
++/* this function is forced noinline and has short generic name to look better
++ * in test_progs output (in case of a failure)
++ */
++static noinline
++void run_subtest(struct test_loader *tester,
++ const char *skel_name,
++ skel_elf_bytes_fn elf_bytes_factory)
++{
++ LIBBPF_OPTS(bpf_object_open_opts, open_opts, .object_name = skel_name);
++ struct bpf_object *obj = NULL, *tobj;
++ struct bpf_program *prog, *tprog;
++ const void *obj_bytes;
++ size_t obj_byte_cnt;
++ int err;
++
++ if (tester_init(tester) < 0)
++ return; /* failed to initialize tester */
++
++ obj_bytes = elf_bytes_factory(&obj_byte_cnt);
++ obj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, &open_opts);
++ if (!ASSERT_OK_PTR(obj, "obj_open_mem"))
++ return;
++
++ bpf_object__for_each_program(prog, obj) {
++ const char *prog_name = bpf_program__name(prog);
++ struct test_spec spec;
++
++ if (!test__start_subtest(prog_name))
++ continue;
++
++ /* if we can't derive test specification, go to the next test */
++ err = parse_test_spec(tester, obj, prog, &spec);
++ if (!ASSERT_OK(err, "parse_test_spec"))
++ continue;
++
++ tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, &open_opts);
++ if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
++ continue;
++
++ bpf_object__for_each_program(tprog, tobj)
++ bpf_program__set_autoload(tprog, false);
++
++ bpf_object__for_each_program(tprog, tobj) {
++ /* only load specified program */
++ if (strcmp(bpf_program__name(tprog), prog_name) == 0) {
++ bpf_program__set_autoload(tprog, true);
++ break;
++ }
++ }
++
++ prepare_case(tester, &spec, tobj, tprog);
++
++ err = bpf_object__load(tobj);
++ if (spec.expect_failure) {
++ if (!ASSERT_ERR(err, "unexpected_load_success")) {
++ emit_verifier_log(tester->log_buf, false /*force*/);
++ goto tobj_cleanup;
++ }
++ } else {
++ if (!ASSERT_OK(err, "unexpected_load_failure")) {
++ emit_verifier_log(tester->log_buf, true /*force*/);
++ goto tobj_cleanup;
++ }
++ }
++
++ emit_verifier_log(tester->log_buf, false /*force*/);
++ validate_case(tester, &spec, tobj, tprog, err);
++
++tobj_cleanup:
++ bpf_object__close(tobj);
++ }
++
++ bpf_object__close(obj);
++}
++
++void test_loader__run_subtests(struct test_loader *tester,
++ const char *skel_name,
++ skel_elf_bytes_fn elf_bytes_factory)
++{
++ /* see comment in run_subtest() for why we do this function nesting */
++ run_subtest(tester, skel_name, elf_bytes_factory);
++}
+diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
+index feb14f14006d98..ff1caffefa5256 100644
+--- a/tools/testing/selftests/bpf/test_progs.h
++++ b/tools/testing/selftests/bpf/test_progs.h
+@@ -1,4 +1,7 @@
+ /* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __TEST_PROGS_H
++#define __TEST_PROGS_H
++
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <errno.h>
+@@ -210,6 +213,12 @@ int test__join_cgroup(const char *path);
+ #define CHECK_ATTR(condition, tag, format...) \
+ _CHECK(condition, tag, tattr.duration, format)
+
++#define ASSERT_FAIL(fmt, args...) ({ \
++ static int duration = 0; \
++ CHECK(false, "", fmt"\n", ##args); \
++ false; \
++})
++
+ #define ASSERT_TRUE(actual, name) ({ \
+ static int duration = 0; \
+ bool ___ok = (actual); \
+@@ -395,3 +404,27 @@ int write_sysctl(const char *sysctl, const char *value);
+ #endif
+
+ #define BPF_TESTMOD_TEST_FILE "/sys/kernel/bpf_testmod"
++
++struct test_loader {
++ char *log_buf;
++ size_t log_buf_sz;
++
++ struct bpf_object *obj;
++};
++
++typedef const void *(*skel_elf_bytes_fn)(size_t *sz);
++
++extern void test_loader__run_subtests(struct test_loader *tester,
++ const char *skel_name,
++ skel_elf_bytes_fn elf_bytes_factory);
++
++extern void test_loader_fini(struct test_loader *tester);
++
++#define RUN_TESTS(skel) ({ \
++ struct test_loader tester = {}; \
++ \
++ test_loader__run_subtests(&tester, #skel, skel##__elf_bytes); \
++ test_loader_fini(&tester); \
++})
++
++#endif /* __TEST_PROGS_H */
+--
+2.53.0
+
--- /dev/null
+From 8f6682bf60d248de4e5130bc840b9b3828b172ac Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:07 +0200
+Subject: selftests/bpf: Add read_build_id function
+
+From: Jiri Olsa <jolsa@kernel.org>
+
+[ Upstream commit 88dc8b3605b38a440fba45edcc53a6c7a98eee3b ]
+
+Adding read_build_id function that parses out build id from
+specified binary.
+
+It will replace extract_build_id and also be used in following
+changes.
+
+Signed-off-by: Jiri Olsa <jolsa@kernel.org>
+Link: https://lore.kernel.org/r/20230331093157.1749137-3-jolsa@kernel.org
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Fixes: be4e85369e5a ("selftests/bpf: Replace extract_build_id with read_build_id")
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/trace_helpers.c | 82 +++++++++++++++++++++
+ tools/testing/selftests/bpf/trace_helpers.h | 5 ++
+ 2 files changed, 87 insertions(+)
+
+diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
+index 9c4be2cdb21a02..afc33ba36ccc6e 100644
+--- a/tools/testing/selftests/bpf/trace_helpers.c
++++ b/tools/testing/selftests/bpf/trace_helpers.c
+@@ -11,6 +11,9 @@
+ #include <linux/perf_event.h>
+ #include <sys/mman.h>
+ #include "trace_helpers.h"
++#include <linux/limits.h>
++#include <libelf.h>
++#include <gelf.h>
+
+ #define DEBUGFS "/sys/kernel/debug/tracing/"
+
+@@ -224,3 +227,82 @@ ssize_t get_rel_offset(uintptr_t addr)
+ fclose(f);
+ return -EINVAL;
+ }
++
++static int
++parse_build_id_buf(const void *note_start, Elf32_Word note_size, char *build_id)
++{
++ Elf32_Word note_offs = 0;
++
++ while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
++ Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
++
++ if (nhdr->n_type == 3 && nhdr->n_namesz == sizeof("GNU") &&
++ !strcmp((char *)(nhdr + 1), "GNU") && nhdr->n_descsz > 0 &&
++ nhdr->n_descsz <= BPF_BUILD_ID_SIZE) {
++ memcpy(build_id, note_start + note_offs +
++ ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr), nhdr->n_descsz);
++ memset(build_id + nhdr->n_descsz, 0, BPF_BUILD_ID_SIZE - nhdr->n_descsz);
++ return (int) nhdr->n_descsz;
++ }
++
++ note_offs = note_offs + sizeof(Elf32_Nhdr) +
++ ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
++ }
++
++ return -ENOENT;
++}
++
++/* Reads binary from *path* file and returns it in the *build_id* buffer
++ * with *size* which is expected to be at least BPF_BUILD_ID_SIZE bytes.
++ * Returns size of build id on success. On error the error value is
++ * returned.
++ */
++int read_build_id(const char *path, char *build_id, size_t size)
++{
++ int fd, err = -EINVAL;
++ Elf *elf = NULL;
++ GElf_Ehdr ehdr;
++ size_t max, i;
++
++ if (size < BPF_BUILD_ID_SIZE)
++ return -EINVAL;
++
++ fd = open(path, O_RDONLY | O_CLOEXEC);
++ if (fd < 0)
++ return -errno;
++
++ (void)elf_version(EV_CURRENT);
++
++ elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
++ if (!elf)
++ goto out;
++ if (elf_kind(elf) != ELF_K_ELF)
++ goto out;
++ if (!gelf_getehdr(elf, &ehdr))
++ goto out;
++
++ for (i = 0; i < ehdr.e_phnum; i++) {
++ GElf_Phdr mem, *phdr;
++ char *data;
++
++ phdr = gelf_getphdr(elf, i, &mem);
++ if (!phdr)
++ goto out;
++ if (phdr->p_type != PT_NOTE)
++ continue;
++ data = elf_rawfile(elf, &max);
++ if (!data)
++ goto out;
++ if (phdr->p_offset + phdr->p_memsz > max)
++ goto out;
++ err = parse_build_id_buf(data + phdr->p_offset, phdr->p_memsz, build_id);
++ if (err > 0)
++ break;
++ }
++
++out:
++ if (elf)
++ elf_end(elf);
++ close(fd);
++ return err;
++}
+diff --git a/tools/testing/selftests/bpf/trace_helpers.h b/tools/testing/selftests/bpf/trace_helpers.h
+index 238a9c98cde27f..709871f3285256 100644
+--- a/tools/testing/selftests/bpf/trace_helpers.h
++++ b/tools/testing/selftests/bpf/trace_helpers.h
+@@ -4,6 +4,9 @@
+
+ #include <bpf/libbpf.h>
+
++#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
++#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
++
+ struct ksym {
+ long addr;
+ char *name;
+@@ -21,4 +24,6 @@ void read_trace_pipe(void);
+ ssize_t get_uprobe_offset(const void *addr);
+ ssize_t get_rel_offset(uintptr_t addr);
+
++int read_build_id(const char *path, char *build_id, size_t size);
++
+ #endif
+--
+2.53.0
+
--- /dev/null
+From e18371de4fcaddfe95085750932ac7de86e02fe4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:40:40 +0200
+Subject: selftests/bpf: Convert test_global_funcs test to test_loader
+ framework
+
+From: Andrii Nakryiko <andrii@kernel.org>
+
+[ Upstream commit 95ebb376176c52382293e05e63f142114a5e40ef ]
+
+Convert 17 test_global_funcs subtests into test_loader framework for
+easier maintenance and more declarative way to define expected
+failures/successes.
+
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Stanislav Fomichev <sdf@google.com>
+Link: https://lore.kernel.org/bpf/20230216045954.3002473-3-andrii@kernel.org
+Fixes: bbac91d57ac2 ("bpf: Allow reads from uninit stack")
+[ Notes: This backport fixes backport commit bbac91d57ac2 ("bpf: Allow
+ reads from uninit stack"), which broke the BPF selftest build. A minor
+ conflict needed resolution in test_global_func10.c on the error
+ message. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ .../bpf/prog_tests/test_global_funcs.c | 131 +++++-------------
+ .../selftests/bpf/progs/test_global_func1.c | 6 +-
+ .../selftests/bpf/progs/test_global_func10.c | 1 +
+ .../selftests/bpf/progs/test_global_func11.c | 4 +-
+ .../selftests/bpf/progs/test_global_func12.c | 4 +-
+ .../selftests/bpf/progs/test_global_func13.c | 4 +-
+ .../selftests/bpf/progs/test_global_func14.c | 4 +-
+ .../selftests/bpf/progs/test_global_func15.c | 4 +-
+ .../selftests/bpf/progs/test_global_func16.c | 4 +-
+ .../selftests/bpf/progs/test_global_func17.c | 4 +-
+ .../selftests/bpf/progs/test_global_func2.c | 43 +++++-
+ .../selftests/bpf/progs/test_global_func3.c | 10 +-
+ .../selftests/bpf/progs/test_global_func4.c | 55 +++++++-
+ .../selftests/bpf/progs/test_global_func5.c | 4 +-
+ .../selftests/bpf/progs/test_global_func6.c | 4 +-
+ .../selftests/bpf/progs/test_global_func7.c | 4 +-
+ .../selftests/bpf/progs/test_global_func8.c | 4 +-
+ .../selftests/bpf/progs/test_global_func9.c | 4 +-
+ 18 files changed, 172 insertions(+), 122 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/prog_tests/test_global_funcs.c b/tools/testing/selftests/bpf/prog_tests/test_global_funcs.c
+index 7295cc60f72487..2ff4d5c7abfceb 100644
+--- a/tools/testing/selftests/bpf/prog_tests/test_global_funcs.c
++++ b/tools/testing/selftests/bpf/prog_tests/test_global_funcs.c
+@@ -1,104 +1,41 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /* Copyright (c) 2020 Facebook */
+ #include <test_progs.h>
+-
+-const char *err_str;
+-bool found;
+-
+-static int libbpf_debug_print(enum libbpf_print_level level,
+- const char *format, va_list args)
+-{
+- char *log_buf;
+-
+- if (level != LIBBPF_WARN ||
+- strcmp(format, "libbpf: \n%s\n")) {
+- vprintf(format, args);
+- return 0;
+- }
+-
+- log_buf = va_arg(args, char *);
+- if (!log_buf)
+- goto out;
+- if (err_str && strstr(log_buf, err_str) == 0)
+- found = true;
+-out:
+- printf(format, log_buf);
+- return 0;
+-}
+-
+-extern int extra_prog_load_log_flags;
+-
+-static int check_load(const char *file)
+-{
+- struct bpf_object *obj = NULL;
+- struct bpf_program *prog;
+- int err;
+-
+- found = false;
+-
+- obj = bpf_object__open_file(file, NULL);
+- err = libbpf_get_error(obj);
+- if (err)
+- return err;
+-
+- prog = bpf_object__next_program(obj, NULL);
+- if (!prog) {
+- err = -ENOENT;
+- goto err_out;
+- }
+-
+- bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+- bpf_program__set_log_level(prog, extra_prog_load_log_flags);
+-
+- err = bpf_object__load(obj);
+-
+-err_out:
+- bpf_object__close(obj);
+- return err;
+-}
+-
+-struct test_def {
+- const char *file;
+- const char *err_str;
+-};
++#include "test_global_func1.skel.h"
++#include "test_global_func2.skel.h"
++#include "test_global_func3.skel.h"
++#include "test_global_func4.skel.h"
++#include "test_global_func5.skel.h"
++#include "test_global_func6.skel.h"
++#include "test_global_func7.skel.h"
++#include "test_global_func8.skel.h"
++#include "test_global_func9.skel.h"
++#include "test_global_func10.skel.h"
++#include "test_global_func11.skel.h"
++#include "test_global_func12.skel.h"
++#include "test_global_func13.skel.h"
++#include "test_global_func14.skel.h"
++#include "test_global_func15.skel.h"
++#include "test_global_func16.skel.h"
++#include "test_global_func17.skel.h"
+
+ void test_test_global_funcs(void)
+ {
+- struct test_def tests[] = {
+- { "test_global_func1.bpf.o", "combined stack size of 4 calls is 544" },
+- { "test_global_func2.bpf.o" },
+- { "test_global_func3.bpf.o", "the call stack of 8 frames" },
+- { "test_global_func4.bpf.o" },
+- { "test_global_func5.bpf.o", "expected pointer to ctx, but got PTR" },
+- { "test_global_func6.bpf.o", "modified ctx ptr R2" },
+- { "test_global_func7.bpf.o", "foo() doesn't return scalar" },
+- { "test_global_func8.bpf.o" },
+- { "test_global_func9.bpf.o" },
+- { "test_global_func10.bpf.o", "invalid indirect read from stack" },
+- { "test_global_func11.bpf.o", "Caller passes invalid args into func#1" },
+- { "test_global_func12.bpf.o", "invalid mem access 'mem_or_null'" },
+- { "test_global_func13.bpf.o", "Caller passes invalid args into func#1" },
+- { "test_global_func14.bpf.o", "reference type('FWD S') size cannot be determined" },
+- { "test_global_func15.bpf.o", "At program exit the register R0 has value" },
+- { "test_global_func16.bpf.o", "invalid indirect read from stack" },
+- { "test_global_func17.bpf.o", "Caller passes invalid args into func#1" },
+- };
+- libbpf_print_fn_t old_print_fn = NULL;
+- int err, i, duration = 0;
+-
+- old_print_fn = libbpf_set_print(libbpf_debug_print);
+-
+- for (i = 0; i < ARRAY_SIZE(tests); i++) {
+- const struct test_def *test = &tests[i];
+-
+- if (!test__start_subtest(test->file))
+- continue;
+-
+- err_str = test->err_str;
+- err = check_load(test->file);
+- CHECK_FAIL(!!err ^ !!err_str);
+- if (err_str)
+- CHECK(found, "", "expected string '%s'", err_str);
+- }
+- libbpf_set_print(old_print_fn);
++ RUN_TESTS(test_global_func1);
++ RUN_TESTS(test_global_func2);
++ RUN_TESTS(test_global_func3);
++ RUN_TESTS(test_global_func4);
++ RUN_TESTS(test_global_func5);
++ RUN_TESTS(test_global_func6);
++ RUN_TESTS(test_global_func7);
++ RUN_TESTS(test_global_func8);
++ RUN_TESTS(test_global_func9);
++ RUN_TESTS(test_global_func10);
++ RUN_TESTS(test_global_func11);
++ RUN_TESTS(test_global_func12);
++ RUN_TESTS(test_global_func13);
++ RUN_TESTS(test_global_func14);
++ RUN_TESTS(test_global_func15);
++ RUN_TESTS(test_global_func16);
++ RUN_TESTS(test_global_func17);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func1.c b/tools/testing/selftests/bpf/progs/test_global_func1.c
+index 7b42dad187b894..23970a20b3249d 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func1.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func1.c
+@@ -3,10 +3,9 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+-#ifndef MAX_STACK
+ #define MAX_STACK (512 - 3 * 32 + 8)
+-#endif
+
+ static __attribute__ ((noinline))
+ int f0(int var, struct __sk_buff *skb)
+@@ -39,7 +38,8 @@ int f3(int val, struct __sk_buff *skb, int var)
+ }
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("combined stack size of 4 calls is 544")
++int global_func1(struct __sk_buff *skb)
+ {
+ return f0(1, skb) + f1(skb) + f2(2, skb) + f3(3, skb, 4);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func10.c b/tools/testing/selftests/bpf/progs/test_global_func10.c
+index d361eba167f6a4..8fba3f3649e227 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func10.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func10.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct Small {
+ long x;
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func11.c b/tools/testing/selftests/bpf/progs/test_global_func11.c
+index ef5277d982d921..283e036dc401e9 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func11.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func11.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct S {
+ int x;
+@@ -13,7 +14,8 @@ __noinline int foo(const struct S *s)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("Caller passes invalid args into func#1")
++int global_func11(struct __sk_buff *skb)
+ {
+ return foo((const void *)skb);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func12.c b/tools/testing/selftests/bpf/progs/test_global_func12.c
+index 62343527cc598d..7f159d83c6f673 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func12.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func12.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct S {
+ int x;
+@@ -13,7 +14,8 @@ __noinline int foo(const struct S *s)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("invalid mem access 'mem_or_null'")
++int global_func12(struct __sk_buff *skb)
+ {
+ const struct S s = {.x = skb->len };
+
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func13.c b/tools/testing/selftests/bpf/progs/test_global_func13.c
+index ff8897c1ac22b4..02ea80da75b573 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func13.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func13.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct S {
+ int x;
+@@ -16,7 +17,8 @@ __noinline int foo(const struct S *s)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("Caller passes invalid args into func#1")
++int global_func13(struct __sk_buff *skb)
+ {
+ const struct S *s = (const struct S *)(0xbedabeda);
+
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func14.c b/tools/testing/selftests/bpf/progs/test_global_func14.c
+index 698c77199ebf7d..33b7d5efd7b262 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func14.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func14.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct S;
+
+@@ -14,7 +15,8 @@ __noinline int foo(const struct S *s)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("reference type('FWD S') size cannot be determined")
++int global_func14(struct __sk_buff *skb)
+ {
+
+ return foo(NULL);
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func15.c b/tools/testing/selftests/bpf/progs/test_global_func15.c
+index c19c435988d557..b512d6a6c75e54 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func15.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func15.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __noinline int foo(unsigned int *v)
+ {
+@@ -12,7 +13,8 @@ __noinline int foo(unsigned int *v)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("At program exit the register R0 has value")
++int global_func15(struct __sk_buff *skb)
+ {
+ unsigned int v = 1;
+
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func16.c b/tools/testing/selftests/bpf/progs/test_global_func16.c
+index 0312d1e8d8c060..e7206304632e15 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func16.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func16.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __noinline int foo(int (*arr)[10])
+ {
+@@ -12,7 +13,8 @@ __noinline int foo(int (*arr)[10])
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("invalid indirect read from stack")
++int global_func16(struct __sk_buff *skb)
+ {
+ int array[10];
+
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func17.c b/tools/testing/selftests/bpf/progs/test_global_func17.c
+index 2b8b9b8ba0183c..a32e11c7d933ee 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func17.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func17.c
+@@ -1,6 +1,7 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ #include <vmlinux.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __noinline int foo(int *p)
+ {
+@@ -10,7 +11,8 @@ __noinline int foo(int *p)
+ const volatile int i;
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("Caller passes invalid args into func#1")
++int global_func17(struct __sk_buff *skb)
+ {
+ return foo((int *)&i);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func2.c b/tools/testing/selftests/bpf/progs/test_global_func2.c
+index 2c18d82923a2d5..3dce97fb52a4b7 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func2.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func2.c
+@@ -1,4 +1,45 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ /* Copyright (c) 2020 Facebook */
++#include <stddef.h>
++#include <linux/bpf.h>
++#include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
++
+ #define MAX_STACK (512 - 3 * 32)
+-#include "test_global_func1.c"
++
++static __attribute__ ((noinline))
++int f0(int var, struct __sk_buff *skb)
++{
++ return skb->len;
++}
++
++__attribute__ ((noinline))
++int f1(struct __sk_buff *skb)
++{
++ volatile char buf[MAX_STACK] = {};
++
++ return f0(0, skb) + skb->len;
++}
++
++int f3(int, struct __sk_buff *skb, int);
++
++__attribute__ ((noinline))
++int f2(int val, struct __sk_buff *skb)
++{
++ return f1(skb) + f3(val, skb, 1);
++}
++
++__attribute__ ((noinline))
++int f3(int val, struct __sk_buff *skb, int var)
++{
++ volatile char buf[MAX_STACK] = {};
++
++ return skb->ifindex * val * var;
++}
++
++SEC("tc")
++__success
++int global_func2(struct __sk_buff *skb)
++{
++ return f0(1, skb) + f1(skb) + f2(2, skb) + f3(3, skb, 4);
++}
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func3.c b/tools/testing/selftests/bpf/progs/test_global_func3.c
+index 01bf8275dfd640..142b682d3c2f08 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func3.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func3.c
+@@ -3,6 +3,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __attribute__ ((noinline))
+ int f1(struct __sk_buff *skb)
+@@ -46,20 +47,15 @@ int f7(struct __sk_buff *skb)
+ return f6(skb);
+ }
+
+-#ifndef NO_FN8
+ __attribute__ ((noinline))
+ int f8(struct __sk_buff *skb)
+ {
+ return f7(skb);
+ }
+-#endif
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("the call stack of 8 frames")
++int global_func3(struct __sk_buff *skb)
+ {
+-#ifndef NO_FN8
+ return f8(skb);
+-#else
+- return f7(skb);
+-#endif
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func4.c b/tools/testing/selftests/bpf/progs/test_global_func4.c
+index 610f75edf27643..1733d87ad3f3ea 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func4.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func4.c
+@@ -1,4 +1,55 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ /* Copyright (c) 2020 Facebook */
+-#define NO_FN8
+-#include "test_global_func3.c"
++#include <stddef.h>
++#include <linux/bpf.h>
++#include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
++
++__attribute__ ((noinline))
++int f1(struct __sk_buff *skb)
++{
++ return skb->len;
++}
++
++__attribute__ ((noinline))
++int f2(int val, struct __sk_buff *skb)
++{
++ return f1(skb) + val;
++}
++
++__attribute__ ((noinline))
++int f3(int val, struct __sk_buff *skb, int var)
++{
++ return f2(var, skb) + val;
++}
++
++__attribute__ ((noinline))
++int f4(struct __sk_buff *skb)
++{
++ return f3(1, skb, 2);
++}
++
++__attribute__ ((noinline))
++int f5(struct __sk_buff *skb)
++{
++ return f4(skb);
++}
++
++__attribute__ ((noinline))
++int f6(struct __sk_buff *skb)
++{
++ return f5(skb);
++}
++
++__attribute__ ((noinline))
++int f7(struct __sk_buff *skb)
++{
++ return f6(skb);
++}
++
++SEC("tc")
++__success
++int global_func4(struct __sk_buff *skb)
++{
++ return f7(skb);
++}
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func5.c b/tools/testing/selftests/bpf/progs/test_global_func5.c
+index 9248d03e0d06fb..cc55aedaf82d57 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func5.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func5.c
+@@ -3,6 +3,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __attribute__ ((noinline))
+ int f1(struct __sk_buff *skb)
+@@ -25,7 +26,8 @@ int f3(int val, struct __sk_buff *skb)
+ }
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("expected pointer to ctx, but got PTR")
++int global_func5(struct __sk_buff *skb)
+ {
+ return f1(skb) + f2(2, skb) + f3(3, skb);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func6.c b/tools/testing/selftests/bpf/progs/test_global_func6.c
+index af8c78bdfb2575..46c38c8f2cf032 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func6.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func6.c
+@@ -3,6 +3,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __attribute__ ((noinline))
+ int f1(struct __sk_buff *skb)
+@@ -25,7 +26,8 @@ int f3(int val, struct __sk_buff *skb)
+ }
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("modified ctx ptr R2")
++int global_func6(struct __sk_buff *skb)
+ {
+ return f1(skb) + f2(2, skb) + f3(3, skb);
+ }
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func7.c b/tools/testing/selftests/bpf/progs/test_global_func7.c
+index 6cb8e2f5254cf1..f182febfde3c08 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func7.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func7.c
+@@ -3,6 +3,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __attribute__ ((noinline))
+ void foo(struct __sk_buff *skb)
+@@ -11,7 +12,8 @@ void foo(struct __sk_buff *skb)
+ }
+
+ SEC("tc")
+-int test_cls(struct __sk_buff *skb)
++__failure __msg("foo() doesn't return scalar")
++int global_func7(struct __sk_buff *skb)
+ {
+ foo(skb);
+ return 0;
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func8.c b/tools/testing/selftests/bpf/progs/test_global_func8.c
+index d55a6544b1abd2..9b9c57fa2dd34b 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func8.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func8.c
+@@ -3,6 +3,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ __noinline int foo(struct __sk_buff *skb)
+ {
+@@ -10,7 +11,8 @@ __noinline int foo(struct __sk_buff *skb)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__success
++int global_func8(struct __sk_buff *skb)
+ {
+ if (!foo(skb))
+ return 0;
+diff --git a/tools/testing/selftests/bpf/progs/test_global_func9.c b/tools/testing/selftests/bpf/progs/test_global_func9.c
+index bd233ddede98ae..1f2cb0159b8d83 100644
+--- a/tools/testing/selftests/bpf/progs/test_global_func9.c
++++ b/tools/testing/selftests/bpf/progs/test_global_func9.c
+@@ -2,6 +2,7 @@
+ #include <stddef.h>
+ #include <linux/bpf.h>
+ #include <bpf/bpf_helpers.h>
++#include "bpf_misc.h"
+
+ struct S {
+ int x;
+@@ -74,7 +75,8 @@ __noinline int quuz(int **p)
+ }
+
+ SEC("cgroup_skb/ingress")
+-int test_cls(struct __sk_buff *skb)
++__success
++int global_func9(struct __sk_buff *skb)
+ {
+ int result = 0;
+
+--
+2.53.0
+
--- /dev/null
+From 600bb6b05ff2caef1797b6532ff200c786083f65 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:34 +0200
+Subject: selftests/bpf: enhance align selftest's expected log matching
+
+From: Andrii Nakryiko <andrii@kernel.org>
+
+[ Upstream commit 6f876e75d316a75957f3d43c3a8c2a6fe9bc18b2 ]
+
+Allow to search for expected register state in all the verifier log
+output that's related to specified instruction number.
+
+See added comment for an example of possible situation that is happening
+due to a simple enhancement done in the next patch, which fixes handling
+of env->test_state_freq flag in state checkpointing logic.
+
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Link: https://lore.kernel.org/r/20230302235015.2044271-4-andrii@kernel.org
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+[ Note: Backport needed to fix the align selftest where some of the
+ expected log messages can't be found. This is happening because
+ commit 1a8a315f008a ("bpf: Ensure proper register state printing for
+ cond jumps") was also backported to 6.1. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/prog_tests/align.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/prog_tests/align.c b/tools/testing/selftests/bpf/prog_tests/align.c
+index 8baebb41541dc7..b9277059256300 100644
+--- a/tools/testing/selftests/bpf/prog_tests/align.c
++++ b/tools/testing/selftests/bpf/prog_tests/align.c
+@@ -660,16 +660,22 @@ static int do_test_single(struct bpf_align_test *test)
+ * func#0 @0
+ * 0: R1=ctx(off=0,imm=0) R10=fp0
+ * 0: (b7) r3 = 2 ; R3_w=2
++ *
++ * Sometimes it's actually two lines below, e.g. when
++ * searching for "6: R3_w=scalar(umax=255,var_off=(0x0; 0xff))":
++ * from 4 to 6: R0_w=pkt(off=8,r=8,imm=0) R1=ctx(off=0,imm=0) R2_w=pkt(off=0,r=8,imm=0) R3_w=pkt_end(off=0,imm=0) R10=fp0
++ * 6: R0_w=pkt(off=8,r=8,imm=0) R1=ctx(off=0,imm=0) R2_w=pkt(off=0,r=8,imm=0) R3_w=pkt_end(off=0,imm=0) R10=fp0
++ * 6: (71) r3 = *(u8 *)(r2 +0) ; R2_w=pkt(off=0,r=8,imm=0) R3_w=scalar(umax=255,var_off=(0x0; 0xff))
+ */
+- if (!strstr(line_ptr, m.match)) {
++ while (!strstr(line_ptr, m.match)) {
+ cur_line = -1;
+ line_ptr = strtok(NULL, "\n");
+- sscanf(line_ptr, "%u: ", &cur_line);
++ sscanf(line_ptr ?: "", "%u: ", &cur_line);
++ if (!line_ptr || cur_line != m.line)
++ break;
+ }
+- if (cur_line != m.line || !line_ptr ||
+- !strstr(line_ptr, m.match)) {
+- printf("Failed to find match %u: %s\n",
+- m.line, m.match);
++ if (cur_line != m.line || !line_ptr || !strstr(line_ptr, m.match)) {
++ printf("Failed to find match %u: %s\n", m.line, m.match);
+ ret = 1;
+ printf("%s", bpf_vlog);
+ break;
+--
+2.53.0
+
--- /dev/null
+From 0d9262b00e2d1f3dc00e7e9252603450556be798 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:55 +0200
+Subject: selftests/bpf: Fix ARG_PTR_TO_LONG {half-,}uninitialized test
+
+From: Daniel Borkmann <daniel@iogearbox.net>
+
+[ Upstream commit b8e188f023e07a733b47d5865311ade51878fe40 ]
+
+The assumption of 'in privileged mode reads from uninitialized stack locations
+are permitted' is not quite correct since the verifier was probing for read
+access rather than write access. Both tests need to be annotated as __success
+for privileged and unprivileged.
+
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Andrii Nakryiko <andrii@kernel.org>
+Link: https://lore.kernel.org/r/20240913191754.13290-6-daniel@iogearbox.net
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+[ Note: The format of logs completely changed since 6.1 so this change
+ had to be reapplied to the old test file. This commit needs to be
+ backported because it fixes a test broken by commit 32556ce93bc4
+ ("bpf: Fix helper writes to read-only maps") from the same patchset. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/verifier/int_ptr.c | 6 +-----
+ 1 file changed, 1 insertion(+), 5 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/verifier/int_ptr.c b/tools/testing/selftests/bpf/verifier/int_ptr.c
+index 02d9e004260b33..8c74cff2090364 100644
+--- a/tools/testing/selftests/bpf/verifier/int_ptr.c
++++ b/tools/testing/selftests/bpf/verifier/int_ptr.c
+@@ -25,9 +25,8 @@
+ BPF_MOV64_IMM(BPF_REG_0, 1),
+ BPF_EXIT_INSN(),
+ },
+- .result = REJECT,
++ .result = ACCEPT,
+ .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL,
+- .errstr = "invalid indirect read from stack R4 off -16+0 size 8",
+ },
+ {
+ "ARG_PTR_TO_LONG half-uninitialized",
+@@ -57,9 +56,6 @@
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ },
+- .result_unpriv = REJECT,
+- .errstr_unpriv = "invalid indirect read from stack R4 off -16+4 size 8",
+- /* in privileged mode reads from uninitialized stack locations are permitted */
+ .result = ACCEPT,
+ },
+ {
+--
+2.53.0
+
--- /dev/null
+From 16be54df6956e1b0372b80ae057e7b3675dc72a3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:42:04 +0200
+Subject: selftests/bpf: S/iptables/iptables-legacy/ in the bpf_nf and
+ xdp_synproxy test
+
+From: Martin KaFai Lau <martin.lau@kernel.org>
+
+[ Upstream commit de9c8d848d90cf2e53aced50b350827442ca5a4f ]
+
+The recent vm image in CI has reported error in selftests that use
+the iptables command. Manu Bretelle has pointed out the difference
+in the recent vm image that the iptables is sym-linked to the iptables-nft.
+With this knowledge, I can also reproduce the CI error by manually running
+with the 'iptables-nft'.
+
+This patch is to replace the iptables command with iptables-legacy
+to unblock the CI tests.
+
+Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Acked-by: David Vernet <void@manifault.com>
+Link: https://lore.kernel.org/bpf/20221012221235.3529719-1-martin.lau@linux.dev
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/prog_tests/bpf_nf.c | 6 +++---
+ tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
+index b2998896f9f7bc..b30ff6b3b81ae3 100644
+--- a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
++++ b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
+@@ -49,14 +49,14 @@ static int connect_to_server(int srv_fd)
+
+ static void test_bpf_nf_ct(int mode)
+ {
+- const char *iptables = "iptables -t raw %s PREROUTING -j CONNMARK --set-mark 42/0";
++ const char *iptables = "iptables-legacy -t raw %s PREROUTING -j CONNMARK --set-mark 42/0";
+ int srv_fd = -1, client_fd = -1, srv_client_fd = -1;
+ struct sockaddr_in peer_addr = {};
+ struct test_bpf_nf *skel;
+ int prog_fd, err;
+ socklen_t len;
+ u16 srv_port;
+- char cmd[64];
++ char cmd[128];
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .data_in = &pkt_v4,
+ .data_size_in = sizeof(pkt_v4),
+@@ -69,7 +69,7 @@ static void test_bpf_nf_ct(int mode)
+
+ /* Enable connection tracking */
+ snprintf(cmd, sizeof(cmd), iptables, "-A");
+- if (!ASSERT_OK(system(cmd), "iptables"))
++ if (!ASSERT_OK(system(cmd), cmd))
+ goto end;
+
+ srv_port = (mode == TEST_XDP) ? 5005 : 5006;
+diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c b/tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c
+index 879f5da2f21e63..13daa3746064af 100644
+--- a/tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c
++++ b/tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c
+@@ -94,12 +94,12 @@ static void test_synproxy(bool xdp)
+ SYS("sysctl -w net.ipv4.tcp_syncookies=2");
+ SYS("sysctl -w net.ipv4.tcp_timestamps=1");
+ SYS("sysctl -w net.netfilter.nf_conntrack_tcp_loose=0");
+- SYS("iptables -t raw -I PREROUTING \
++ SYS("iptables-legacy -t raw -I PREROUTING \
+ -i tmp1 -p tcp -m tcp --syn --dport 8080 -j CT --notrack");
+- SYS("iptables -t filter -A INPUT \
++ SYS("iptables-legacy -t filter -A INPUT \
+ -i tmp1 -p tcp -m tcp --dport 8080 -m state --state INVALID,UNTRACKED \
+ -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460");
+- SYS("iptables -t filter -A INPUT \
++ SYS("iptables-legacy -t filter -A INPUT \
+ -i tmp1 -m state --state INVALID -j DROP");
+
+ ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --ports 8080 \
+--
+2.53.0
+
--- /dev/null
+From 8d982a71260603aa8c9bc5df1238aa3add555921 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 21:41:25 +0200
+Subject: selftests/bpf: Update bpf_clone_redirect expected return code
+
+From: Stanislav Fomichev <sdf@google.com>
+
+[ Upstream commit b772b70b69046c5b76e3f2eda680f692dee5e6d5 ]
+
+Commit 151e887d8ff9 ("veth: Fixing transmit return status for dropped
+packets") started propagating proper NET_XMIT_DROP error to the caller
+which means it's now possible to get positive error code when calling
+bpf_clone_redirect() in this particular test. Update the test to reflect
+that.
+
+Reported-by: Daniel Borkmann <daniel@iogearbox.net>
+Signed-off-by: Stanislav Fomichev <sdf@google.com>
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Link: https://lore.kernel.org/bpf/20230911194731.286342-2-sdf@google.com
+[ Note: Commit 151e887d8ff9 was backported to 6.1 so this fix should be
+ as well. ]
+Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
+Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/bpf/prog_tests/empty_skb.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/tools/testing/selftests/bpf/prog_tests/empty_skb.c b/tools/testing/selftests/bpf/prog_tests/empty_skb.c
+index 0613f3bb8b5e4e..329e34e5226e3a 100644
+--- a/tools/testing/selftests/bpf/prog_tests/empty_skb.c
++++ b/tools/testing/selftests/bpf/prog_tests/empty_skb.c
+@@ -29,6 +29,7 @@ void serial_test_empty_skb(void)
+ int *ifindex;
+ int err;
+ int ret;
++ int lwt_egress_ret; /* expected retval at lwt/egress */
+ bool success_on_tc;
+ } tests[] = {
+ /* Empty packets are always rejected. */
+@@ -62,6 +63,7 @@ void serial_test_empty_skb(void)
+ .data_size_in = sizeof(eth_hlen),
+ .ifindex = &veth_ifindex,
+ .ret = -ERANGE,
++ .lwt_egress_ret = -ERANGE,
+ .success_on_tc = true,
+ },
+ {
+@@ -75,6 +77,7 @@ void serial_test_empty_skb(void)
+ .data_size_in = sizeof(eth_hlen),
+ .ifindex = &ipip_ifindex,
+ .ret = -ERANGE,
++ .lwt_egress_ret = -ERANGE,
+ },
+
+ /* ETH_HLEN+1-sized packet should be redirected. */
+@@ -84,6 +87,7 @@ void serial_test_empty_skb(void)
+ .data_in = eth_hlen_pp,
+ .data_size_in = sizeof(eth_hlen_pp),
+ .ifindex = &veth_ifindex,
++ .lwt_egress_ret = 1, /* veth_xmit NET_XMIT_DROP */
+ },
+ {
+ .msg = "ipip ETH_HLEN+1 packet ingress",
+@@ -113,8 +117,12 @@ void serial_test_empty_skb(void)
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ bpf_object__for_each_program(prog, bpf_obj->obj) {
+- char buf[128];
++ bool at_egress = strstr(bpf_program__name(prog), "egress") != NULL;
+ bool at_tc = !strncmp(bpf_program__section_name(prog), "tc", 2);
++ int expected_ret;
++ char buf[128];
++
++ expected_ret = at_egress && !at_tc ? tests[i].lwt_egress_ret : tests[i].ret;
+
+ tattr.data_in = tests[i].data_in;
+ tattr.data_size_in = tests[i].data_size_in;
+@@ -133,7 +141,7 @@ void serial_test_empty_skb(void)
+ if (at_tc && tests[i].success_on_tc)
+ ASSERT_GE(bpf_obj->bss->ret, 0, buf);
+ else
+- ASSERT_EQ(bpf_obj->bss->ret, tests[i].ret, buf);
++ ASSERT_EQ(bpf_obj->bss->ret, expected_ret, buf);
+ }
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 5c9a34d2e3f45d48ca596c75bd1b37d8dbb77bd4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 13:33:34 +0200
+Subject: selftests: forwarding: lib: Add helpers for checksum handling
+
+From: Petr Machata <petrm@nvidia.com>
+
+commit 952e0ee38c7215c45192d8c899acd1830873f28b upstream.
+
+In order to generate IGMPv3 and MLDv2 packets on the fly, we will need
+helpers to calculate the packet checksum.
+
+The approach presented in this patch revolves around payload templates
+for mausezahn. These are mausezahn-like payload strings (01:23:45:...)
+with possibly one 2-byte sequence replaced with the word PAYLOAD. The
+main function is payload_template_calc_checksum(), which calculates
+RFC 1071 checksum of the message. There are further helpers to then
+convert the checksum to the payload format, and to expand it.
+
+For IPv6, MLDv2 message checksum is computed using a pseudoheader that
+differs from the header used in the payload itself. The fact that the
+two messages are different means that the checksum needs to be
+returned as a separate quantity, instead of being expanded in-place in
+the payload itself. Furthermore, the pseudoheader includes a length of
+the message. Much like the checksum, this needs to be expanded in
+mausezahn format. And likewise for number of addresses for (S,G)
+entries. Thus we have several places where a computed quantity needs
+to be presented in the payload format. Add a helper u16_to_bytes(),
+which will be used in all these cases.
+
+Signed-off-by: Petr Machata <petrm@nvidia.com>
+Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 02cb2e6bacbb ("selftests: forwarding: vxlan_bridge_1d: fix test failure with br_netfilter enabled")
+[bwh: Backported to 6,1: adjust context]
+Signed-off-by: Ben Hutchings <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/selftests/net/forwarding/lib.sh | 56 +++++++++++++++++++
+ 1 file changed, 56 insertions(+)
+
+diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh
+index 06027772cf79af..48d913341af267 100755
+--- a/tools/testing/selftests/net/forwarding/lib.sh
++++ b/tools/testing/selftests/net/forwarding/lib.sh
+@@ -1701,3 +1701,59 @@ hw_stats_monitor_test()
+
+ log_test "${type}_stats notifications"
+ }
++
++u16_to_bytes()
++{
++ local u16=$1; shift
++
++ printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
++}
++
++# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
++# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
++# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
++# stands for 00:00.
++payload_template_calc_checksum()
++{
++ local payload=$1; shift
++
++ (
++ # Set input radix.
++ echo "16i"
++ # Push zero for the initial checksum.
++ echo 0
++
++ # Pad the payload with a terminating 00: in case we get an odd
++ # number of bytes.
++ echo "${payload%:}:00:" |
++ sed 's/CHECKSUM/00:00/g' |
++ tr '[:lower:]' '[:upper:]' |
++ # Add the word to the checksum.
++ sed 's/\(..\):\(..\):/\1\2+\n/g' |
++ # Strip the extra odd byte we pushed if left unconverted.
++ sed 's/\(..\):$//'
++
++ echo "10000 ~ +" # Calculate and add carry.
++ echo "FFFF r - p" # Bit-flip and print.
++ ) |
++ dc |
++ tr '[:upper:]' '[:lower:]'
++}
++
++payload_template_expand_checksum()
++{
++ local payload=$1; shift
++ local checksum=$1; shift
++
++ local ckbytes=$(u16_to_bytes $checksum)
++
++ echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
++}
++
++payload_template_nbytes()
++{
++ local payload=$1; shift
++
++ payload_template_expand_checksum "${payload%:}" 0 |
++ sed 's/:/\n/g' | wc -l
++}
+--
+2.53.0
+
ipv6-fix-possible-infinite-loop-in-rt6_fill_node.patch
ipv6-fix-possible-infinite-loop-in-fib6_select_path.patch
net-skbuff-fix-pskb_carve-leaking-zcopy-pages.patch
+batman-adv-v-stop-ogmv2-on-disabled-interface.patch
+batman-adv-tvlv-abort-ogm-send-on-tvlv-append-failur.patch
+batman-adv-tt-reject-oversized-local-tvlv-buffers.patch
+batman-adv-bla-avoid-null-ptr-deref-for-claim-via-dr.patch
+batman-adv-tvlv-reject-oversized-tvlv-packets.patch
+batman-adv-iv-recover-ogm-scheduling-after-forward-p.patch
+selftests-forwarding-lib-add-helpers-for-checksum-ha.patch
+batman-adv-tp_meter-directly-shut-down-timer-on-clea.patch
+batman-adv-tt-fix-toctou-race-for-reported-vlans.patch
+batman-adv-tt-avoid-empty-vlan-responses.patch
+batman-adv-bla-avoid-double-decrement-of-bla.num_req.patch
+mm-page_alloc-clear-page-private-in-free_pages_prepa.patch
+selftests-bpf-add-generic-bpf-program-tester-loader.patch
+selftests-bpf-convert-test_global_funcs-test-to-test.patch
+revert-selftests-bpf-workaround-strict-bpf_lsm-retur.patch
+revert-selftests-bpf-add-tests-for-_opts-variants-of.patch
+selftests-bpf-add-read_build_id-function.patch
+bpf-fix-a-few-selftest-failures-due-to-llvm18-change.patch
+selftests-bpf-update-bpf_clone_redirect-expected-ret.patch
+selftests-bpf-enhance-align-selftest-s-expected-log-.patch
+revert-selftests-bpf-add-a-cgroup-prog-bpf_get_ns_cu.patch
+selftests-bpf-fix-arg_ptr_to_long-half-uninitialized.patch
+selftests-bpf-s-iptables-iptables-legacy-in-the-bpf_.patch
+net-packet-convert-po-tp_tx_has_off-to-an-atomic-fla.patch
+net-packet-convert-po-tp_loss-to-an-atomic-flag.patch
+net-packet-convert-po-has_vnet_hdr-to-an-atomic-flag.patch
+net-packet-convert-po-running-to-an-atomic-flag.patch
+net-packet-fix-toctou-race-on-mmap-d-vnet_hdr-in-tpa.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-dp-add-edp-1.5-bit-definition.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch
+revert-rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch
+rdma-rxe-fix-double-free-in-rxe_srq_from_init.patch-25226
--- /dev/null
+From 963533f502c669c85cee2e202682c19d4be6663b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:10:42 +0200
+Subject: batman-adv: bla: avoid double decrement of bla.num_requests
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 83ab69bd12b80f6ea169c8bea6977701b53a043d upstream.
+
+The bla.num_requests is increased when no request_sent was in progress. And
+it is decremented in various places (announcement was received, backbone is
+purged, periodic work). But the check if the request_sent is actually set
+to a specific state and the atomic_dec/_inc are not safe because they are
+not atomic (TOCTOU) and multiple such code portions can run concurrently.
+
+At the same time, it is necessary to modify request_sent (state) and
+bla.num_requests atomically. Otherwise batadv_bla_send_request() might set
+request_sent to 1 and is interrupted. batadv_handle_announce() can then
+set request_sent back to 0 and decrement num_requests before
+batadv_bla_send_request() incremented it.
+
+The two operations must therefore be locked. And since state (request_sent)
+and wait_periods are only accessed inside this lock, they can be converted
+to simpler datatypes. And to avoid that the bla.num_requests is touched by
+a parallel running context with a valid backbone_gw reference after
+batadv_bla_purge_backbone_gw() ran, a third state "stopped" is required to
+correctly signal that a backbone_gw is in the state of being cleaned up.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 51 ++++++++++++++++++--------
+ net/batman-adv/soft-interface.c | 1 +
+ net/batman-adv/types.h | 39 ++++++++++++++++----
+ 3 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 17db4191ddb39e..15aeb07285e61f 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -516,8 +516,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+- atomic_set(&entry->request_sent, 0);
+- atomic_set(&entry->wait_periods, 0);
++ entry->state = BATADV_BLA_BACKBONE_GW_SYNCED;
++ entry->wait_periods = 0;
+ ether_addr_copy(entry->orig, orig);
+ INIT_WORK(&entry->report_work, batadv_bla_loopdetect_report);
+ kref_init(&entry->refcount);
+@@ -546,9 +546,13 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ batadv_bla_send_announce(bat_priv, entry);
+
+ /* this will be decreased in the worker thread */
+- atomic_inc(&entry->request_sent);
+- atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS);
+- atomic_inc(&bat_priv->bla.num_requests);
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (entry->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ entry->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
++ entry->wait_periods = BATADV_BLA_WAIT_PERIODS;
++ atomic_inc(&bat_priv->bla.num_requests);
++ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ return entry;
+@@ -651,10 +655,12 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw)
+ backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST);
+
+ /* no local broadcasts should be sent or received, for now. */
+- if (!atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
+ atomic_inc(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 1);
+ }
++ spin_unlock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
+ }
+
+ /**
+@@ -875,10 +881,12 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ /* if we have sent a request and the crc was OK,
+ * we can allow traffic again.
+ */
+- if (atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
+ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ batadv_backbone_gw_put(backbone_gw);
+@@ -1257,9 +1265,13 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ purged = true;
+
+ /* don't wait for the pending request anymore */
+- if (atomic_read(&backbone_gw->request_sent))
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED)
+ atomic_dec(&bat_priv->bla.num_requests);
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_STOPPED;
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
++
+ batadv_bla_del_backbone_claims(backbone_gw);
+
+ hlist_del_rcu(&backbone_gw->hash_entry);
+@@ -1510,7 +1522,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ batadv_bla_send_loopdetect(bat_priv,
+ backbone_gw);
+
+- /* request_sent is only set after creation to avoid
++ /* state is only set to unsynced after creation to avoid
+ * problems when we are not yet known as backbone gw
+ * in the backbone.
+ *
+@@ -1519,14 +1531,21 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ * some grace time.
+ */
+
+- if (atomic_read(&backbone_gw->request_sent) == 0)
+- continue;
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state != BATADV_BLA_BACKBONE_GW_UNSYNCED)
++ goto unlock_next;
+
+- if (!atomic_dec_and_test(&backbone_gw->wait_periods))
+- continue;
++ if (backbone_gw->wait_periods > 0)
++ backbone_gw->wait_periods--;
++
++ if (backbone_gw->wait_periods > 0)
++ goto unlock_next;
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
++
++unlock_next:
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+ rcu_read_unlock();
+ }
+diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
+index 2758aba47a2f25..f46064333f3343 100644
+--- a/net/batman-adv/soft-interface.c
++++ b/net/batman-adv/soft-interface.c
+@@ -787,6 +787,7 @@ static int batadv_softif_init_late(struct net_device *dev)
+ atomic_set(&bat_priv->tt.ogm_append_cnt, 0);
+ #ifdef CONFIG_BATMAN_ADV_BLA
+ atomic_set(&bat_priv->bla.num_requests, 0);
++ spin_lock_init(&bat_priv->bla.num_requests_lock);
+ #endif
+ atomic_set(&bat_priv->tp_num, 0);
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 0ccd55b251fa26..f703d266780d74 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1091,6 +1091,12 @@ struct batadv_priv_bla {
+ /** @num_requests: number of bla requests in flight */
+ atomic_t num_requests;
+
++ /**
++ * @num_requests_lock: locks update num_requests +
++ * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
++ */
++ spinlock_t num_requests_lock;
++
+ /**
+ * @claim_hash: hash table containing mesh nodes this host has claimed
+ */
+@@ -1825,6 +1831,27 @@ struct batadv_priv {
+
+ #ifdef CONFIG_BATMAN_ADV_BLA
+
++enum batadv_bla_backbone_gw_state {
++ /**
++ * @BATADV_BLA_BACKBONE_GW_STOPPED: backbone gw is being removed
++ * and it must not longer work on requests
++ */
++ BATADV_BLA_BACKBONE_GW_STOPPED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_UNSYNCED: backbone was detected out
++ * of sync and a request was send. No traffic is forwarded until the
++ * situation is resolved
++ */
++ BATADV_BLA_BACKBONE_GW_UNSYNCED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_SYNCED: backbone is consider to be in
++ * sync. traffic can be forwarded
++ */
++ BATADV_BLA_BACKBONE_GW_SYNCED,
++};
++
+ /**
+ * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN
+ */
+@@ -1850,16 +1877,12 @@ struct batadv_bla_backbone_gw {
+ /**
+ * @wait_periods: grace time for bridge forward delays and bla group
+ * forming at bootup phase - no bcast traffic is formwared until it has
+- * elapsed
++ * elapsed. Must only be access with num_requests_lock.
+ */
+- atomic_t wait_periods;
++ u8 wait_periods;
+
+- /**
+- * @request_sent: if this bool is set to true we are out of sync with
+- * this backbone gateway - no bcast traffic is formwared until the
+- * situation was resolved
+- */
+- atomic_t request_sent;
++ /** @state: sync state. Must only be access with num_requests_lock. */
++ enum batadv_bla_backbone_gw_state state;
+
+ /** @crc: crc16 checksum over all claims */
+ u16 crc;
+--
+2.53.0
+
--- /dev/null
+From 9dfb7ab4aa826246f27d18f2d57a9c192d0110a4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:11:25 +0200
+Subject: batman-adv: bla: avoid NULL-ptr deref for claim via dropped interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f80d3d98d2ff78d9e2fe5d68b1f45948c4f7bd24 upstream.
+
+Without rtnl_lock held, a hardif might be retrieved as primary interface of
+a meshif, but then (while operating on this interface) getting decoupled
+from the mesh interface. In this case, the meshif still exists but the
+pointer from the primary hardif to the meshif is set to NULL.
+
+The mesh_iface must be checked first to be non-NULL before continuing to
+send an ARP request using meshif.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Reported-by: Ido Schimmel <idosch@nvidia.com>
+Reported-by: syzbot+9fdcc9f05a98a540b816@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=9fdcc9f05a98a540b816
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index e77f3ef3d7336a..17db4191ddb39e 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -356,12 +356,14 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac,
+ sizeof(local_claim_dest));
+ local_claim_dest.type = claimtype;
+
+- soft_iface = primary_if->soft_iface;
++ soft_iface = READ_ONCE(primary_if->soft_iface);
++ if (!soft_iface)
++ goto out;
+
+ skb = arp_create(ARPOP_REPLY, ETH_P_ARP,
+ /* IP DST: 0.0.0.0 */
+ zeroip,
+- primary_if->soft_iface,
++ soft_iface,
+ /* IP SRC: 0.0.0.0 */
+ zeroip,
+ /* Ethernet DST: Broadcast */
+--
+2.53.0
+
--- /dev/null
+From 1b885ca819cefa038080fb411f5d2a3f012c2d5b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:10:00 +0200
+Subject: batman-adv: iv: recover OGM scheduling after forward packet error
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit aa3153bd139a6c48667dcd02608d3b2c80bff02c upstream.
+
+When batadv_iv_ogm_schedule_buff() fails to allocate and queue a forward
+packet for OGM transmission, the work item that drives periodic OGM
+scheduling is never re-armed. This silently halts transmission of the
+node's own OGMs on the affected interface — only OGMs from other peers
+continue to be aggregated and forwarded.
+
+Fix this by tracking whether batadv_iv_ogm_queue_add() (and transitively
+batadv_iv_ogm_aggregate_new()) successfully scheduled a forward packet.
+When scheduling fails, batadv_iv_ogm_schedule_buff() falls back to queuing
+a dedicated recovery work item (reschedule_work) that fires after one
+originator interval and calls batadv_iv_ogm_schedule() again.
+
+Cc: stable@kernel.org
+Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 76 +++++++++++++++++++++++++++----------
+ net/batman-adv/types.h | 3 ++
+ 2 files changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 42b687c1a76807..b37c9fb178ae50 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -223,6 +223,8 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ hard_iface->bat_iv.ogm_buff = NULL;
+
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
++
++ cancel_delayed_work_sync(&hard_iface->bat_iv.reschedule_work);
+ }
+
+ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface)
+@@ -527,8 +529,10 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet,
+ * @if_incoming: interface where the packet was received
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
++static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ int packet_len, unsigned long send_time,
+ bool direct_link,
+ struct batadv_hard_iface *if_incoming,
+@@ -552,13 +556,13 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+
+ skb = netdev_alloc_skb_ip_align(NULL, skb_size);
+ if (!skb)
+- return;
++ return false;
+
+ forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing,
+ queue_left, bat_priv, skb);
+ if (!forw_packet_aggr) {
+ kfree_skb(skb);
+- return;
++ return false;
+ }
+
+ forw_packet_aggr->skb->priority = TC_PRIO_CONTROL;
+@@ -580,6 +584,8 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ batadv_iv_send_outstanding_bat_ogm_packet);
+
+ batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
++
++ return true;
+ }
+
+ /* aggregate a new packet into the existing ogm packet */
+@@ -609,8 +615,10 @@ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr,
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
+ * @send_time: timestamp (jiffies) when the packet is to be sent
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
++static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ unsigned char *packet_buff,
+ int packet_len,
+ struct batadv_hard_iface *if_incoming,
+@@ -662,14 +670,16 @@ static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ if (!own_packet && atomic_read(&bat_priv->aggregated_ogms))
+ send_time += max_aggregation_jiffies;
+
+- batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
+- send_time, direct_link,
+- if_incoming, if_outgoing,
+- own_packet);
++ return batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
++ send_time, direct_link,
++ if_incoming, if_outgoing,
++ own_packet);
+ } else {
+ batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff,
+ packet_len, direct_link);
+ spin_unlock_bh(&bat_priv->forw_bat_list_lock);
++
++ return true;
+ }
+ }
+
+@@ -781,6 +791,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ bool reschedule = false;
++ bool scheduled;
+ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+@@ -809,11 +821,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ ogm_buff_len,
+ BATADV_OGM_HLEN);
+ if (ret < 0) {
+- /* OGMs must be queued even when the buffer allocation for
+- * TVLVs failed. just fall back to the non-TVLV version
+- */
+- ret = 0;
+- *ogm_buff_len = BATADV_OGM_HLEN;
++ reschedule = true;
++ goto out;
+ }
+
+ tvlv_len = ret;
+@@ -835,8 +844,11 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ /* OGMs from secondary interfaces are only scheduled on their
+ * respective interfaces.
+ */
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
+- hard_iface, hard_iface, 1, send_time);
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
++ hard_iface, hard_iface, 1, send_time);
++ if (!scheduled)
++ reschedule = true;
++
+ goto out;
+ }
+
+@@ -851,15 +863,28 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ if (!kref_get_unless_zero(&tmp_hard_iface->refcount))
+ continue;
+
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
+- *ogm_buff_len, hard_iface,
+- tmp_hard_iface, 1, send_time);
+-
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
++ *ogm_buff_len, hard_iface,
++ tmp_hard_iface, 1, send_time);
+ batadv_hardif_put(tmp_hard_iface);
++
++ if (!scheduled && tmp_hard_iface == hard_iface)
++ reschedule = true;
+ }
+ rcu_read_unlock();
+
+ out:
++ if (reschedule) {
++ /* there was a failure scheduling the own forward packet.
++ * as result, the batadv_iv_send_outstanding_bat_ogm_packet()
++ * work item is no longer scheduled. it is therefore necessary
++ * to reschedule it manually
++ */
++ queue_delayed_work(batadv_event_workqueue,
++ &hard_iface->bat_iv.reschedule_work,
++ msecs_to_jiffies(atomic_read(&bat_priv->orig_interval)));
++ }
++
+ batadv_hardif_put(primary_if);
+ }
+
+@@ -874,6 +899,17 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface)
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
+ }
+
++static void batadv_iv_ogm_reschedule(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct batadv_hard_iface *hard_iface;
++
++ hard_iface = container_of(delayed_work,
++ struct batadv_hard_iface,
++ bat_iv.reschedule_work);
++ batadv_iv_ogm_schedule(hard_iface);
++}
++
+ /**
+ * batadv_iv_orig_ifinfo_sum() - Get bcast_own sum for originator over interface
+ * @orig_node: originator which reproadcasted the OGMs directly
+@@ -2277,6 +2313,8 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1,
+
+ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface)
+ {
++ INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule);
++
+ /* begin scheduling originator messages on that interface */
+ batadv_iv_ogm_schedule(hard_iface);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index fe774ec8b80b75..0ccd55b251fa26 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -83,6 +83,9 @@ struct batadv_hard_iface_bat_iv {
+ /** @ogm_seqno: OGM sequence number - used to identify each OGM */
+ atomic_t ogm_seqno;
+
++ /** @reschedule_work: recover OGM schedule after schedule error */
++ struct delayed_work reschedule_work;
++
+ /** @ogm_buff_mutex: lock protecting ogm_buff and ogm_buff_len */
+ struct mutex ogm_buff_mutex;
+ };
+--
+2.53.0
+
--- /dev/null
+From 58fa179914a593328458ad3eb28c4b2e65f7c786 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:09:05 +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: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 59 ++++++++++++++++++++++++---------------
+ 1 file changed, 36 insertions(+), 23 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 04a83d6be45bc0..bc3dc377f0bfd0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -255,6 +255,7 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason,
+ * 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_notify(enum batadv_tp_meter_reason reason,
+ * 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_list_find(struct batadv_priv *bat_priv,
+ 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
+@@ -286,12 +291,33 @@ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+ return tp_vars;
+ }
+
++/**
++ * 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_list_find(struct batadv_priv *bat_priv,
+ */
+ 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 batadv_priv *bat_priv, const u8 *dst,
+ 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
+@@ -671,13 +700,10 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+
+ /* 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;
+
+@@ -986,10 +1012,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ 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,
+@@ -1110,18 +1134,14 @@ void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
+ 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);
+@@ -1377,7 +1397,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_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;
+
+@@ -1448,7 +1468,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ } 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",
+@@ -1457,13 +1477,6 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ }
+
+- 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
+--
+2.53.0
+
--- /dev/null
+From a5dbd1e85a7af01c6569e23d6e66a8954288c2ec Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:21:35 +0200
+Subject: batman-adv: tp_meter: directly shut down timer on cleanup
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d5487249a81ea658717614009c8f46acc5b7101a upstream.
+
+batadv_tp_sender_cleanup() was calling timer_delete_sync() followed by
+timer_delete() to guard against the timer handler re-arming itself between
+the two calls. This double-deletion hack relied on the sending status being
+set to 0 to suppress re-arming.
+
+Replace both calls with a single timer_shutdown_sync(). This function both
+waits for any running timer callback to complete (like timer_delete_sync())
+and permanently disarms the timer so it cannot be re-armed afterwards,
+making re-arming prevention unconditional and self-documenting.
+
+The re-arming property is also required because otherwise:
+
+1. context 0 (batadv_tp_recv_ack()) checks in
+ batadv_tp_reset_sender_timer() if sending is still 1 -> it is
+2. context 1 changes in batadv_tp_sender_shutdown() sending to 0 and in
+ this process forces the kthread to stop timer in
+ batadv_tp_sender_cleanup()
+3. context 0 continues in batadv_tp_reset_sender_timer() and rearms the
+ timer -> but the reference for it is already gone
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ adapt pre-hunk to old del_timer* names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index bc3dc377f0bfd0..dfc3374549921f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -430,13 +430,7 @@ static void batadv_tp_sender_cleanup(struct batadv_tp_vars *tp_vars)
+ batadv_tp_list_detach(tp_vars);
+
+ /* kill the timer and remove its reference */
+- del_timer_sync(&tp_vars->timer);
+- /* the worker might have rearmed itself therefore we kill it again. Note
+- * that if the worker should run again before invoking the following
+- * del_timer(), it would not re-arm itself once again because the status
+- * is OFF now
+- */
+- del_timer(&tp_vars->timer);
++ timer_shutdown_sync(&tp_vars->timer);
+ batadv_tp_vars_put(tp_vars);
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 5642f51788b0bd26b6a7728d5740a72c5fe5ce56 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:04:50 +0200
+Subject: batman-adv: tt: avoid empty VLAN responses
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit fa1bd704940b5bcbc32c0b28db9167405c8ee5e0 upstream.
+
+The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
+sending inconsistent TT TLVLs") added checks to the local (direct) TT
+response code. But the response can also be done indirectly by another node
+using the global TT state. To avoid such inconsistency states reported in
+the original fix, also avoid sending empty VLANs for replies from the
+global TT state.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index d4cebe122e528a..4045ddefc29b47 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -843,17 +843,19 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ s32 *tt_len)
+ {
+ u16 num_vlan = 0;
+- u16 num_entries = 0;
+ u16 tvlv_len = 0;
+ unsigned int change_offset;
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_orig_node_vlan *vlan;
++ u16 total_entries = 0;
+ u8 *tt_change_ptr;
++ int vlan_entries;
+
+ spin_lock_bh(&orig_node->vlan_list_lock);
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ total_entries += vlan_entries;
+ num_vlan++;
+- num_entries += atomic_read(&vlan->tt.num_entries);
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -861,7 +863,7 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+
+ /* if tt_len is negative, allocate the space needed by the full table */
+ if (*tt_len < 0)
+- *tt_len = batadv_tt_len(num_entries);
++ *tt_len = batadv_tt_len(total_entries);
+
+ if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+ *tt_len = 0;
+@@ -882,14 +884,27 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ if (vlan_entries < 1)
++ continue;
++
+ tt_vlan->vid = htons(vlan->vid);
+ tt_vlan->crc = htonl(vlan->tt.crc);
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 2f1e6d3e5d741e8138179ae67735e3d39e217381 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:57:44 +0200
+Subject: batman-adv: tt: fix TOCTOU race for reported vlans
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 94d27005016be15ffc638b2ecbc4d58805ad7b48 upstream.
+
+The local TT based TVLV is generated by first checking the number of VLANs
+which have at least one TT entry. A new buffer with the correct size for
+the VLANs is then allocated. Only then, the list of VLANs s used to fill
+the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
+is held. But the actual number of TT entries of each VLAN can still
+increase during this time - just not the number of VLANs in the list.
+
+But the prefilter used in the buffer size calculation might still cause an
+increase of the number of VLANs which need to be stored. Simply because a
+VLAN might now suddenly have at least one entry when it had none in the
+pre-alloc check - and then needs to occupy space which was not allocated.
+
+It is better to overestimate the buffer size at the beginning and then fill
+the buffer only with the VLANs which are not empty.
+
+Cc: stable@kernel.org
+Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 8ffebece03c529..d4cebe122e528a 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -934,11 +934,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+- if (vlan_entries < 1)
+- continue;
+-
+- num_vlan++;
+ total_entries += vlan_entries;
++ num_vlan++;
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -964,6 +961,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+@@ -974,8 +972,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 7ec95e77d8cf28983bb8e5e7cad19943f138f2b3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:03:00 +0200
+Subject: batman-adv: tt: reject oversized local TVLV buffers
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 1e9fab756f8395096d5bba7be0c373c4c8f5d165 upstream.
+
+The commit 3a359bf5c61d ("batman-adv: reject oversized global TT response
+buffers") added a check to ensure that a global return buffer size can be
+stored in an u16. The same buffer handling also exists for the local data
+buffer but was not touched.
+
+A similar check should be also be in place for the local TVLV buffer. It
+doesn't have the similar attack surface because it is only generated from
+locally discovered MAC addresses but the dynamic nature could still cause
+temporarily to large buffers.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index d830ccf016697b..8ffebece03c529 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -924,12 +924,12 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_softif_vlan *vlan;
++ size_t change_offset;
+ u16 num_vlan = 0;
+ u16 vlan_entries = 0;
+ u16 total_entries = 0;
+ u16 tvlv_len;
+ u8 *tt_change_ptr;
+- int change_offset;
+
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+@@ -948,8 +948,10 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ if (*tt_len < 0)
+ *tt_len = batadv_tt_len(total_entries);
+
+- tvlv_len = *tt_len;
+- tvlv_len += change_offset;
++ if (check_add_overflow(*tt_len, change_offset, &tvlv_len)) {
++ tvlv_len = 0;
++ goto out;
++ }
+
+ *tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
+ if (!*tt_data) {
+--
+2.53.0
+
--- /dev/null
+From 9b197968072387191d0b3f4d3cdb5396109fbaba Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:46:02 +0200
+Subject: batman-adv: tvlv: abort OGM send on tvlv append failure
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 501368506563e151b322c8c3f228b796e615b90d upstream.
+
+batadv_tvlv_container_ogm_append() could fail in two ways: a memory
+allocation failure when resizing the packet buffer, or the tvlv data
+exceeding U16_MAX bytes. In both cases the function previously returned the
+old (now stale) tvlv_value_len rather than signalling an error, causing the
+OGM/OGM2 send path to transmit a packet whose TVLV length field no longer
+matched the actual buffer contents. And because it also didn't fill in the
+new TVLV data, sending either uninitialized or corrupted data on the wire.
+
+All errors in batadv_tvlv_container_ogm_append() must be forwarded to the
+caller. And the caller must abort the send of the OGM2. For B.A.T.M.A.N.
+IV, it is currently not allowed to abort the send. The non-TVLV part of the
+OGM must be queued up instead.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 16 +++++++++++++---
+ net/batman-adv/bat_v_ogm.c | 26 ++++++++++++++------------
+ net/batman-adv/tvlv.c | 17 ++++++++++++-----
+ net/batman-adv/tvlv.h | 2 +-
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 748188d3b878b2..42b687c1a76807 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -781,6 +781,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+
+@@ -804,9 +805,18 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
+- ogm_buff_len,
+- BATADV_OGM_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM_HLEN);
++ if (ret < 0) {
++ /* OGMs must be queued even when the buffer allocation for
++ * TVLVs failed. just fall back to the non-TVLV version
++ */
++ ret = 0;
++ *ogm_buff_len = BATADV_OGM_HLEN;
++ }
++
++ tvlv_len = ret;
+ }
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff);
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 310248a5812c49..8cfc3944dcfd52 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -271,9 +271,9 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_ogm2_packet *ogm_packet;
+ struct sk_buff *skb, *skb_tmp;
+- unsigned char *ogm_buff;
+- int ogm_buff_len;
+- u16 tvlv_len = 0;
++ unsigned char **ogm_buff;
++ int *ogm_buff_len;
++ u16 tvlv_len;
+ int ret;
+
+ lockdep_assert_held(&bat_priv->bat_v.ogm_buff_mutex);
+@@ -281,25 +281,27 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING)
+ goto out;
+
+- ogm_buff = bat_priv->bat_v.ogm_buff;
+- ogm_buff_len = bat_priv->bat_v.ogm_buff_len;
++ ogm_buff = &bat_priv->bat_v.ogm_buff;
++ ogm_buff_len = &bat_priv->bat_v.ogm_buff_len;
++
+ /* tt changes have to be committed before the tvlv data is
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, &ogm_buff,
+- &ogm_buff_len,
+- BATADV_OGM2_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM2_HLEN);
++ if (ret < 0)
++ goto reschedule;
+
+- bat_priv->bat_v.ogm_buff = ogm_buff;
+- bat_priv->bat_v.ogm_buff_len = ogm_buff_len;
++ tvlv_len = ret;
+
+- skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + ogm_buff_len);
++ skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + *ogm_buff_len);
+ if (!skb)
+ goto reschedule;
+
+ skb_reserve(skb, ETH_HLEN);
+- skb_put_data(skb, ogm_buff, ogm_buff_len);
++ skb_put_data(skb, *ogm_buff, *ogm_buff_len);
+
+ ogm_packet = (struct batadv_ogm2_packet *)skb->data;
+ ogm_packet->seqno = htonl(atomic_read(&bat_priv->bat_v.ogm_seqno));
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 2a583215d439bd..76c6e0599694c7 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -8,6 +8,7 @@
+
+ #include <linux/byteorder/generic.h>
+ #include <linux/container_of.h>
++#include <linux/errno.h>
+ #include <linux/etherdevice.h>
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+@@ -306,9 +307,10 @@ static bool batadv_tvlv_realloc_packet_buff(unsigned char **packet_buff,
+ * The ogm packet might be enlarged or shrunk depending on the current size
+ * and the size of the to-be-appended tvlv containers.
+ *
+- * Return: size of all appended tvlv containers in bytes.
++ * Return: size of all appended tvlv containers in bytes (max U16_MAX), negative
++ * if operation failed
+ */
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len)
+ {
+@@ -316,6 +318,7 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_len;
+ void *tvlv_value;
++ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+@@ -323,9 +326,12 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+-
+- if (!ret)
++ if (!ret) {
++ tvlv_len_ret = -ENOMEM;
+ goto end;
++ }
++
++ tvlv_len_ret = tvlv_value_len;
+
+ if (!tvlv_value_len)
+ goto end;
+@@ -344,7 +350,8 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ end:
+ spin_unlock_bh(&bat_priv->tvlv.container_list_lock);
+- return tvlv_value_len;
++
++ return tvlv_len_ret;
+ }
+
+ /**
+diff --git a/net/batman-adv/tvlv.h b/net/batman-adv/tvlv.h
+index e5697230d99173..f96f6b3f44a001 100644
+--- a/net/batman-adv/tvlv.h
++++ b/net/batman-adv/tvlv.h
+@@ -16,7 +16,7 @@
+ void batadv_tvlv_container_register(struct batadv_priv *bat_priv,
+ u8 type, u8 version,
+ void *tvlv_value, u16 tvlv_value_len);
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len);
+ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv,
+--
+2.53.0
+
--- /dev/null
+From 41c0d1833ed99ea68e8473043f65fbba76f136bc Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:08:04 +0200
+Subject: batman-adv: tvlv: reject oversized TVLV packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f50487e3566358b2b982b7801945e858c78ad9ab upstream.
+
+batadv_tvlv_container_ogm_append() builds a TVLV packet section from
+the tvlv.container_list. The total size of this section is computed by
+batadv_tvlv_container_list_size(), which sums the sizes of all registered
+containers.
+
+The return type and accumulator in batadv_tvlv_container_list_size() were
+u16. If the accumulated size exceeds U16_MAX, the value wraps around,
+causing the subsequent allocation in batadv_tvlv_container_ogm_append()
+to be undersized. The memcpy-style copy that follows would then write
+beyond the end of the allocated buffer, corrupting kernel memory.
+
+Fix this by widening the return type of batadv_tvlv_container_list_size()
+to size_t. In batadv_tvlv_container_ogm_append(), check the computed length
+against U16_MAX before proceeding, and bail out as if the allocation had
+failed when the limit is exceeded.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 76c6e0599694c7..8d6b017c433cc9 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -13,6 +13,7 @@
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+ #include <linux/kref.h>
++#include <linux/limits.h>
+ #include <linux/list.h>
+ #include <linux/lockdep.h>
+ #include <linux/netdevice.h>
+@@ -160,10 +161,10 @@ batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version)
+ *
+ * Return: size of all currently registered tvlv containers in bytes.
+ */
+-static u16 batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
++static size_t batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
+ {
+ struct batadv_tvlv_container *tvlv;
+- u16 tvlv_len = 0;
++ size_t tvlv_len = 0;
+
+ lockdep_assert_held(&bat_priv->tvlv.container_list_lock);
+
+@@ -316,13 +317,17 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_container *tvlv;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+- u16 tvlv_value_len;
++ size_t tvlv_value_len;
+ void *tvlv_value;
+ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+ tvlv_value_len = batadv_tvlv_container_list_size(bat_priv);
++ if (tvlv_value_len > U16_MAX) {
++ tvlv_len_ret = -E2BIG;
++ goto end;
++ }
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+--
+2.53.0
+
--- /dev/null
+From a4866cc765d098305ad5e0af312c15a329862277 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:27:33 +0200
+Subject: batman-adv: v: stop OGMv2 on disabled interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f8ce8b8331a1bc44ad4905886a482214d428b253 upstream.
+
+When a batadv_hard_iface is disabled, its mesh_iface pointer is set to
+NULL. However, batadv_v_ogm_send_meshif() may still dispatch OGMs via
+batadv_v_ogm_queue_on_if() for interfaces that have since lost their
+mesh_iface association. This results in a NULL pointer dereference when
+batadv_v_ogm_queue_on_if() unconditionally calls netdev_priv() on the
+now NULL hard_iface->mesh_iface to retrieve the batadv_priv.
+
+It is necessary to ensure that the batadv_v_ogm_queue_on_if() checks that
+it is using the same mesh_iface for which batadv_v_ogm_send_meshif() was
+called.
+
+Cc: stable@kernel.org
+Fixes: 0da0035942d4 ("batman-adv: OGMv2 - add basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v_ogm.c | 33 +++++++++++++++++++++------------
+ 1 file changed, 21 insertions(+), 12 deletions(-)
+
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 8f89ffe6020ced..310248a5812c49 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -115,14 +115,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
+
+ /**
+ * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to send
+ * @hard_iface: the interface to use to send the OGM
+ */
+-static void batadv_v_ogm_send_to_if(struct sk_buff *skb,
++static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+-
+ if (hard_iface->if_status != BATADV_IF_ACTIVE) {
+ kfree_skb(skb);
+ return;
+@@ -189,6 +189,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+
+ /**
+ * batadv_v_ogm_aggr_send() - flush & send aggregation queue
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @hard_iface: the interface with the aggregation queue to flush
+ *
+ * Aggregates all OGMv2 packets currently in the aggregation queue into a
+@@ -198,7 +199,8 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+ *
+ * Caller needs to hold the hard_iface->bat_v.aggr_list.lock.
+ */
+-static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
++static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv,
++ struct batadv_hard_iface *hard_iface)
+ {
+ unsigned int aggr_len = hard_iface->bat_v.aggr_len;
+ struct sk_buff *skb_aggr;
+@@ -228,27 +230,32 @@ static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
+ consume_skb(skb);
+ }
+
+- batadv_v_ogm_send_to_if(skb_aggr, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface);
+ }
+
+ /**
+ * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to queue
+ * @hard_iface: the interface to queue the OGM on
+ */
+-static void batadv_v_ogm_queue_on_if(struct sk_buff *skb,
++static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
++ if (hard_iface->soft_iface != bat_priv->soft_iface) {
++ kfree_skb(skb);
++ return;
++ }
+
+ if (!atomic_read(&bat_priv->aggregated_ogms)) {
+- batadv_v_ogm_send_to_if(skb, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface);
+ return;
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
+@@ -347,7 +354,7 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ break;
+ }
+
+- batadv_v_ogm_queue_on_if(skb_tmp, hard_iface);
++ batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface);
+ batadv_hardif_put(hard_iface);
+ }
+ rcu_read_unlock();
+@@ -387,12 +394,14 @@ void batadv_v_ogm_aggr_work(struct work_struct *work)
+ {
+ struct batadv_hard_iface_bat_v *batv;
+ struct batadv_hard_iface *hard_iface;
++ struct batadv_priv *bat_priv;
+
+ batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work);
+ hard_iface = container_of(batv, struct batadv_hard_iface, bat_v);
++ bat_priv = netdev_priv(hard_iface->soft_iface);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+
+ batadv_v_ogm_start_queue_timer(hard_iface);
+@@ -582,7 +591,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv,
+ if_outgoing->net_dev->name, ntohl(ogm_forward->throughput),
+ ogm_forward->ttl, if_incoming->net_dev->name);
+
+- batadv_v_ogm_queue_on_if(skb, if_outgoing);
++ batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing);
+
+ out:
+ batadv_orig_ifinfo_put(orig_ifinfo);
+--
+2.53.0
+
--- /dev/null
+From 1be1a226934affa1bea20e0a351aae1e7c164050 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:42:05 +0300
+Subject: drm/dp: Add eDP 1.5 bit definition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Suraj Kandpal <suraj.kandpal@intel.com>
+
+commit 5dfc37a6b77bf6beedbd30d70184b54e1a08ccac upstream.
+
+Add the eDP revision bit value for 1.5.
+
+Spec: eDPv1.5 Table 16-5
+Signed-off-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Reviewed-by: Arun R Murthy <arun.r.murthy@intel.com>
+Tested-by: Ben Kao <ben.kao@intel.com>
+Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Link: https://patchwork.freedesktop.org/patch/msgid/20250206063253.2827017-2-suraj.kandpal@intel.com
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/drm/display/drm_dp.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
+index 3bd9f482f0c3e6..dd218400a613e3 100644
+--- a/include/drm/display/drm_dp.h
++++ b/include/drm/display/drm_dp.h
+@@ -997,6 +997,7 @@
+ # define DP_EDP_14 0x03
+ # define DP_EDP_14a 0x04 /* eDP 1.4a */
+ # define DP_EDP_14b 0x05 /* eDP 1.4b */
++# define DP_EDP_15 0x06 /* eDP 1.5 */
+
+ #define DP_EDP_GENERAL_CAP_1 0x701
+ # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0)
+--
+2.53.0
+
--- /dev/null
+From 570a6a6f6b17c1ab5a96e802e3d8e6a069cc833a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:42:03 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From 13a8e2b69c4b6c16032c020b9e4f5a8304622a25 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:42:06 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 27 +++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 9c38b957701675..5173f5759ce888 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -1303,6 +1303,30 @@ static bool psr2_granularity_check(struct intel_dp *intel_dp,
+ return true;
+ }
+
++static bool apply_scanline_indication_wa(struct intel_dp *intel_dp,
++ struct intel_crtc_state *crtc_state)
++{
++ u8 early_scanline_support = intel_dp->intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
+ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+ struct intel_crtc_state *crtc_state)
+ {
+@@ -1324,7 +1348,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(intel_dp, crtc_state);
+ }
+
+ static int intel_psr_entry_setup_frames(struct intel_dp *intel_dp,
+--
+2.53.0
+
--- /dev/null
+From 7e7c761578ce407e19bb206dced0bac66da9a83e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:42:04 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index 2039c17a9ee787..992945b37190e6 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -1773,6 +1773,7 @@ struct intel_dp {
+ u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE];
+ u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE];
+ u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+ /* source rates */
+ int num_source_rates;
+ const int *source_rates;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 2a7f379c59fefd..9c38b957701675 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -35,6 +35,7 @@
+ #include "intel_de.h"
+ #include "intel_display_types.h"
+ #include "intel_dp.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_frontbuffer.h"
+ #include "intel_hdmi.h"
+@@ -665,6 +666,12 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp)
+ drm_dbg_kms(display->drm, "PSR2 %ssupported\n",
+ intel_dp->psr.sink_psr2_support ? "" : "not ");
+ }
++
++ if (intel_dp->psr.sink_psr2_support)
++ drm_dp_dpcd_read(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &intel_dp->intel_wa_dpcd,
++ sizeof(intel_dp->intel_wa_dpcd));
+ }
+
+ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
+--
+2.53.0
+
--- /dev/null
+From f7fde9d942389143ae0aa178819bd77ece7adbf4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 1 Jun 2026 09:36:09 +0100
+Subject: HID: core: Add printk_ratelimited variants to hid_warn() etc
+
+From: Vicki Pfau <vi@endrift.com>
+
+[ Upstream commit 1d64624243af8329b4b219d8c39e28ea448f9929 ]
+
+hid_warn_ratelimited() is needed. Add the others as part of the block.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/linux/hid.h | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/include/linux/hid.h b/include/linux/hid.h
+index 7d8d09318fa91d..bef017d6b44042 100644
+--- a/include/linux/hid.h
++++ b/include/linux/hid.h
+@@ -1245,4 +1245,15 @@ void hid_quirks_exit(__u16 bus);
+ #define hid_dbg_once(hid, fmt, ...) \
+ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__)
+
++#define hid_err_ratelimited(hid, fmt, ...) \
++ dev_err_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
++#define hid_notice_ratelimited(hid, fmt, ...) \
++ dev_notice_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
++#define hid_warn_ratelimited(hid, fmt, ...) \
++ dev_warn_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
++#define hid_info_ratelimited(hid, fmt, ...) \
++ dev_info_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
++#define hid_dbg_ratelimited(hid, fmt, ...) \
++ dev_dbg_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
++
+ #endif
+--
+2.53.0
+
--- /dev/null
+From 61a38d8797ea96decb246fc6fbd728f7f1d09310 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 1 Jun 2026 09:36:12 +0100
+Subject: HID: core: Fix size_t specifier in hid_report_raw_event()
+
+From: Nathan Chancellor <nathan@kernel.org>
+
+[ Upstream commit 4d3a2a466b8d68d852a1f3bbf11204b718428dc4 ]
+
+When building for 32-bit platforms, for which 'size_t' is
+'unsigned int', there are warnings around using the incorrect format
+specifier to print bsize in hid_report_raw_event():
+
+ drivers/hid/hid-core.c:2054:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
+ 2053 | hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
+ | ~~~
+ | %zu
+ 2054 | report->id, csize, bsize);
+ | ^~~~~
+ drivers/hid/hid-core.c:2076:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
+ 2075 | hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
+ | ~~~
+ | %zu
+ 2076 | report->id, rsize, bsize);
+ | ^~~~~
+
+Use the proper 'size_t' format specifier, '%zu', to clear up the
+warnings.
+
+Cc: stable@vger.kernel.org
+Fixes: 2c85c61d1332 ("HID: pass the buffer size to hid_report_raw_event")
+Reported-by: Miguel Ojeda <ojeda@kernel.org>
+Closes: https://lore.kernel.org/20260516020430.110135-1-ojeda@kernel.org/
+Signed-off-by: Nathan Chancellor <nathan@kernel.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+(cherry picked from commit 3ab135238832446399614e7a4bb796d620717806)
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/hid/hid-core.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
+index d9ea99cdb68e06..87d990ada8688a 100644
+--- a/drivers/hid/hid-core.c
++++ b/drivers/hid/hid-core.c
+@@ -2015,7 +2015,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
+ return 0;
+
+ if (unlikely(bsize < csize)) {
+- hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
++ hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n",
+ report->id, csize, bsize);
+ return -EINVAL;
+ }
+@@ -2037,7 +2037,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
+ rsize = max_buffer_size;
+
+ if (bsize < rsize) {
+- hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
++ hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n",
+ report->id, rsize, bsize);
+ return -EINVAL;
+ }
+--
+2.53.0
+
--- /dev/null
+From 993f336256ff20948bbc58104e32524d9d813fa4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 1 Jun 2026 09:36:11 +0100
+Subject: HID: core: introduce hid_safe_input_report()
+
+From: Benjamin Tissoires <bentiss@kernel.org>
+
+[ Upstream commit 206342541fc887ae919774a43942dc883161fece ]
+
+hid_input_report() is used in too many places to have a commit that
+doesn't cross subsystem borders. Instead of changing the API, introduce
+a new one when things matters in the transport layers:
+- usbhid
+- i2chid
+
+This effectively revert to the old behavior for those two transport
+layers.
+
+Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+(cherry picked from commit 301338b8edadc67a42b1c86add975091e66768d9)
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/hid/hid-core.c | 25 +++++++++++++++++++++++++
+ drivers/hid/i2c-hid/i2c-hid-core.c | 7 ++++---
+ drivers/hid/usbhid/hid-core.c | 11 ++++++-----
+ include/linux/hid.h | 2 ++
+ 4 files changed, 37 insertions(+), 8 deletions(-)
+
+diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
+index ceff91722c3c83..d9ea99cdb68e06 100644
+--- a/drivers/hid/hid-core.c
++++ b/drivers/hid/hid-core.c
+@@ -2146,6 +2146,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+ * @interrupt: distinguish between interrupt and control transfers
+ *
+ * This is data entry for lower layers.
++ * Legacy, please use hid_safe_input_report() instead.
+ */
+ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+ int interrupt)
+@@ -2156,6 +2157,30 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
+ }
+ EXPORT_SYMBOL_GPL(hid_input_report);
+
++/**
++ * hid_safe_input_report - report data from lower layer (usb, bt...)
++ *
++ * @hid: hid device
++ * @type: HID report type (HID_*_REPORT)
++ * @data: report contents
++ * @bufsize: allocated size of the data buffer
++ * @size: useful size of data parameter
++ * @interrupt: distinguish between interrupt and control transfers
++ *
++ * This is data entry for lower layers.
++ * Please use this function instead of the non safe version because we provide
++ * here the size of the buffer, allowing hid-core to make smarter decisions
++ * regarding the incoming buffer.
++ */
++int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data,
++ size_t bufsize, u32 size, int interrupt)
++{
++ return __hid_input_report(hid, type, data, bufsize, size, interrupt, 0,
++ false, /* from_bpf */
++ false /* lock_already_taken */);
++}
++EXPORT_SYMBOL_GPL(hid_safe_input_report);
++
+ bool hid_match_one_id(const struct hid_device *hdev,
+ const struct hid_device_id *id)
+ {
+diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
+index cf8ae0df0cda95..8ce0535fc42d60 100644
+--- a/drivers/hid/i2c-hid/i2c-hid-core.c
++++ b/drivers/hid/i2c-hid/i2c-hid-core.c
+@@ -568,9 +568,10 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
+ if (ihid->hid->group != HID_GROUP_RMI)
+ pm_wakeup_event(&ihid->client->dev, 0);
+
+- hid_input_report(ihid->hid, HID_INPUT_REPORT,
+- ihid->inbuf + sizeof(__le16),
+- ret_size - sizeof(__le16), 1);
++ hid_safe_input_report(ihid->hid, HID_INPUT_REPORT,
++ ihid->inbuf + sizeof(__le16),
++ ihid->bufsize - sizeof(__le16),
++ ret_size - sizeof(__le16), 1);
+ }
+
+ return;
+diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
+index f14b46ce00cb67..336ad7cf3d484e 100644
+--- a/drivers/hid/usbhid/hid-core.c
++++ b/drivers/hid/usbhid/hid-core.c
+@@ -283,9 +283,9 @@ static void hid_irq_in(struct urb *urb)
+ break;
+ usbhid_mark_busy(usbhid);
+ if (!test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) {
+- hid_input_report(urb->context, HID_INPUT_REPORT,
+- urb->transfer_buffer,
+- urb->actual_length, 1);
++ hid_safe_input_report(urb->context, HID_INPUT_REPORT,
++ urb->transfer_buffer, urb->transfer_buffer_length,
++ urb->actual_length, 1);
+ /*
+ * autosuspend refused while keys are pressed
+ * because most keyboards don't wake up when
+@@ -482,9 +482,10 @@ static void hid_ctrl(struct urb *urb)
+ switch (status) {
+ case 0: /* success */
+ if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN)
+- hid_input_report(urb->context,
++ hid_safe_input_report(urb->context,
+ usbhid->ctrl[usbhid->ctrltail].report->type,
+- urb->transfer_buffer, urb->actual_length, 0);
++ urb->transfer_buffer, urb->transfer_buffer_length,
++ urb->actual_length, 0);
+ break;
+ case -ESHUTDOWN: /* unplug */
+ unplug = 1;
+diff --git a/include/linux/hid.h b/include/linux/hid.h
+index fdd401e4ebde3d..7d05b1edacd80c 100644
+--- a/include/linux/hid.h
++++ b/include/linux/hid.h
+@@ -949,6 +949,8 @@ struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_ty
+ int hid_set_field(struct hid_field *, unsigned, __s32);
+ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+ int interrupt);
++int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data,
++ size_t bufsize, u32 size, int interrupt);
+ struct hid_field *hidinput_get_led_field(struct hid_device *hid);
+ unsigned int hidinput_count_leds(struct hid_device *hid);
+ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code);
+--
+2.53.0
+
--- /dev/null
+From 159621bf6568570c7d59925c9e23a54ecc46fe3b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 1 Jun 2026 09:36:10 +0100
+Subject: HID: pass the buffer size to hid_report_raw_event
+
+From: Benjamin Tissoires <bentiss@kernel.org>
+
+[ Upstream commit 2c85c61d1332e1e16f020d76951baf167dcb6f7a ]
+
+commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing
+bogus memset()") enforced the provided data to be at least the size of
+the declared buffer in the report descriptor to prevent a buffer
+overflow. However, we can try to be smarter by providing both the buffer
+size and the data size, meaning that hid_report_raw_event() can make
+better decision whether we should plaining reject the buffer (buffer
+overflow attempt) or if we can safely memset it to 0 and pass it to the
+rest of the stack.
+
+Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Acked-by: Johan Hovold <johan@kernel.org>
+Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Stable-dep-of: 206342541fc8 ("HID: core: introduce hid_safe_input_report()")
+(cherry picked from commit 509c2605065004fc4cd86ee50a9350d402785307)
+[Lee: Backported to linux-6.12.y and beyond]
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/hid/bpf/hid_bpf_dispatch.c | 6 +++--
+ drivers/hid/hid-core.c | 37 +++++++++++++++++++++---------
+ drivers/hid/hid-gfrm.c | 4 ++--
+ drivers/hid/hid-logitech-hidpp.c | 2 +-
+ drivers/hid/hid-multitouch.c | 2 +-
+ drivers/hid/hid-primax.c | 2 +-
+ drivers/hid/hid-vivaldi-common.c | 2 +-
+ drivers/hid/wacom_sys.c | 6 ++---
+ drivers/staging/greybus/hid.c | 2 +-
+ include/linux/hid.h | 4 ++--
+ include/linux/hid_bpf.h | 14 +++++++----
+ 11 files changed, 51 insertions(+), 30 deletions(-)
+
+diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
+index 284861c166d9c4..b711d83dfde1d4 100644
+--- a/drivers/hid/bpf/hid_bpf_dispatch.c
++++ b/drivers/hid/bpf/hid_bpf_dispatch.c
+@@ -24,7 +24,8 @@ EXPORT_SYMBOL(hid_ops);
+
+ u8 *
+ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
+- u32 *size, int interrupt, u64 source, bool from_bpf)
++ size_t *buf_size, u32 *size, int interrupt, u64 source,
++ bool from_bpf)
+ {
+ struct hid_bpf_ctx_kern ctx_kern = {
+ .ctx = {
+@@ -74,6 +75,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
+ *size = ret;
+ }
+
++ *buf_size = ctx_kern.ctx.allocated_size;
+ return ctx_kern.data;
+ }
+ EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
+@@ -514,7 +516,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b
+ if (ret)
+ return ret;
+
+- return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
++ return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true,
+ lock_already_taken);
+ }
+
+diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
+index 294a25330ed030..ceff91722c3c83 100644
+--- a/drivers/hid/hid-core.c
++++ b/drivers/hid/hid-core.c
+@@ -1998,24 +1998,32 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
+ }
+ EXPORT_SYMBOL_GPL(__hid_request);
+
+-int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+- int interrupt)
++int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
++ size_t bufsize, u32 size, int interrupt)
+ {
+ struct hid_report_enum *report_enum = hid->report_enum + type;
+ struct hid_report *report;
+ struct hid_driver *hdrv;
+ int max_buffer_size = HID_MAX_BUFFER_SIZE;
+ u32 rsize, csize = size;
++ size_t bsize = bufsize;
+ u8 *cdata = data;
+ int ret = 0;
+
+ report = hid_get_report(report_enum, data);
+ if (!report)
+- goto out;
++ return 0;
++
++ if (unlikely(bsize < csize)) {
++ hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
++ report->id, csize, bsize);
++ return -EINVAL;
++ }
+
+ if (report_enum->numbered) {
+ cdata++;
+ csize--;
++ bsize--;
+ }
+
+ rsize = hid_compute_report_size(report);
+@@ -2028,9 +2036,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
+ else if (rsize > max_buffer_size)
+ rsize = max_buffer_size;
+
++ if (bsize < rsize) {
++ hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
++ report->id, rsize, bsize);
++ return -EINVAL;
++ }
++
+ if (csize < rsize) {
+ dbg_hid("report %d is too short, (%d < %d)\n", report->id,
+- csize, rsize);
++ csize, rsize);
+ memset(cdata + csize, 0, rsize - csize);
+ }
+
+@@ -2039,7 +2053,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
+ if (hid->claimed & HID_CLAIMED_HIDRAW) {
+ ret = hidraw_report_event(hid, data, size);
+ if (ret)
+- goto out;
++ return ret;
+ }
+
+ if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
+@@ -2051,15 +2065,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
+
+ if (hid->claimed & HID_CLAIMED_INPUT)
+ hidinput_report_event(hid, report);
+-out:
++
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(hid_report_raw_event);
+
+
+ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+- u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
+- bool lock_already_taken)
++ u8 *data, size_t bufsize, u32 size, int interrupt, u64 source,
++ bool from_bpf, bool lock_already_taken)
+ {
+ struct hid_report_enum *report_enum;
+ struct hid_driver *hdrv;
+@@ -2084,7 +2098,8 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+ report_enum = hid->report_enum + type;
+ hdrv = hid->driver;
+
+- data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf);
++ data = dispatch_hid_bpf_device_event(hid, type, data, &bufsize, &size, interrupt,
++ source, from_bpf);
+ if (IS_ERR(data)) {
+ ret = PTR_ERR(data);
+ goto unlock;
+@@ -2113,7 +2128,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+ goto unlock;
+ }
+
+- ret = hid_report_raw_event(hid, type, data, size, interrupt);
++ ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt);
+
+ unlock:
+ if (!lock_already_taken)
+@@ -2135,7 +2150,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+ int interrupt)
+ {
+- return __hid_input_report(hid, type, data, size, interrupt, 0,
++ return __hid_input_report(hid, type, data, size, size, interrupt, 0,
+ false, /* from_bpf */
+ false /* lock_already_taken */);
+ }
+diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c
+index 699186ff2349e9..d2a56bf92b416e 100644
+--- a/drivers/hid/hid-gfrm.c
++++ b/drivers/hid/hid-gfrm.c
+@@ -66,7 +66,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
+ switch (data[1]) {
+ case GFRM100_SEARCH_KEY_DOWN:
+ ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
+- sizeof(search_key_dn), 1);
++ sizeof(search_key_dn), sizeof(search_key_dn), 1);
+ break;
+
+ case GFRM100_SEARCH_KEY_AUDIO_DATA:
+@@ -74,7 +74,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
+
+ case GFRM100_SEARCH_KEY_UP:
+ ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
+- sizeof(search_key_up), 1);
++ sizeof(search_key_up), sizeof(search_key_up), 1);
+ break;
+
+ default:
+diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
+index d60cd4379e866a..858ac2ab46bd97 100644
+--- a/drivers/hid/hid-logitech-hidpp.c
++++ b/drivers/hid/hid-logitech-hidpp.c
+@@ -3691,7 +3691,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp,
+ memcpy(&consumer_report[1], &data[3], 4);
+ /* We are called from atomic context */
+ hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
+- consumer_report, 5, 1);
++ consumer_report, sizeof(consumer_report), 5, 1);
+
+ return 1;
+ }
+diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
+index fcf9a806f109a5..760f9db44c9e32 100644
+--- a/drivers/hid/hid-multitouch.c
++++ b/drivers/hid/hid-multitouch.c
+@@ -500,7 +500,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
+ }
+
+ ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
+- size, 0);
++ size, size, 0);
+ if (ret)
+ dev_warn(&hdev->dev, "failed to report feature\n");
+ }
+diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c
+index e44d79dff8de63..8db054280afbcd 100644
+--- a/drivers/hid/hid-primax.c
++++ b/drivers/hid/hid-primax.c
+@@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report,
+ data[0] |= (1 << (data[idx] - 0xE0));
+ data[idx] = 0;
+ }
+- hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
++ hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0);
+ return 1;
+
+ default: /* unknown report */
+diff --git a/drivers/hid/hid-vivaldi-common.c b/drivers/hid/hid-vivaldi-common.c
+index bf734055d4b69d..b12bb5cc091aa3 100644
+--- a/drivers/hid/hid-vivaldi-common.c
++++ b/drivers/hid/hid-vivaldi-common.c
+@@ -85,7 +85,7 @@ void vivaldi_feature_mapping(struct hid_device *hdev,
+ }
+
+ ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data,
+- report_len, 0);
++ report_len, report_len, 0);
+ if (ret) {
+ dev_warn(&hdev->dev, "failed to report feature %d\n",
+ field->report->id);
+diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
+index 1b1112772777ca..ffcf65dcf71347 100644
+--- a/drivers/hid/wacom_sys.c
++++ b/drivers/hid/wacom_sys.c
+@@ -74,7 +74,7 @@ static void wacom_wac_queue_flush(struct hid_device *hdev,
+ int err;
+
+ size = kfifo_out(fifo, buf, sizeof(buf));
+- err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false);
++ err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, size, false);
+ if (err) {
+ hid_warn(hdev, "%s: unable to flush event due to error %d\n",
+ __func__, err);
+@@ -319,7 +319,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
+ data, n, WAC_CMD_RETRIES);
+ if (ret == n && features->type == HID_GENERIC) {
+ ret = hid_report_raw_event(hdev,
+- HID_FEATURE_REPORT, data, n, 0);
++ HID_FEATURE_REPORT, data, n, n, 0);
+ } else if (ret == 2 && features->type != HID_GENERIC) {
+ features->touch_max = data[1];
+ } else {
+@@ -380,7 +380,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
+ data, n, WAC_CMD_RETRIES);
+ if (ret == n) {
+ ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT,
+- data, n, 0);
++ data, n, n, 0);
+ } else {
+ hid_warn(hdev, "%s: could not retrieve sensor offsets\n",
+ __func__);
+diff --git a/drivers/staging/greybus/hid.c b/drivers/staging/greybus/hid.c
+index 63c77a3df59111..afa78c96ede898 100644
+--- a/drivers/staging/greybus/hid.c
++++ b/drivers/staging/greybus/hid.c
+@@ -201,7 +201,7 @@ static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
+ * we just need to setup the input fields, so using
+ * hid_report_raw_event is safe.
+ */
+- hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
++ hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, ghid->bufsize, size, 1);
+ }
+
+ static void gb_hid_init_reports(struct gb_hid *ghid)
+diff --git a/include/linux/hid.h b/include/linux/hid.h
+index bef017d6b44042..fdd401e4ebde3d 100644
+--- a/include/linux/hid.h
++++ b/include/linux/hid.h
+@@ -1213,8 +1213,8 @@ static inline u32 hid_report_len(struct hid_report *report)
+ return DIV_ROUND_UP(report->size, 8) + (report->id > 0);
+ }
+
+-int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+- int interrupt);
++int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
++ size_t bufsize, u32 size, int interrupt);
+
+ /* HID quirks API */
+ unsigned long hid_lookup_quirk(const struct hid_device *hdev);
+diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
+index 6a47223e646006..aa87513acbcd24 100644
+--- a/include/linux/hid_bpf.h
++++ b/include/linux/hid_bpf.h
+@@ -72,8 +72,8 @@ struct hid_ops {
+ int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len,
+ u64 source, bool from_bpf);
+ int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type,
+- u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
+- bool lock_already_taken);
++ u8 *data, size_t bufsize, u32 size, int interrupt, u64 source,
++ bool from_bpf, bool lock_already_taken);
+ struct module *owner;
+ const struct bus_type *bus_type;
+ };
+@@ -200,7 +200,8 @@ struct hid_bpf {
+
+ #ifdef CONFIG_HID_BPF
+ u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
+- u32 *size, int interrupt, u64 source, bool from_bpf);
++ size_t *buf_size, u32 *size, int interrupt, u64 source,
++ bool from_bpf);
+ int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf,
+ u32 size, enum hid_report_type rtype,
+@@ -215,8 +216,11 @@ int hid_bpf_device_init(struct hid_device *hid);
+ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size);
+ #else /* CONFIG_HID_BPF */
+ static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
+- u8 *data, u32 *size, int interrupt,
+- u64 source, bool from_bpf) { return data; }
++ u8 *data, size_t *buf_size, u32 *size,
++ int interrupt, u64 source, bool from_bpf)
++{
++ return data;
++}
+ static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+ unsigned char reportnum, u8 *buf,
+ u32 size, enum hid_report_type rtype,
+--
+2.53.0
+
--- /dev/null
+From 9d595a2730135481b82f345fe859a5e2f8483826 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 17:29:51 +0800
+Subject: inet: frags: add inet_frag_queue_flush()
+
+From: Jakub Kicinski <kuba@kernel.org>
+
+[ Upstream commit 1231eec6994be29d6bb5c303dfa54731ed9fc0e6 ]
+
+Instead of exporting inet_frag_rbtree_purge() which requires that
+caller takes care of memory accounting, add a new helper. We will
+need to call it from a few places in the next patch.
+
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20251207010942.1672972-3-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Rajani Kantha <681739313@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/inet_frag.h | 5 ++---
+ net/ipv4/inet_fragment.c | 15 ++++++++++++---
+ net/ipv4/ip_fragment.c | 6 +-----
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h
+index 5af6eb14c5db15..94edc0e130d2c4 100644
+--- a/include/net/inet_frag.h
++++ b/include/net/inet_frag.h
+@@ -141,9 +141,8 @@ void inet_frag_kill(struct inet_frag_queue *q);
+ void inet_frag_destroy(struct inet_frag_queue *q);
+ struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key);
+
+-/* Free all skbs in the queue; return the sum of their truesizes. */
+-unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+- enum skb_drop_reason reason);
++void inet_frag_queue_flush(struct inet_frag_queue *q,
++ enum skb_drop_reason reason);
+
+ static inline void inet_frag_put(struct inet_frag_queue *q)
+ {
+diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c
+index d179a2c8422276..70640906337757 100644
+--- a/net/ipv4/inet_fragment.c
++++ b/net/ipv4/inet_fragment.c
+@@ -264,8 +264,8 @@ static void inet_frag_destroy_rcu(struct rcu_head *head)
+ kmem_cache_free(f->frags_cachep, q);
+ }
+
+-unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+- enum skb_drop_reason reason)
++static unsigned int
++inet_frag_rbtree_purge(struct rb_root *root, enum skb_drop_reason reason)
+ {
+ struct rb_node *p = rb_first(root);
+ unsigned int sum = 0;
+@@ -285,7 +285,16 @@ unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+ }
+ return sum;
+ }
+-EXPORT_SYMBOL(inet_frag_rbtree_purge);
++
++void inet_frag_queue_flush(struct inet_frag_queue *q,
++ enum skb_drop_reason reason)
++{
++ unsigned int sum;
++
++ sum = inet_frag_rbtree_purge(&q->rb_fragments, reason);
++ sub_frag_mem_limit(q->fqdir, sum);
++}
++EXPORT_SYMBOL(inet_frag_queue_flush);
+
+ void inet_frag_destroy(struct inet_frag_queue *q)
+ {
+diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
+index 183856b0b74094..eb5f6060b85d52 100644
+--- a/net/ipv4/ip_fragment.c
++++ b/net/ipv4/ip_fragment.c
+@@ -253,16 +253,12 @@ static int ip_frag_too_far(struct ipq *qp)
+
+ static int ip_frag_reinit(struct ipq *qp)
+ {
+- unsigned int sum_truesize = 0;
+-
+ if (!mod_timer(&qp->q.timer, jiffies + qp->q.fqdir->timeout)) {
+ refcount_inc(&qp->q.refcnt);
+ return -ETIMEDOUT;
+ }
+
+- sum_truesize = inet_frag_rbtree_purge(&qp->q.rb_fragments,
+- SKB_DROP_REASON_FRAG_TOO_FAR);
+- sub_frag_mem_limit(qp->q.fqdir, sum_truesize);
++ inet_frag_queue_flush(&qp->q, SKB_DROP_REASON_FRAG_TOO_FAR);
+
+ qp->q.flags = 0;
+ qp->q.len = 0;
+--
+2.53.0
+
--- /dev/null
+From 4d76798300d3b111bdaa22eccdfe650a382b9496 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 17:29:52 +0800
+Subject: inet: frags: flush pending skbs in fqdir_pre_exit()
+
+From: Jakub Kicinski <kuba@kernel.org>
+
+[ Upstream commit 006a5035b495dec008805df249f92c22c89c3d2e ]
+
+We have been seeing occasional deadlocks on pernet_ops_rwsem since
+September in NIPA. The stuck task was usually modprobe (often loading
+a driver like ipvlan), trying to take the lock as a Writer.
+lockdep does not track readers for rwsems so the read wasn't obvious
+from the reports.
+
+On closer inspection the Reader holding the lock was conntrack looping
+forever in nf_conntrack_cleanup_net_list(). Based on past experience
+with occasional NIPA crashes I looked thru the tests which run before
+the crash and noticed that the crash follows ip_defrag.sh. An immediate
+red flag. Scouring thru (de)fragmentation queues reveals skbs sitting
+around, holding conntrack references.
+
+The problem is that since conntrack depends on nf_defrag_ipv6,
+nf_defrag_ipv6 will load first. Since nf_defrag_ipv6 loads first its
+netns exit hooks run _after_ conntrack's netns exit hook.
+
+Flush all fragment queue SKBs during fqdir_pre_exit() to release
+conntrack references before conntrack cleanup runs. Also flush
+the queues in timer expiry handlers when they discover fqdir->dead
+is set, in case packet sneaks in while we're running the pre_exit
+flush.
+
+The commit under Fixes is not exactly the culprit, but I think
+previously the timer firing would eventually unblock the spinning
+conntrack.
+
+Fixes: d5dd88794a13 ("inet: fix various use-after-free in defrags units")
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20251207010942.1672972-4-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Rajani Kantha <681739313@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/inet_frag.h | 13 +------------
+ include/net/ipv6_frag.h | 9 ++++++---
+ net/ipv4/inet_fragment.c | 36 ++++++++++++++++++++++++++++++++++++
+ net/ipv4/ip_fragment.c | 12 +++++++-----
+ 4 files changed, 50 insertions(+), 20 deletions(-)
+
+diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h
+index 94edc0e130d2c4..fcabb34fff35de 100644
+--- a/include/net/inet_frag.h
++++ b/include/net/inet_frag.h
+@@ -123,18 +123,7 @@ void inet_frags_fini(struct inet_frags *);
+
+ int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net);
+
+-static inline void fqdir_pre_exit(struct fqdir *fqdir)
+-{
+- /* Prevent creation of new frags.
+- * Pairs with READ_ONCE() in inet_frag_find().
+- */
+- WRITE_ONCE(fqdir->high_thresh, 0);
+-
+- /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire()
+- * and ip6frag_expire_frag_queue().
+- */
+- WRITE_ONCE(fqdir->dead, true);
+-}
++void fqdir_pre_exit(struct fqdir *fqdir);
+ void fqdir_exit(struct fqdir *fqdir);
+
+ void inet_frag_kill(struct inet_frag_queue *q);
+diff --git a/include/net/ipv6_frag.h b/include/net/ipv6_frag.h
+index 7321ffe3a108c1..df61b98b521531 100644
+--- a/include/net/ipv6_frag.h
++++ b/include/net/ipv6_frag.h
+@@ -68,9 +68,6 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)
+ struct sk_buff *head;
+
+ rcu_read_lock();
+- /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */
+- if (READ_ONCE(fq->q.fqdir->dead))
+- goto out_rcu_unlock;
+ spin_lock(&fq->q.lock);
+
+ if (fq->q.flags & INET_FRAG_COMPLETE)
+@@ -79,6 +76,12 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)
+ fq->q.flags |= INET_FRAG_DROP;
+ inet_frag_kill(&fq->q);
+
++ /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */
++ if (READ_ONCE(fq->q.fqdir->dead)) {
++ inet_frag_queue_flush(&fq->q, 0);
++ goto out;
++ }
++
+ dev = dev_get_by_index_rcu(net, fq->iif);
+ if (!dev)
+ goto out;
+diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c
+index 70640906337757..f9cf20b21a0781 100644
+--- a/net/ipv4/inet_fragment.c
++++ b/net/ipv4/inet_fragment.c
+@@ -219,6 +219,41 @@ static int __init inet_frag_wq_init(void)
+
+ pure_initcall(inet_frag_wq_init);
+
++void fqdir_pre_exit(struct fqdir *fqdir)
++{
++ struct inet_frag_queue *fq;
++ struct rhashtable_iter hti;
++
++ /* Prevent creation of new frags.
++ * Pairs with READ_ONCE() in inet_frag_find().
++ */
++ WRITE_ONCE(fqdir->high_thresh, 0);
++
++ /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire()
++ * and ip6frag_expire_frag_queue().
++ */
++ WRITE_ONCE(fqdir->dead, true);
++
++ rhashtable_walk_enter(&fqdir->rhashtable, &hti);
++ rhashtable_walk_start(&hti);
++
++ while ((fq = rhashtable_walk_next(&hti))) {
++ if (IS_ERR(fq)) {
++ if (PTR_ERR(fq) != -EAGAIN)
++ break;
++ continue;
++ }
++ spin_lock_bh(&fq->lock);
++ if (!(fq->flags & INET_FRAG_COMPLETE))
++ inet_frag_queue_flush(fq, 0);
++ spin_unlock_bh(&fq->lock);
++ }
++
++ rhashtable_walk_stop(&hti);
++ rhashtable_walk_exit(&hti);
++}
++EXPORT_SYMBOL(fqdir_pre_exit);
++
+ void fqdir_exit(struct fqdir *fqdir)
+ {
+ INIT_WORK(&fqdir->destroy_work, fqdir_work_fn);
+@@ -291,6 +326,7 @@ void inet_frag_queue_flush(struct inet_frag_queue *q,
+ {
+ unsigned int sum;
+
++ reason = reason ?: SKB_DROP_REASON_FRAG_REASM_TIMEOUT;
+ sum = inet_frag_rbtree_purge(&q->rb_fragments, reason);
+ sub_frag_mem_limit(q->fqdir, sum);
+ }
+diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
+index eb5f6060b85d52..124c0d64d4204b 100644
+--- a/net/ipv4/ip_fragment.c
++++ b/net/ipv4/ip_fragment.c
+@@ -148,11 +148,6 @@ static void ip_expire(struct timer_list *t)
+ net = qp->q.fqdir->net;
+
+ rcu_read_lock();
+-
+- /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */
+- if (READ_ONCE(qp->q.fqdir->dead))
+- goto out_rcu_unlock;
+-
+ spin_lock(&qp->q.lock);
+
+ if (qp->q.flags & INET_FRAG_COMPLETE)
+@@ -160,6 +155,13 @@ static void ip_expire(struct timer_list *t)
+
+ qp->q.flags |= INET_FRAG_DROP;
+ ipq_kill(qp);
++
++ /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */
++ if (READ_ONCE(qp->q.fqdir->dead)) {
++ inet_frag_queue_flush(&qp->q, 0);
++ goto out;
++ }
++
+ __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS);
+ __IP_INC_STATS(net, IPSTATS_MIB_REASMTIMEOUT);
+
+--
+2.53.0
+
--- /dev/null
+From 696344ed3f941bb466477beb5ecb66d31998a47e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 20 Dec 2025 10:33:26 +0000
+Subject: media: rc: fix race between unregister and urb/irq callbacks
+
+From: Sean Young <sean@mess.org>
+
+[ Upstream commit dccc0c3ddf8f16071736f98a7d6dd46a2d43e037 ]
+
+Some rc device drivers have a race condition between rc_unregister_device()
+and irq or urb callbacks. This is because rc_unregister_device() does two
+things, it marks the device as unregistered so no new commands can be
+issued and then it calls rc_free_device(). This means the driver has no
+chance to cancel any pending urb callbacks or interrupts after the device
+has been marked as unregistered. Those callbacks may access struct rc_dev
+or its members (e.g. struct ir_raw_event_ctrl), which have been freed by
+rc_free_device().
+
+This change removes the implicit call to rc_free_device() from
+rc_unregister_device(). This means that device drivers can call
+rc_unregister_device() in their remove or disconnect function, then cancel
+all the urbs and interrupts before explicitly calling rc_free_device().
+
+Note this is an alternative fix for an issue found by Haotian Zhang, see
+the Closes: tags.
+
+Reported-by: Haotian Zhang <vulab@iscas.ac.cn>
+Closes: https://lore.kernel.org/linux-media/20251114101432.2566-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114101418.2548-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114101346.2530-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114090605.2413-1-vulab@iscas.ac.cn/
+Reviewed-by: Patrice Chotard <patrice.chotard@foss.st.com>
+Signed-off-by: Sean Young <sean@mess.org>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Stable-dep-of: 646ebdd31058 ("media: rc: ttusbir: fix inverted error logic")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/bridge/sil-sii8620.c | 1 +
+ drivers/hid/hid-picolcd_cir.c | 1 +
+ drivers/media/cec/core/cec-core.c | 2 +-
+ drivers/media/common/siano/smsir.c | 1 +
+ drivers/media/i2c/ir-kbd-i2c.c | 2 ++
+ drivers/media/pci/bt8xx/bttv-input.c | 3 ++-
+ drivers/media/pci/cx23885/cx23885-input.c | 1 +
+ drivers/media/pci/cx88/cx88-input.c | 3 ++-
+ drivers/media/pci/dm1105/dm1105.c | 1 +
+ drivers/media/pci/mantis/mantis_input.c | 1 +
+ drivers/media/pci/saa7134/saa7134-input.c | 1 +
+ drivers/media/pci/smipcie/smipcie-ir.c | 1 +
+ drivers/media/pci/ttpci/budget-ci.c | 1 +
+ drivers/media/rc/ati_remote.c | 6 +++---
+ drivers/media/rc/ene_ir.c | 2 +-
+ drivers/media/rc/fintek-cir.c | 3 ++-
+ drivers/media/rc/igorplugusb.c | 1 +
+ drivers/media/rc/iguanair.c | 1 +
+ drivers/media/rc/img-ir/img-ir-hw.c | 3 ++-
+ drivers/media/rc/img-ir/img-ir-raw.c | 3 ++-
+ drivers/media/rc/imon.c | 3 ++-
+ drivers/media/rc/ir-hix5hd2.c | 2 +-
+ drivers/media/rc/ir_toy.c | 1 +
+ drivers/media/rc/ite-cir.c | 2 +-
+ drivers/media/rc/mceusb.c | 1 +
+ drivers/media/rc/rc-ir-raw.c | 5 -----
+ drivers/media/rc/rc-loopback.c | 1 +
+ drivers/media/rc/rc-main.c | 6 +-----
+ drivers/media/rc/redrat3.c | 4 +++-
+ drivers/media/rc/st_rc.c | 2 +-
+ drivers/media/rc/streamzap.c | 7 ++++---
+ drivers/media/rc/sunxi-cir.c | 1 +
+ drivers/media/rc/ttusbir.c | 2 +-
+ drivers/media/rc/winbond-cir.c | 2 +-
+ drivers/media/rc/xbox_remote.c | 5 +++--
+ drivers/media/usb/au0828/au0828-input.c | 1 +
+ drivers/media/usb/dvb-usb-v2/dvb_usb_core.c | 1 +
+ drivers/media/usb/dvb-usb/dvb-usb-remote.c | 6 ++++--
+ drivers/media/usb/em28xx/em28xx-input.c | 1 +
+ drivers/staging/media/av7110/av7110_ir.c | 1 +
+ include/media/rc-core.h | 2 --
+ 41 files changed, 58 insertions(+), 36 deletions(-)
+
+diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
+index 26b8d137bce096..2baeb1c5301ead 100644
+--- a/drivers/gpu/drm/bridge/sil-sii8620.c
++++ b/drivers/gpu/drm/bridge/sil-sii8620.c
+@@ -2220,6 +2220,7 @@ static void sii8620_detach(struct drm_bridge *bridge)
+ return;
+
+ rc_unregister_device(ctx->rc_dev);
++ rc_free_device(ctx->rc_dev);
+ }
+
+ static int sii8620_is_packing_required(struct sii8620 *ctx,
+diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c
+index d6faa0e00f95ac..6d4c636e1c9f7e 100644
+--- a/drivers/hid/hid-picolcd_cir.c
++++ b/drivers/hid/hid-picolcd_cir.c
+@@ -134,5 +134,6 @@ void picolcd_exit_cir(struct picolcd_data *data)
+
+ data->rc_dev = NULL;
+ rc_unregister_device(rdev);
++ rc_free_device(rdev);
+ }
+
+diff --git a/drivers/media/cec/core/cec-core.c b/drivers/media/cec/core/cec-core.c
+index 865d86f34add0e..b3c0710eb5b18d 100644
+--- a/drivers/media/cec/core/cec-core.c
++++ b/drivers/media/cec/core/cec-core.c
+@@ -337,8 +337,8 @@ int cec_register_adapter(struct cec_adapter *adap,
+ res = cec_devnode_register(&adap->devnode, adap->owner);
+ if (res) {
+ #ifdef CONFIG_MEDIA_CEC_RC
+- /* Note: rc_unregister also calls rc_free */
+ rc_unregister_device(adap->rc);
++ rc_free_device(adap->rc);
+ adap->rc = NULL;
+ #endif
+ return res;
+diff --git a/drivers/media/common/siano/smsir.c b/drivers/media/common/siano/smsir.c
+index d85c78c104b990..5f4c0aa7a0d72a 100644
+--- a/drivers/media/common/siano/smsir.c
++++ b/drivers/media/common/siano/smsir.c
+@@ -92,6 +92,7 @@ int sms_ir_init(struct smscore_device_t *coredev)
+ void sms_ir_exit(struct smscore_device_t *coredev)
+ {
+ rc_unregister_device(coredev->ir.dev);
++ rc_free_device(coredev->ir.dev);
+
+ pr_debug("\n");
+ }
+diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c
+index 5588cdd7ec20de..60474531700433 100644
+--- a/drivers/media/i2c/ir-kbd-i2c.c
++++ b/drivers/media/i2c/ir-kbd-i2c.c
+@@ -355,6 +355,7 @@ static void ir_work(struct work_struct *work)
+ mutex_unlock(&ir->lock);
+ if (rc == -ENODEV) {
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ ir->rc = NULL;
+ return;
+ }
+@@ -972,6 +973,7 @@ static void ir_remove(struct i2c_client *client)
+ i2c_unregister_device(ir->tx_c);
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ }
+
+ static const struct i2c_device_id ir_kbd_id[] = {
+diff --git a/drivers/media/pci/bt8xx/bttv-input.c b/drivers/media/pci/bt8xx/bttv-input.c
+index 41226f1d0e5b64..d70e6282c48b2e 100644
+--- a/drivers/media/pci/bt8xx/bttv-input.c
++++ b/drivers/media/pci/bt8xx/bttv-input.c
+@@ -572,8 +572,9 @@ void bttv_input_fini(struct bttv *btv)
+ if (btv->remote == NULL)
+ return;
+
+- bttv_ir_stop(btv);
+ rc_unregister_device(btv->remote->dev);
++ bttv_ir_stop(btv);
++ rc_free_device(btv->remote->dev);
+ kfree(btv->remote);
+ btv->remote = NULL;
+ }
+diff --git a/drivers/media/pci/cx23885/cx23885-input.c b/drivers/media/pci/cx23885/cx23885-input.c
+index d2e84c6457e0ab..722329ef3fd2cc 100644
+--- a/drivers/media/pci/cx23885/cx23885-input.c
++++ b/drivers/media/pci/cx23885/cx23885-input.c
+@@ -402,6 +402,7 @@ void cx23885_input_fini(struct cx23885_dev *dev)
+ if (dev->kernel_ir == NULL)
+ return;
+ rc_unregister_device(dev->kernel_ir->rc);
++ rc_free_device(dev->kernel_ir->rc);
+ kfree(dev->kernel_ir->phys);
+ kfree(dev->kernel_ir->name);
+ kfree(dev->kernel_ir);
+diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c
+index a04a1d33fadb1b..74a8769dd6c79c 100644
+--- a/drivers/media/pci/cx88/cx88-input.c
++++ b/drivers/media/pci/cx88/cx88-input.c
+@@ -510,8 +510,9 @@ int cx88_ir_fini(struct cx88_core *core)
+ if (!ir)
+ return 0;
+
+- cx88_ir_stop(core);
+ rc_unregister_device(ir->dev);
++ cx88_ir_stop(core);
++ rc_free_device(ir->dev);
+ kfree(ir);
+
+ /* done */
+diff --git a/drivers/media/pci/dm1105/dm1105.c b/drivers/media/pci/dm1105/dm1105.c
+index 9e9c7c071accce..e1185aa669f480 100644
+--- a/drivers/media/pci/dm1105/dm1105.c
++++ b/drivers/media/pci/dm1105/dm1105.c
+@@ -763,6 +763,7 @@ static int dm1105_ir_init(struct dm1105_dev *dm1105)
+ static void dm1105_ir_exit(struct dm1105_dev *dm1105)
+ {
+ rc_unregister_device(dm1105->ir.dev);
++ rc_free_device(dm1105->ir.dev);
+ }
+
+ static int dm1105_hw_init(struct dm1105_dev *dev)
+diff --git a/drivers/media/pci/mantis/mantis_input.c b/drivers/media/pci/mantis/mantis_input.c
+index 34c0d979240fda..edb4cacf55d229 100644
+--- a/drivers/media/pci/mantis/mantis_input.c
++++ b/drivers/media/pci/mantis/mantis_input.c
+@@ -72,5 +72,6 @@ EXPORT_SYMBOL_GPL(mantis_input_init);
+ void mantis_input_exit(struct mantis_pci *mantis)
+ {
+ rc_unregister_device(mantis->rc);
++ rc_free_device(mantis->rc);
+ }
+ EXPORT_SYMBOL_GPL(mantis_input_exit);
+diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c
+index 8610eb473b39e1..8a0f26d94d1de8 100644
+--- a/drivers/media/pci/saa7134/saa7134-input.c
++++ b/drivers/media/pci/saa7134/saa7134-input.c
+@@ -834,6 +834,7 @@ void saa7134_input_fini(struct saa7134_dev *dev)
+ return;
+
+ rc_unregister_device(dev->remote->dev);
++ rc_free_device(dev->remote->dev);
+ kfree(dev->remote);
+ dev->remote = NULL;
+ }
+diff --git a/drivers/media/pci/smipcie/smipcie-ir.c b/drivers/media/pci/smipcie/smipcie-ir.c
+index c0604d9c70119a..0bbe4fa2d5a84e 100644
+--- a/drivers/media/pci/smipcie/smipcie-ir.c
++++ b/drivers/media/pci/smipcie/smipcie-ir.c
+@@ -181,5 +181,6 @@ void smi_ir_exit(struct smi_dev *dev)
+
+ rc_unregister_device(rc_dev);
+ smi_ir_stop(ir);
++ rc_free_device(rc_dev);
+ ir->rc_dev = NULL;
+ }
+diff --git a/drivers/media/pci/ttpci/budget-ci.c b/drivers/media/pci/ttpci/budget-ci.c
+index 33f08adf4feb10..16973ac8e6a920 100644
+--- a/drivers/media/pci/ttpci/budget-ci.c
++++ b/drivers/media/pci/ttpci/budget-ci.c
+@@ -249,6 +249,7 @@ static void msp430_ir_deinit(struct budget_ci *budget_ci)
+ cancel_work_sync(&budget_ci->ir.msp430_irq_bh_work);
+
+ rc_unregister_device(budget_ci->ir.dev);
++ rc_free_device(budget_ci->ir.dev);
+ }
+
+ static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address)
+diff --git a/drivers/media/rc/ati_remote.c b/drivers/media/rc/ati_remote.c
+index d7721e60776edd..46d1844f5c9873 100644
+--- a/drivers/media/rc/ati_remote.c
++++ b/drivers/media/rc/ati_remote.c
+@@ -921,7 +921,6 @@ static int ati_remote_probe(struct usb_interface *interface,
+ input_free_device(input_dev);
+ exit_unregister_device:
+ rc_unregister_device(rc_dev);
+- rc_dev = NULL;
+ exit_kill_urbs:
+ usb_kill_urb(ati_remote->irq_urb);
+ usb_kill_urb(ati_remote->out_urb);
+@@ -941,18 +940,19 @@ static void ati_remote_disconnect(struct usb_interface *interface)
+ struct ati_remote *ati_remote;
+
+ ati_remote = usb_get_intfdata(interface);
+- usb_set_intfdata(interface, NULL);
+ if (!ati_remote) {
+ dev_warn(&interface->dev, "%s - null device?\n", __func__);
+ return;
+ }
+
++ rc_unregister_device(ati_remote->rdev);
++ usb_set_intfdata(interface, NULL);
+ usb_kill_urb(ati_remote->irq_urb);
+ usb_kill_urb(ati_remote->out_urb);
+ if (ati_remote->idev)
+ input_unregister_device(ati_remote->idev);
+- rc_unregister_device(ati_remote->rdev);
+ ati_remote_free_buffers(ati_remote);
++ rc_free_device(ati_remote->rdev);
+ kfree(ati_remote);
+ }
+
+diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c
+index 67722e2e47ff78..3fd51a41c3b2b6 100644
+--- a/drivers/media/rc/ene_ir.c
++++ b/drivers/media/rc/ene_ir.c
+@@ -1090,7 +1090,6 @@ static int ene_probe(struct pnp_dev *pnp_dev, const struct pnp_device_id *id)
+ release_region(dev->hw_io, ENE_IO_SIZE);
+ exit_unregister_device:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ exit_free_dev_rdev:
+ rc_free_device(rdev);
+ kfree(dev);
+@@ -1110,6 +1109,7 @@ static void ene_remove(struct pnp_dev *pnp_dev)
+ ene_rx_restore_hw_buffer(dev);
+ spin_unlock_irqrestore(&dev->hw_lock, flags);
+
++ rc_free_device(dev->rdev);
+ free_irq(dev->irq, dev);
+ release_region(dev->hw_io, ENE_IO_SIZE);
+ kfree(dev);
+diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c
+index 3fb0968efd57d3..9b789097cdd4c3 100644
+--- a/drivers/media/rc/fintek-cir.c
++++ b/drivers/media/rc/fintek-cir.c
+@@ -568,6 +568,7 @@ static void fintek_remove(struct pnp_dev *pdev)
+ struct fintek_dev *fintek = pnp_get_drvdata(pdev);
+ unsigned long flags;
+
++ rc_unregister_device(fintek->rdev);
+ spin_lock_irqsave(&fintek->fintek_lock, flags);
+ /* disable CIR */
+ fintek_disable_cir(fintek);
+@@ -580,7 +581,7 @@ static void fintek_remove(struct pnp_dev *pdev)
+ free_irq(fintek->cir_irq, fintek);
+ release_region(fintek->cir_addr, fintek->cir_port_len);
+
+- rc_unregister_device(fintek->rdev);
++ rc_free_device(fintek->rdev);
+
+ kfree(fintek);
+ }
+diff --git a/drivers/media/rc/igorplugusb.c b/drivers/media/rc/igorplugusb.c
+index f3616607d4f52b..8bf059b9a31b45 100644
+--- a/drivers/media/rc/igorplugusb.c
++++ b/drivers/media/rc/igorplugusb.c
+@@ -247,6 +247,7 @@ static void igorplugusb_disconnect(struct usb_interface *intf)
+ usb_set_intfdata(intf, NULL);
+ usb_unpoison_urb(ir->urb);
+ usb_free_urb(ir->urb);
++ rc_free_device(ir->rc);
+ kfree(ir->buf_in);
+ kfree(ir->request);
+ }
+diff --git a/drivers/media/rc/iguanair.c b/drivers/media/rc/iguanair.c
+index 8af94246e5916e..7bd6dd7254157a 100644
+--- a/drivers/media/rc/iguanair.c
++++ b/drivers/media/rc/iguanair.c
+@@ -500,6 +500,7 @@ static void iguanair_disconnect(struct usb_interface *intf)
+ usb_set_intfdata(intf, NULL);
+ usb_kill_urb(ir->urb_in);
+ usb_kill_urb(ir->urb_out);
++ rc_free_device(ir->rc);
+ usb_free_urb(ir->urb_in);
+ usb_free_urb(ir->urb_out);
+ usb_free_coherent(ir->udev, MAX_IN_PACKET, ir->buf_in, ir->dma_in);
+diff --git a/drivers/media/rc/img-ir/img-ir-hw.c b/drivers/media/rc/img-ir/img-ir-hw.c
+index 5da7479c1793b1..07f41372976eaf 100644
+--- a/drivers/media/rc/img-ir/img-ir-hw.c
++++ b/drivers/media/rc/img-ir/img-ir-hw.c
+@@ -1117,9 +1117,10 @@ void img_ir_remove_hw(struct img_ir_priv *priv)
+ struct rc_dev *rdev = hw->rdev;
+ if (!rdev)
+ return;
++ rc_unregister_device(rdev);
+ img_ir_set_decoder(priv, NULL, 0);
+ hw->rdev = NULL;
+- rc_unregister_device(rdev);
++ rc_free_device(rdev);
+ #ifdef CONFIG_COMMON_CLK
+ if (!IS_ERR(priv->clk))
+ clk_notifier_unregister(priv->clk, &hw->clk_nb);
+diff --git a/drivers/media/rc/img-ir/img-ir-raw.c b/drivers/media/rc/img-ir/img-ir-raw.c
+index 8b0bdd9603b3c5..533d40dae54224 100644
+--- a/drivers/media/rc/img-ir/img-ir-raw.c
++++ b/drivers/media/rc/img-ir/img-ir-raw.c
+@@ -136,6 +136,7 @@ void img_ir_remove_raw(struct img_ir_priv *priv)
+ if (!rdev)
+ return;
+
++ rc_unregister_device(rdev);
+ /* switch off and disable raw (edge) interrupts */
+ spin_lock_irq(&priv->lock);
+ raw->rdev = NULL;
+@@ -145,7 +146,7 @@ void img_ir_remove_raw(struct img_ir_priv *priv)
+ img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
+ spin_unlock_irq(&priv->lock);
+
+- rc_unregister_device(rdev);
++ rc_free_device(rdev);
+
+ del_timer_sync(&raw->timer);
+ }
+diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c
+index ddb1304cb77b82..cb9bd5a6ff54f7 100644
+--- a/drivers/media/rc/imon.c
++++ b/drivers/media/rc/imon.c
+@@ -2546,9 +2546,10 @@ static void imon_disconnect(struct usb_interface *interface)
+
+ if (ifnum == 0) {
+ ictx->dev_present_intf0 = false;
++ rc_unregister_device(ictx->rdev);
+ usb_kill_urb(ictx->rx_urb_intf0);
+ input_unregister_device(ictx->idev);
+- rc_unregister_device(ictx->rdev);
++ rc_free_device(ictx->rdev);
+ if (ictx->display_supported) {
+ if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
+ usb_deregister_dev(interface, &imon_lcd_class);
+diff --git a/drivers/media/rc/ir-hix5hd2.c b/drivers/media/rc/ir-hix5hd2.c
+index de5bb9a08ea4c0..1604679fa2c807 100644
+--- a/drivers/media/rc/ir-hix5hd2.c
++++ b/drivers/media/rc/ir-hix5hd2.c
+@@ -331,7 +331,6 @@ static int hix5hd2_ir_probe(struct platform_device *pdev)
+
+ regerr:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ clkerr:
+ clk_disable_unprepare(priv->clock);
+ err:
+@@ -346,6 +345,7 @@ static void hix5hd2_ir_remove(struct platform_device *pdev)
+
+ clk_disable_unprepare(priv->clock);
+ rc_unregister_device(priv->rdev);
++ rc_free_device(priv->rdev);
+ }
+
+ #ifdef CONFIG_PM_SLEEP
+diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c
+index 533faa11751744..e79de56997a426 100644
+--- a/drivers/media/rc/ir_toy.c
++++ b/drivers/media/rc/ir_toy.c
+@@ -536,6 +536,7 @@ static void irtoy_disconnect(struct usb_interface *intf)
+ usb_free_urb(ir->urb_out);
+ usb_kill_urb(ir->urb_in);
+ usb_free_urb(ir->urb_in);
++ rc_free_device(ir->rc);
+ kfree(ir->in);
+ kfree(ir->out);
+ kfree(ir);
+diff --git a/drivers/media/rc/ite-cir.c b/drivers/media/rc/ite-cir.c
+index 2bacecb022623e..23afbafb557488 100644
+--- a/drivers/media/rc/ite-cir.c
++++ b/drivers/media/rc/ite-cir.c
+@@ -1414,7 +1414,6 @@ static int ite_probe(struct pnp_dev *pdev, const struct pnp_device_id
+ release_region(itdev->cir_addr, itdev->params->io_region_size);
+ exit_unregister_device:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ exit_free_dev_rdev:
+ rc_free_device(rdev);
+ kfree(itdev);
+@@ -1439,6 +1438,7 @@ static void ite_remove(struct pnp_dev *pdev)
+ release_region(dev->cir_addr, dev->params->io_region_size);
+
+ rc_unregister_device(dev->rdev);
++ rc_free_device(dev->rdev);
+
+ kfree(dev);
+ }
+diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c
+index cd7af4d88b7f77..bf93b94d337fea 100644
+--- a/drivers/media/rc/mceusb.c
++++ b/drivers/media/rc/mceusb.c
+@@ -1851,6 +1851,7 @@ static void mceusb_dev_disconnect(struct usb_interface *intf)
+ usb_free_urb(ir->urb_in);
+ usb_free_coherent(dev, ir->len_in, ir->buf_in, ir->dma_in);
+ usb_put_dev(dev);
++ rc_free_device(ir->rc);
+
+ kfree(ir);
+ }
+diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c
+index 16e33d7eaaa2d8..dd70f8ad526642 100644
+--- a/drivers/media/rc/rc-ir-raw.c
++++ b/drivers/media/rc/rc-ir-raw.c
+@@ -647,9 +647,6 @@ int ir_raw_event_register(struct rc_dev *dev)
+
+ void ir_raw_event_free(struct rc_dev *dev)
+ {
+- if (!dev)
+- return;
+-
+ kfree(dev->raw);
+ dev->raw = NULL;
+ }
+@@ -673,8 +670,6 @@ void ir_raw_event_unregister(struct rc_dev *dev)
+
+ lirc_bpf_free(dev);
+
+- ir_raw_event_free(dev);
+-
+ /*
+ * A user can be calling bpf(BPF_PROG_{QUERY|ATTACH|DETACH}), so
+ * ensure that the raw member is null on unlock; this is how
+diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c
+index 8288366f891fc9..a108b057b5fd56 100644
+--- a/drivers/media/rc/rc-loopback.c
++++ b/drivers/media/rc/rc-loopback.c
+@@ -263,6 +263,7 @@ static int __init loop_init(void)
+ static void __exit loop_exit(void)
+ {
+ rc_unregister_device(loopdev.dev);
++ rc_free_device(loopdev.dev);
+ }
+
+ module_init(loop_init);
+diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
+index a4c539b17cf344..a4c0ec06ee6aa3 100644
+--- a/drivers/media/rc/rc-main.c
++++ b/drivers/media/rc/rc-main.c
+@@ -1611,6 +1611,7 @@ static void rc_dev_release(struct device *device)
+ {
+ struct rc_dev *dev = to_rc_dev(device);
+
++ ir_raw_event_free(dev);
+ kfree(dev);
+ }
+
+@@ -1773,7 +1774,6 @@ struct rc_dev *devm_rc_allocate_device(struct device *dev,
+ }
+
+ rc->dev.parent = dev;
+- rc->managed_alloc = true;
+ *dr = rc;
+ devres_add(dev, dr);
+
+@@ -2042,11 +2042,7 @@ void rc_unregister_device(struct rc_dev *dev)
+ device_del(&dev->dev);
+
+ ida_free(&rc_ida, dev->minor);
+-
+- if (!dev->managed_alloc)
+- rc_free_device(dev);
+ }
+-
+ EXPORT_SYMBOL_GPL(rc_unregister_device);
+
+ /*
+diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c
+index a49173f54a4d0e..b8289327f6a206 100644
+--- a/drivers/media/rc/redrat3.c
++++ b/drivers/media/rc/redrat3.c
+@@ -1133,11 +1133,13 @@ static void redrat3_dev_disconnect(struct usb_interface *intf)
+ {
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct redrat3_dev *rr3 = usb_get_intfdata(intf);
++ struct rc_dev *rc = rr3->rc;
+
+ usb_set_intfdata(intf, NULL);
+- rc_unregister_device(rr3->rc);
++ rc_unregister_device(rc);
+ led_classdev_unregister(&rr3->led);
+ redrat3_delete(rr3, udev);
++ rc_free_device(rc);
+ }
+
+ static int redrat3_dev_suspend(struct usb_interface *intf, pm_message_t message)
+diff --git a/drivers/media/rc/st_rc.c b/drivers/media/rc/st_rc.c
+index fd2f056f287b25..79aad3d7f69f0d 100644
+--- a/drivers/media/rc/st_rc.c
++++ b/drivers/media/rc/st_rc.c
+@@ -203,6 +203,7 @@ static void st_rc_remove(struct platform_device *pdev)
+ device_init_wakeup(&pdev->dev, false);
+ clk_disable_unprepare(rc_dev->sys_clock);
+ rc_unregister_device(rc_dev->rdev);
++ rc_free_device(rc_dev->rdev);
+ }
+
+ static int st_rc_open(struct rc_dev *rdev)
+@@ -334,7 +335,6 @@ static int st_rc_probe(struct platform_device *pdev)
+ return ret;
+ rcerr:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ clkerr:
+ clk_disable_unprepare(rc_dev->sys_clock);
+ err:
+diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c
+index 8e9b156e430022..8c85b9f30a3a96 100644
+--- a/drivers/media/rc/streamzap.c
++++ b/drivers/media/rc/streamzap.c
+@@ -392,15 +392,16 @@ static void streamzap_disconnect(struct usb_interface *interface)
+ struct streamzap_ir *sz = usb_get_intfdata(interface);
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+
+- usb_set_intfdata(interface, NULL);
+-
+ if (!sz)
+ return;
+
+- usb_kill_urb(sz->urb_in);
+ rc_unregister_device(sz->rdev);
++ usb_set_intfdata(interface, NULL);
++
++ usb_kill_urb(sz->urb_in);
+ usb_free_urb(sz->urb_in);
+ usb_free_coherent(usbdev, sz->buf_in_len, sz->buf_in, sz->dma_in);
++ rc_free_device(sz->rdev);
+
+ kfree(sz);
+ }
+diff --git a/drivers/media/rc/sunxi-cir.c b/drivers/media/rc/sunxi-cir.c
+index b49df8355e6b39..448d453cfda934 100644
+--- a/drivers/media/rc/sunxi-cir.c
++++ b/drivers/media/rc/sunxi-cir.c
+@@ -371,6 +371,7 @@ static void sunxi_ir_remove(struct platform_device *pdev)
+ struct sunxi_ir *ir = platform_get_drvdata(pdev);
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ sunxi_ir_hw_exit(&pdev->dev);
+ }
+
+diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c
+index dde446a95eaa93..a670d4b008cb0d 100644
+--- a/drivers/media/rc/ttusbir.c
++++ b/drivers/media/rc/ttusbir.c
+@@ -336,7 +336,6 @@ static int ttusbir_probe(struct usb_interface *intf,
+ return 0;
+ out3:
+ rc_unregister_device(rc);
+- rc = NULL;
+ out2:
+ led_classdev_unregister(&tt->led);
+ out:
+@@ -378,6 +377,7 @@ static void ttusbir_disconnect(struct usb_interface *intf)
+ usb_kill_urb(tt->bulk_urb);
+ usb_free_urb(tt->bulk_urb);
+ kfree(tt->bulk_buffer);
++ rc_free_device(tt->rc);
+ usb_set_intfdata(intf, NULL);
+ kfree(tt);
+ }
+diff --git a/drivers/media/rc/winbond-cir.c b/drivers/media/rc/winbond-cir.c
+index 25884a79985c8a..14d8b58e283980 100644
+--- a/drivers/media/rc/winbond-cir.c
++++ b/drivers/media/rc/winbond-cir.c
+@@ -1132,7 +1132,6 @@ wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+ release_region(data->wbase, WAKEUP_IOMEM_LEN);
+ exit_unregister_device:
+ rc_unregister_device(data->dev);
+- data->dev = NULL;
+ exit_free_rc:
+ rc_free_device(data->dev);
+ exit_unregister_led:
+@@ -1163,6 +1162,7 @@ wbcir_remove(struct pnp_dev *device)
+ wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+ rc_unregister_device(data->dev);
++ rc_free_device(data->dev);
+
+ led_classdev_unregister(&data->led);
+
+diff --git a/drivers/media/rc/xbox_remote.c b/drivers/media/rc/xbox_remote.c
+index 0c9c855ced729c..80b7c247932a8f 100644
+--- a/drivers/media/rc/xbox_remote.c
++++ b/drivers/media/rc/xbox_remote.c
+@@ -283,14 +283,15 @@ static void xbox_remote_disconnect(struct usb_interface *interface)
+ struct xbox_remote *xbox_remote;
+
+ xbox_remote = usb_get_intfdata(interface);
+- usb_set_intfdata(interface, NULL);
+ if (!xbox_remote) {
+ dev_warn(&interface->dev, "%s - null device?\n", __func__);
+ return;
+ }
+
+- usb_kill_urb(xbox_remote->irq_urb);
+ rc_unregister_device(xbox_remote->rdev);
++ usb_set_intfdata(interface, NULL);
++ usb_kill_urb(xbox_remote->irq_urb);
++ rc_free_device(xbox_remote->rdev);
+ usb_free_urb(xbox_remote->irq_urb);
+ kfree(xbox_remote->inbuf);
+ kfree(xbox_remote);
+diff --git a/drivers/media/usb/au0828/au0828-input.c b/drivers/media/usb/au0828/au0828-input.c
+index 3d3368202cd018..283ad2c6288cd5 100644
+--- a/drivers/media/usb/au0828/au0828-input.c
++++ b/drivers/media/usb/au0828/au0828-input.c
+@@ -357,6 +357,7 @@ void au0828_rc_unregister(struct au0828_dev *dev)
+ return;
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+
+ /* done */
+ kfree(ir);
+diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
+index f1c79f351ec8de..17e8961179d14b 100644
+--- a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
++++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
+@@ -187,6 +187,7 @@ static int dvb_usbv2_remote_exit(struct dvb_usb_device *d)
+ if (d->rc_dev) {
+ cancel_delayed_work_sync(&d->rc_query_work);
+ rc_unregister_device(d->rc_dev);
++ rc_free_device(d->rc_dev);
+ d->rc_dev = NULL;
+ }
+
+diff --git a/drivers/media/usb/dvb-usb/dvb-usb-remote.c b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
+index 65e2c9e2cdc99f..6dc11718dfb985 100644
+--- a/drivers/media/usb/dvb-usb/dvb-usb-remote.c
++++ b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
+@@ -347,10 +347,12 @@ int dvb_usb_remote_exit(struct dvb_usb_device *d)
+ {
+ if (d->state & DVB_USB_STATE_REMOTE) {
+ cancel_delayed_work_sync(&d->rc_query_work);
+- if (d->props.rc.mode == DVB_RC_LEGACY)
++ if (d->props.rc.mode == DVB_RC_LEGACY) {
+ input_unregister_device(d->input_dev);
+- else
++ } else {
+ rc_unregister_device(d->rc_dev);
++ rc_free_device(d->rc_dev);
++ }
+ }
+ d->state &= ~DVB_USB_STATE_REMOTE;
+ return 0;
+diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c
+index 5f3b00869bdbc9..26f333b5be7325 100644
+--- a/drivers/media/usb/em28xx/em28xx-input.c
++++ b/drivers/media/usb/em28xx/em28xx-input.c
+@@ -853,6 +853,7 @@ static int em28xx_ir_fini(struct em28xx *dev)
+ goto ref_put;
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+
+ kfree(ir->i2c_client);
+
+diff --git a/drivers/staging/media/av7110/av7110_ir.c b/drivers/staging/media/av7110/av7110_ir.c
+index 68b3979ba5f20c..fdae467fd7ab81 100644
+--- a/drivers/staging/media/av7110/av7110_ir.c
++++ b/drivers/staging/media/av7110/av7110_ir.c
+@@ -151,6 +151,7 @@ int av7110_ir_init(struct av7110 *av7110)
+ void av7110_ir_exit(struct av7110 *av7110)
+ {
+ rc_unregister_device(av7110->ir.rcdev);
++ rc_free_device(av7110->ir.rcdev);
+ }
+
+ //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
+diff --git a/include/media/rc-core.h b/include/media/rc-core.h
+index d095908073ef9d..7a8511d0d4b4a4 100644
+--- a/include/media/rc-core.h
++++ b/include/media/rc-core.h
+@@ -81,7 +81,6 @@ struct lirc_fh {
+ /**
+ * struct rc_dev - represents a remote control device
+ * @dev: driver model's view of this device
+- * @managed_alloc: devm_rc_allocate_device was used to create rc_dev
+ * @sysfs_groups: sysfs attribute groups
+ * @device_name: name of the rc child device
+ * @input_phys: physical path to the input child device
+@@ -156,7 +155,6 @@ struct lirc_fh {
+ */
+ struct rc_dev {
+ struct device dev;
+- bool managed_alloc;
+ const struct attribute_group *sysfs_groups[5];
+ const char *device_name;
+ const char *input_phys;
+--
+2.53.0
+
--- /dev/null
+From 491f8165e6392cf906f3dc941f01c4afd1c84427 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Apr 2026 23:03:09 +0200
+Subject: media: rc: ttusbir: fix inverted error logic
+
+From: Oliver Neukum <oneukum@suse.com>
+
+[ Upstream commit 646ebdd3105809d84ed04aa9e92e47e89cc44502 ]
+
+We have to report ENOMEM if no buffer is allocated.
+Typo dropped a "!". Restore it.
+
+Fixes: 50acaad3d202 ("media: rc: ttusbir: respect DMA coherency rules")
+Cc: stable@vger.kernel.org
+Signed-off-by: Oliver Neukum <oneukum@suse.com>
+Signed-off-by: Sean Young <sean@mess.org>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/media/rc/ttusbir.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c
+index a670d4b008cb0d..3452b5aefd2848 100644
+--- a/drivers/media/rc/ttusbir.c
++++ b/drivers/media/rc/ttusbir.c
+@@ -191,7 +191,7 @@ static int ttusbir_probe(struct usb_interface *intf,
+ tt = kzalloc(sizeof(*tt), GFP_KERNEL);
+ buffer = kzalloc(5, GFP_KERNEL);
+ rc = rc_allocate_device(RC_DRIVER_IR_RAW);
+- if (!tt || !rc || buffer) {
++ if (!tt || !rc || !buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+--
+2.53.0
+
--- /dev/null
+From adfba1244f2825484fdd8949ef2b4e8217109b61 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:02:30 +0800
+Subject: mm/page_alloc: clear page->private in free_pages_prepare()
+
+From: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+
+[ Upstream commit ac1ea219590c09572ed5992dc233bbf7bb70fef9 ]
+
+Several subsystems (slub, shmem, ttm, etc.) use page->private but don't
+clear it before freeing pages. When these pages are later allocated as
+high-order pages and split via split_page(), tail pages retain stale
+page->private values.
+
+This causes a use-after-free in the swap subsystem. The swap code uses
+page->private to track swap count continuations, assuming freshly
+allocated pages have page->private == 0. When stale values are present,
+swap_count_continued() incorrectly assumes the continuation list is valid
+and iterates over uninitialized page->lru containing LIST_POISON values,
+causing a crash:
+
+ KASAN: maybe wild-memory-access in range [0xdead000000000100-0xdead000000000107]
+ RIP: 0010:__do_sys_swapoff+0x1151/0x1860
+
+Fix this by clearing page->private in free_pages_prepare(), ensuring all
+freed pages have clean state regardless of previous use.
+
+Link: https://lkml.kernel.org/r/20260207173615.146159-1-mikhail.v.gavrilov@gmail.com
+Fixes: 3b8000ae185c ("mm/vmalloc: huge vmalloc backing pages should be split rather than compound")
+Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+Suggested-by: Zi Yan <ziy@nvidia.com>
+Acked-by: Zi Yan <ziy@nvidia.com>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Reviewed-by: Vlastimil Babka <vbabka@suse.cz>
+Cc: Brendan Jackman <jackmanb@google.com>
+Cc: Chris Li <chrisl@kernel.org>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Kairui Song <ryncsn@gmail.com>
+Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+[backport: context only]
+Signed-off-by: Li Wang <li.wang@windriver.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ mm/page_alloc.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mm/page_alloc.c b/mm/page_alloc.c
+index b1a8abe5005e99..259249a37faf01 100644
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -1158,6 +1158,7 @@ __always_inline bool free_pages_prepare(struct page *page,
+
+ page_cpupid_reset_last(page);
+ page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
++ page->private = 0;
+ reset_page_owner(page, order);
+ page_table_check_free(page, order);
+ pgalloc_tag_sub(page, 1 << order);
+--
+2.53.0
+
--- /dev/null
+From f17c5cf4f79bf980e7ed7511f2d03097bda6dfa7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 23:06:57 -0700
+Subject: perf: Fix dangling cgroup pointer in cpuctx
+
+From: Yeoreum Yun <yeoreum.yun@arm.com>
+
+[ Upstream commit 3b7a34aebbdf2a4b7295205bf0c654294283ec82 ]
+
+Commit a3c3c6667("perf/core: Fix child_total_time_enabled accounting
+bug at task exit") moves the event->state update to before
+list_del_event(). This makes the event->state test in list_del_event()
+always false; never calling perf_cgroup_event_disable().
+
+As a result, cpuctx->cgrp won't be cleared properly; causing havoc.
+
+Fixes: a3c3c6667("perf/core: Fix child_total_time_enabled accounting bug at task exit")
+Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Tested-by: David Wang <00107082@163.com>
+Link: https://lore.kernel.org/all/aD2TspKH%2F7yvfYoO@e129823.arm.com/
+Signed-off-by: Ian Klatzco <iklatzco@gmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ kernel/events/core.c | 16 ++++------------
+ 1 file changed, 4 insertions(+), 12 deletions(-)
+
+diff --git a/kernel/events/core.c b/kernel/events/core.c
+index 6fce2bac6dae52..9099c0cc933be2 100644
+--- a/kernel/events/core.c
++++ b/kernel/events/core.c
+@@ -2096,18 +2096,6 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx)
+ if (event->group_leader == event)
+ del_event_from_groups(event, ctx);
+
+- /*
+- * If event was in error state, then keep it
+- * that way, otherwise bogus counts will be
+- * returned on read(). The only way to get out
+- * of error state is by explicit re-enabling
+- * of the event
+- */
+- if (event->state > PERF_EVENT_STATE_OFF) {
+- perf_cgroup_event_disable(event, ctx);
+- perf_event_set_state(event, PERF_EVENT_STATE_OFF);
+- }
+-
+ ctx->generation++;
+ event->pmu_ctx->nr_events--;
+ }
+@@ -2457,6 +2445,10 @@ __perf_remove_from_context(struct perf_event *event,
+ state = PERF_EVENT_STATE_DEAD;
+ }
+ event_sched_out(event, ctx);
++
++ if (event->state > PERF_EVENT_STATE_OFF)
++ perf_cgroup_event_disable(event, ctx);
++
+ perf_event_set_state(event, min(event->state, state));
+ if (flags & DETACH_GROUP)
+ perf_group_detach(event);
+--
+2.53.0
+
--- /dev/null
+From 462f5fe7184fef33160dba35c966ea3cc35a26dc Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 17:48:55 +0200
+Subject: s390/cio: Restore GFP_DMA for CHSC allocation
+
+From: Peter Oberparleiter <oberpar@linux.ibm.com>
+
+[ Upstream commit ea34567db0a6b3a7ce78ba421592344315c8f90e ]
+
+Re-add GFP_DMA when allocating memory for CHSC control blocks.
+On some supported machines, CHSC cannot access memory outside
+the DMA zone, causing CHSC command failures.
+
+Cc: stable@vger.kernel.org
+Fixes: a3a64a4def8d ("s390/cio: remove unneeded DMA zone allocation")
+Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
+Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
+Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
+[ adjusted context to account for missing commit bf4afc53b77ae ]
+Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/s390/cio/chsc.c | 4 ++--
+ drivers/s390/cio/chsc_sch.c | 20 ++++++++++----------
+ drivers/s390/cio/scm.c | 2 +-
+ 3 files changed, 13 insertions(+), 13 deletions(-)
+
+diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
+index dcc1e1c34ca2e1..8fe6658dcfe1a3 100644
+--- a/drivers/s390/cio/chsc.c
++++ b/drivers/s390/cio/chsc.c
+@@ -1153,8 +1153,8 @@ int __init chsc_init(void)
+ {
+ int ret;
+
+- sei_page = (void *)get_zeroed_page(GFP_KERNEL);
+- chsc_page = (void *)get_zeroed_page(GFP_KERNEL);
++ sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
++ chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sei_page || !chsc_page) {
+ ret = -ENOMEM;
+ goto out_err;
+diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c
+index 1e58ee3cc87db1..9131ce3af1b8eb 100644
+--- a/drivers/s390/cio/chsc_sch.c
++++ b/drivers/s390/cio/chsc_sch.c
+@@ -293,7 +293,7 @@ static int chsc_ioctl_start(void __user *user_area)
+ if (!css_general_characteristics.dynio)
+ /* It makes no sense to try. */
+ return -EOPNOTSUPP;
+- chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL);
+ if (!chsc_area)
+ return -ENOMEM;
+ request = kzalloc(sizeof(*request), GFP_KERNEL);
+@@ -341,7 +341,7 @@ static int chsc_ioctl_on_close_set(void __user *user_area)
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+- on_close_chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ on_close_chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL);
+ if (!on_close_chsc_area) {
+ ret = -ENOMEM;
+ goto out_free_request;
+@@ -393,7 +393,7 @@ static int chsc_ioctl_start_sync(void __user *user_area)
+ struct chsc_sync_area *chsc_area;
+ int ret, ccode;
+
+- chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ chsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!chsc_area)
+ return -ENOMEM;
+ if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) {
+@@ -439,7 +439,7 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *scpcd_area;
+
+- scpcd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scpcd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scpcd_area)
+ return -ENOMEM;
+ cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+@@ -501,7 +501,7 @@ static int chsc_ioctl_info_cu(void __user *user_cd)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *scucd_area;
+
+- scucd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scucd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scucd_area)
+ return -ENOMEM;
+ cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+@@ -564,7 +564,7 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *sscud_area;
+
+- sscud_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sscud_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sscud_area)
+ return -ENOMEM;
+ cud = kzalloc(sizeof(*cud), GFP_KERNEL);
+@@ -626,7 +626,7 @@ static int chsc_ioctl_conf_info(void __user *user_ci)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *sci_area;
+
+- sci_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sci_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sci_area)
+ return -ENOMEM;
+ ci = kzalloc(sizeof(*ci), GFP_KERNEL);
+@@ -697,7 +697,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl)
+ u32 res;
+ } __attribute__ ((packed)) *cssids_parm;
+
+- sccl_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sccl_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sccl_area)
+ return -ENOMEM;
+ ccl = kzalloc(sizeof(*ccl), GFP_KERNEL);
+@@ -757,7 +757,7 @@ static int chsc_ioctl_chpd(void __user *user_chpd)
+ int ret;
+
+ chpd = kzalloc(sizeof(*chpd), GFP_KERNEL);
+- scpd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scpd_area || !chpd) {
+ ret = -ENOMEM;
+ goto out_free;
+@@ -797,7 +797,7 @@ static int chsc_ioctl_dcal(void __user *user_dcal)
+ u8 data[PAGE_SIZE - 36];
+ } __attribute__ ((packed)) *sdcal_area;
+
+- sdcal_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sdcal_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sdcal_area)
+ return -ENOMEM;
+ dcal = kzalloc(sizeof(*dcal), GFP_KERNEL);
+diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c
+index c7894d61306d74..375cbfa31b5373 100644
+--- a/drivers/s390/cio/scm.c
++++ b/drivers/s390/cio/scm.c
+@@ -228,7 +228,7 @@ int scm_update_information(void)
+ size_t num;
+ int ret;
+
+- scm_info = (void *)__get_free_page(GFP_KERNEL);
++ scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+ if (!scm_info)
+ return -ENOMEM;
+
+--
+2.53.0
+
ipv6-fix-possible-infinite-loop-in-rt6_fill_node.patch
ipv6-fix-possible-infinite-loop-in-fib6_select_path.patch
net-skbuff-fix-pskb_carve-leaking-zcopy-pages.patch
+perf-fix-dangling-cgroup-pointer-in-cpuctx.patch
+batman-adv-v-stop-ogmv2-on-disabled-interface.patch
+batman-adv-tvlv-abort-ogm-send-on-tvlv-append-failur.patch
+batman-adv-tt-reject-oversized-local-tvlv-buffers.patch
+batman-adv-bla-avoid-null-ptr-deref-for-claim-via-dr.patch
+batman-adv-tvlv-reject-oversized-tvlv-packets.patch
+batman-adv-iv-recover-ogm-scheduling-after-forward-p.patch
+batman-adv-tp_meter-avoid-role-confusion-in-tp_list.patch
+s390-cio-restore-gfp_dma-for-chsc-allocation.patch
+batman-adv-tp_meter-directly-shut-down-timer-on-clea.patch
+batman-adv-tt-fix-toctou-race-for-reported-vlans.patch
+batman-adv-tt-avoid-empty-vlan-responses.patch
+batman-adv-bla-avoid-double-decrement-of-bla.num_req.patch
+mm-page_alloc-clear-page-private-in-free_pages_prepa.patch
+media-rc-fix-race-between-unregister-and-urb-irq-cal.patch
+media-rc-ttusbir-fix-inverted-error-logic.patch
+inet-frags-add-inet_frag_queue_flush.patch
+inet-frags-flush-pending-skbs-in-fqdir_pre_exit.patch
+hid-core-add-printk_ratelimited-variants-to-hid_warn.patch
+hid-pass-the-buffer-size-to-hid_report_raw_event.patch
+hid-core-introduce-hid_safe_input_report.patch
+hid-core-fix-size_t-specifier-in-hid_report_raw_even.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-dp-add-edp-1.5-bit-definition.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch
--- /dev/null
+From 29c45fe3bb0ab56bbccb44b120543e7728827716 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:06:11 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From 96a8f7c9a0997dda535ebf9a2d55d5f02716f609 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:06:13 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 28 +++++++++++++++++++++++-
+ 1 file changed, 27 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 802a671b2afaaa..eed281b93cd84d 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -29,6 +29,7 @@
+ #include <drm/drm_vblank.h>
+
+ #include "i915_reg.h"
++#include "i915_utils.h"
+ #include "intel_alpm.h"
+ #include "intel_atomic.h"
+ #include "intel_crtc.h"
+@@ -1315,6 +1316,30 @@ static bool psr2_granularity_check(struct intel_dp *intel_dp,
+ return true;
+ }
+
++static bool apply_scanline_indication_wa(struct intel_dp *intel_dp,
++ struct intel_crtc_state *crtc_state)
++{
++ u8 early_scanline_support = intel_dp->intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
+ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+ struct intel_crtc_state *crtc_state)
+ {
+@@ -1336,7 +1361,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(intel_dp, crtc_state);
+ }
+
+ static int intel_psr_entry_setup_frames(struct intel_dp *intel_dp,
+--
+2.53.0
+
--- /dev/null
+From c0493c4811c22fd21f1de77a27f328350822659b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:06:12 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index 39dd7389f1a71a..197bdb6592a7f9 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -1710,6 +1710,7 @@ struct intel_dp {
+ u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE];
+ u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE];
+ u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+ /* source rates */
+ int num_source_rates;
+ const int *source_rates;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 1da20065ea7763..802a671b2afaaa 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -41,6 +41,7 @@
+ #include "intel_display_types.h"
+ #include "intel_dmc.h"
+ #include "intel_dp.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_dsb.h"
+ #include "intel_frontbuffer.h"
+@@ -679,6 +680,12 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp)
+ drm_dbg_kms(display->drm, "PSR2 %ssupported\n",
+ intel_dp->psr.sink_psr2_support ? "" : "not ");
+ }
++
++ if (intel_dp->psr.sink_psr2_support)
++ drm_dp_dpcd_read(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &intel_dp->intel_wa_dpcd,
++ sizeof(intel_dp->intel_wa_dpcd));
+ }
+
+ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
+--
+2.53.0
+
--- /dev/null
+From 354c235ba8a454fec4ba58a0d86a2cb09746a428 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 20:50:43 +0000
+Subject: Revert "x86/fpu: Refine and simplify the magic number check during
+ signal return"
+
+From: Andrei Vagin <avagin@google.com>
+
+[ Upstream commit 44eeff9bc467bc7d1fec34fc3f6001f385fe462c ]
+
+This reverts
+
+ dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").
+
+The aforementioned commit broke applications that construct signal frames in
+userspace (such as CRIU and gVisor) if the frame's xstate size is smaller than
+the kernel's fpstate->user_size.
+
+Furthermore, this introduces a critical issue for checkpoint/restore tools
+like CRIU. If a process is checkpointed while inside a signal handler, its
+stack contains a signal frame formatted according to the source host's xstate
+capabilities.
+
+If that process is later restored on a destination host with larger xstate
+capabilities (e.g., a newer CPU with more features enabled, resulting in
+a larger fpstate->user_size), the kernel will look for FP_XSTATE_MAGIC2 at the
+destination host's larger user_size offset instead of the offset encoded in
+the frame's fx_sw->xstate_size.
+
+This causes the magic2 check to fail, forcing sigreturn to silently fall back
+to "FX-only" mode. Upon return from the signal handler, the process's extended
+state is reset to initial values instead of being restored, leading to silent
+data corruption.
+
+The aforementioned commit cited
+
+ d877550eaf2d ("x86/fpu: Stop relying on userspace for info to fault in xsave buffer")
+
+as justification to stop relying on userspace for the magic number check.
+
+However, these two changes are fundamentally different. The last one only
+changed how much memory the kernel ensures is paged-in before running XRSTOR
+to prevent an infinite loop. It did not change the signal frame format or how
+the layout is validated.
+
+Reverting this change restores the use of fx_sw->xstate_size for
+locating magic2 and restores the necessary sanity checks, ensuring that
+the signal frame remains self-describing and portable.
+
+ [ bp: Massage commit message. ]
+
+Fixes: dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return")
+Signed-off-by: Andrei Vagin <avagin@google.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Acked-by: Chang S. Bae <chang.seok.bae@intel.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/all/20260429000623.3356606-1-avagin@google.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/x86/kernel/fpu/signal.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
+index c3ec2512f2bbe4..20b638c507ca2d 100644
+--- a/arch/x86/kernel/fpu/signal.c
++++ b/arch/x86/kernel/fpu/signal.c
+@@ -27,14 +27,19 @@
+ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
+ struct _fpx_sw_bytes *fx_sw)
+ {
++ int min_xstate_size = sizeof(struct fxregs_state) +
++ sizeof(struct xstate_header);
+ void __user *fpstate = fxbuf;
+ unsigned int magic2;
+
+ if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
+ return false;
+
+- /* Check for the first magic field */
+- if (fx_sw->magic1 != FP_XSTATE_MAGIC1)
++ /* Check for the first magic field and other error scenarios. */
++ if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
++ fx_sw->xstate_size < min_xstate_size ||
++ fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
++ fx_sw->xstate_size > fx_sw->extended_size)
+ goto setfx;
+
+ /*
+@@ -43,7 +48,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
+ * fpstate layout with out copying the extended state information
+ * in the memory layout.
+ */
+- if (__get_user(magic2, (__u32 __user *)(fpstate + x86_task_fpu(current)->fpstate->user_size)))
++ if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
+ return false;
+
+ if (likely(magic2 == FP_XSTATE_MAGIC2))
+--
+2.53.0
+
--- /dev/null
+From 69d085ec1e03fbcd9c213d08a92a03f1d8bf7fc0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 2 Jun 2026 18:22:21 +0200
+Subject: s390/cio: Restore GFP_DMA for CHSC allocation
+
+From: Peter Oberparleiter <oberpar@linux.ibm.com>
+
+[ Upstream commit ea34567db0a6b3a7ce78ba421592344315c8f90e ]
+
+Re-add GFP_DMA when allocating memory for CHSC control blocks.
+On some supported machines, CHSC cannot access memory outside
+the DMA zone, causing CHSC command failures.
+
+Cc: stable@vger.kernel.org
+Fixes: a3a64a4def8d ("s390/cio: remove unneeded DMA zone allocation")
+Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
+Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
+Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
+[ adjusted context to account for missing commit bf4afc53b77ae ]
+Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/s390/cio/chsc.c | 4 ++--
+ drivers/s390/cio/chsc_sch.c | 20 ++++++++++----------
+ drivers/s390/cio/scm.c | 2 +-
+ 3 files changed, 13 insertions(+), 13 deletions(-)
+
+diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
+index 239c92d4ec11e5..b5f6eb18ebadb2 100644
+--- a/drivers/s390/cio/chsc.c
++++ b/drivers/s390/cio/chsc.c
+@@ -1143,8 +1143,8 @@ int __init chsc_init(void)
+ {
+ int ret;
+
+- sei_page = (void *)get_zeroed_page(GFP_KERNEL);
+- chsc_page = (void *)get_zeroed_page(GFP_KERNEL);
++ sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
++ chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sei_page || !chsc_page) {
+ ret = -ENOMEM;
+ goto out_err;
+diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c
+index 1e58ee3cc87db1..9131ce3af1b8eb 100644
+--- a/drivers/s390/cio/chsc_sch.c
++++ b/drivers/s390/cio/chsc_sch.c
+@@ -293,7 +293,7 @@ static int chsc_ioctl_start(void __user *user_area)
+ if (!css_general_characteristics.dynio)
+ /* It makes no sense to try. */
+ return -EOPNOTSUPP;
+- chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL);
+ if (!chsc_area)
+ return -ENOMEM;
+ request = kzalloc(sizeof(*request), GFP_KERNEL);
+@@ -341,7 +341,7 @@ static int chsc_ioctl_on_close_set(void __user *user_area)
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+- on_close_chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ on_close_chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL);
+ if (!on_close_chsc_area) {
+ ret = -ENOMEM;
+ goto out_free_request;
+@@ -393,7 +393,7 @@ static int chsc_ioctl_start_sync(void __user *user_area)
+ struct chsc_sync_area *chsc_area;
+ int ret, ccode;
+
+- chsc_area = (void *)get_zeroed_page(GFP_KERNEL);
++ chsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!chsc_area)
+ return -ENOMEM;
+ if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) {
+@@ -439,7 +439,7 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *scpcd_area;
+
+- scpcd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scpcd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scpcd_area)
+ return -ENOMEM;
+ cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+@@ -501,7 +501,7 @@ static int chsc_ioctl_info_cu(void __user *user_cd)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *scucd_area;
+
+- scucd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scucd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scucd_area)
+ return -ENOMEM;
+ cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+@@ -564,7 +564,7 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *sscud_area;
+
+- sscud_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sscud_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sscud_area)
+ return -ENOMEM;
+ cud = kzalloc(sizeof(*cud), GFP_KERNEL);
+@@ -626,7 +626,7 @@ static int chsc_ioctl_conf_info(void __user *user_ci)
+ u8 data[PAGE_SIZE - 20];
+ } __attribute__ ((packed)) *sci_area;
+
+- sci_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sci_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sci_area)
+ return -ENOMEM;
+ ci = kzalloc(sizeof(*ci), GFP_KERNEL);
+@@ -697,7 +697,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl)
+ u32 res;
+ } __attribute__ ((packed)) *cssids_parm;
+
+- sccl_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sccl_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sccl_area)
+ return -ENOMEM;
+ ccl = kzalloc(sizeof(*ccl), GFP_KERNEL);
+@@ -757,7 +757,7 @@ static int chsc_ioctl_chpd(void __user *user_chpd)
+ int ret;
+
+ chpd = kzalloc(sizeof(*chpd), GFP_KERNEL);
+- scpd_area = (void *)get_zeroed_page(GFP_KERNEL);
++ scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scpd_area || !chpd) {
+ ret = -ENOMEM;
+ goto out_free;
+@@ -797,7 +797,7 @@ static int chsc_ioctl_dcal(void __user *user_dcal)
+ u8 data[PAGE_SIZE - 36];
+ } __attribute__ ((packed)) *sdcal_area;
+
+- sdcal_area = (void *)get_zeroed_page(GFP_KERNEL);
++ sdcal_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!sdcal_area)
+ return -ENOMEM;
+ dcal = kzalloc(sizeof(*dcal), GFP_KERNEL);
+diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c
+index 9b4da237a0ed52..f4faa38a4b17ef 100644
+--- a/drivers/s390/cio/scm.c
++++ b/drivers/s390/cio/scm.c
+@@ -229,7 +229,7 @@ int scm_update_information(void)
+ size_t num;
+ int ret;
+
+- scm_info = (void *)__get_free_page(GFP_KERNEL);
++ scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+ if (!scm_info)
+ return -ENOMEM;
+
+--
+2.53.0
+
net-skbuff-fix-pskb_carve-leaking-zcopy-pages.patch
media-rc-fix-race-between-unregister-and-urb-irq-cal.patch
media-rc-ttusbir-fix-inverted-error-logic.patch
+smb-client-validate-the-whole-dacl-before-rewriting-.patch
+revert-x86-fpu-refine-and-simplify-the-magic-number-.patch
+s390-cio-restore-gfp_dma-for-chsc-allocation.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch
--- /dev/null
+From b7395002e94d09ad2a56106bead5544a4cfddba2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Apr 2026 20:11:31 -0400
+Subject: smb: client: validate the whole DACL before rewriting it in cifsacl
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 0a8cf165566ba55a39fd0f4de172119dd646d39a ]
+
+build_sec_desc() and id_mode_to_cifs_acl() derive a DACL pointer from a
+server-supplied dacloffset and then use the incoming ACL to rebuild the
+chmod/chown security descriptor.
+
+The original fix only checked that the struct smb_acl header fits before
+reading dacl_ptr->size or dacl_ptr->num_aces. That avoids the immediate
+header-field OOB read, but the rewrite helpers still walk ACEs based on
+pdacl->num_aces with no structural validation of the incoming DACL body.
+
+A malicious server can return a truncated DACL that still contains a
+header, claims one or more ACEs, and then drive
+replace_sids_and_copy_aces() or set_chmod_dacl() past the validated
+extent while they compare or copy attacker-controlled ACEs.
+
+Factor the DACL structural checks into validate_dacl(), extend them to
+validate each ACE against the DACL bounds, and use the shared validator
+before the chmod/chown rebuild paths. parse_dacl() reuses the same
+validator so the read-side parser and write-side rewrite paths agree on
+what constitutes a well-formed incoming DACL.
+
+Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-6
+Assisted-by: Codex:gpt-5-4
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/smb/client/cifsacl.c | 116 +++++++++++++++++++++++++++++-----------
+ 1 file changed, 85 insertions(+), 31 deletions(-)
+
+diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
+index e2948835587094..d17912beb28ec5 100644
+--- a/fs/smb/client/cifsacl.c
++++ b/fs/smb/client/cifsacl.c
+@@ -757,6 +757,77 @@ static void dump_ace(struct smb_ace *pace, char *end_of_acl)
+ }
+ #endif
+
++static int validate_dacl(struct smb_acl *pdacl, char *end_of_acl)
++{
++ int i, ace_hdr_size, ace_size, min_ace_size;
++ u16 dacl_size, num_aces;
++ char *acl_base, *end_of_dacl;
++ struct smb_ace *pace;
++
++ if (!pdacl)
++ return 0;
++
++ if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl)) {
++ cifs_dbg(VFS, "ACL too small to parse DACL\n");
++ return -EINVAL;
++ }
++
++ dacl_size = le16_to_cpu(pdacl->size);
++ if (dacl_size < sizeof(struct smb_acl) ||
++ end_of_acl < (char *)pdacl + dacl_size) {
++ cifs_dbg(VFS, "ACL too small to parse DACL\n");
++ return -EINVAL;
++ }
++
++ num_aces = le16_to_cpu(pdacl->num_aces);
++ if (!num_aces)
++ return 0;
++
++ ace_hdr_size = offsetof(struct smb_ace, sid) +
++ offsetof(struct smb_sid, sub_auth);
++ min_ace_size = ace_hdr_size + sizeof(__le32);
++ if (num_aces > (dacl_size - sizeof(struct smb_acl)) / min_ace_size) {
++ cifs_dbg(VFS, "ACL too small to parse DACL\n");
++ return -EINVAL;
++ }
++
++ end_of_dacl = (char *)pdacl + dacl_size;
++ acl_base = (char *)pdacl;
++ ace_size = sizeof(struct smb_acl);
++
++ for (i = 0; i < num_aces; ++i) {
++ if (end_of_dacl - acl_base < ace_size) {
++ cifs_dbg(VFS, "ACL too small to parse ACE\n");
++ return -EINVAL;
++ }
++
++ pace = (struct smb_ace *)(acl_base + ace_size);
++ acl_base = (char *)pace;
++
++ if (end_of_dacl - acl_base < ace_hdr_size ||
++ pace->sid.num_subauth == 0 ||
++ pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) {
++ cifs_dbg(VFS, "ACL too small to parse ACE\n");
++ return -EINVAL;
++ }
++
++ ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth;
++ if (end_of_dacl - acl_base < ace_size ||
++ le16_to_cpu(pace->size) < ace_size) {
++ cifs_dbg(VFS, "ACL too small to parse ACE\n");
++ return -EINVAL;
++ }
++
++ ace_size = le16_to_cpu(pace->size);
++ if (end_of_dacl - acl_base < ace_size) {
++ cifs_dbg(VFS, "ACL too small to parse ACE\n");
++ return -EINVAL;
++ }
++ }
++
++ return 0;
++}
++
+ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ struct smb_sid *pownersid, struct smb_sid *pgrpsid,
+ struct cifs_fattr *fattr, bool mode_from_special_sid)
+@@ -764,7 +835,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ int i;
+ u16 num_aces = 0;
+ int acl_size;
+- char *acl_base;
++ char *acl_base, *end_of_dacl;
+ struct smb_ace **ppace;
+
+ /* BB need to add parm so we can store the SID BB */
+@@ -776,12 +847,8 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ return;
+ }
+
+- /* validate that we do not go past end of acl */
+- if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) ||
+- end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) {
+- cifs_dbg(VFS, "ACL too small to parse DACL\n");
++ if (validate_dacl(pdacl, end_of_acl))
+ return;
+- }
+
+ cifs_dbg(NOISY, "DACL revision %d size %d num aces %d\n",
+ le16_to_cpu(pdacl->revision), le16_to_cpu(pdacl->size),
+@@ -792,6 +859,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ user/group/other have no permissions */
+ fattr->cf_mode &= ~(0777);
+
++ end_of_dacl = (char *)pdacl + le16_to_cpu(pdacl->size);
+ acl_base = (char *)pdacl;
+ acl_size = sizeof(struct smb_acl);
+
+@@ -799,36 +867,16 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ if (num_aces > 0) {
+ umode_t denied_mode = 0;
+
+- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
+- (offsetof(struct smb_ace, sid) +
+- offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
+- return;
+-
+ ppace = kmalloc_array(num_aces, sizeof(struct smb_ace *),
+ GFP_KERNEL);
+ if (!ppace)
+ return;
+
+ for (i = 0; i < num_aces; ++i) {
+- if (end_of_acl - acl_base < acl_size)
+- break;
+-
+ ppace[i] = (struct smb_ace *) (acl_base + acl_size);
+- acl_base = (char *)ppace[i];
+- acl_size = offsetof(struct smb_ace, sid) +
+- offsetof(struct smb_sid, sub_auth);
+-
+- if (end_of_acl - acl_base < acl_size ||
+- ppace[i]->sid.num_subauth == 0 ||
+- ppace[i]->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+- (end_of_acl - acl_base <
+- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) ||
+- (le16_to_cpu(ppace[i]->size) <
+- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth))
+- break;
+
+ #ifdef CONFIG_CIFS_DEBUG2
+- dump_ace(ppace[i], end_of_acl);
++ dump_ace(ppace[i], end_of_dacl);
+ #endif
+ if (mode_from_special_sid &&
+ ppace[i]->sid.num_subauth >= 3 &&
+@@ -871,6 +919,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
+ (void *)ppace[i],
+ sizeof(struct smb_ace)); */
+
++ acl_base = (char *)ppace[i];
+ acl_size = le16_to_cpu(ppace[i]->size);
+ }
+
+@@ -1316,10 +1365,9 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
+ }
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
+- if (end_of_acl < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) {
+- cifs_dbg(VFS, "Server returned illegal ACL size\n");
+- return -EINVAL;
+- }
++ rc = validate_dacl(dacl_ptr, end_of_acl);
++ if (rc)
++ return rc;
+ }
+
+ owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
+@@ -1698,6 +1746,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
+ }
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
++ rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
++ if (rc) {
++ kfree(pntsd);
++ cifs_put_tlink(tlink);
++ return rc;
++ }
+ if (mode_from_sid)
+ nsecdesclen +=
+ le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace);
+--
+2.53.0
+
--- /dev/null
+From 08dda28b5d0acfaa0cc0d1c1b06485908972dd54 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:19:23 +0200
+Subject: batman-adv: bla: avoid double decrement of bla.num_requests
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 83ab69bd12b80f6ea169c8bea6977701b53a043d upstream.
+
+The bla.num_requests is increased when no request_sent was in progress. And
+it is decremented in various places (announcement was received, backbone is
+purged, periodic work). But the check if the request_sent is actually set
+to a specific state and the atomic_dec/_inc are not safe because they are
+not atomic (TOCTOU) and multiple such code portions can run concurrently.
+
+At the same time, it is necessary to modify request_sent (state) and
+bla.num_requests atomically. Otherwise batadv_bla_send_request() might set
+request_sent to 1 and is interrupted. batadv_handle_announce() can then
+set request_sent back to 0 and decrement num_requests before
+batadv_bla_send_request() incremented it.
+
+The two operations must therefore be locked. And since state (request_sent)
+and wait_periods are only accessed inside this lock, they can be converted
+to simpler datatypes. And to avoid that the bla.num_requests is touched by
+a parallel running context with a valid backbone_gw reference after
+batadv_bla_purge_backbone_gw() ran, a third state "stopped" is required to
+correctly signal that a backbone_gw is in the state of being cleaned up.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 51 ++++++++++++++++++--------
+ net/batman-adv/soft-interface.c | 1 +
+ net/batman-adv/types.h | 39 ++++++++++++++++----
+ 3 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 76d8c91c156a3b..cfb1eb25c6ac4d 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -516,8 +516,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+- atomic_set(&entry->request_sent, 0);
+- atomic_set(&entry->wait_periods, 0);
++ entry->state = BATADV_BLA_BACKBONE_GW_SYNCED;
++ entry->wait_periods = 0;
+ ether_addr_copy(entry->orig, orig);
+ INIT_WORK(&entry->report_work, batadv_bla_loopdetect_report);
+ kref_init(&entry->refcount);
+@@ -546,9 +546,13 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ batadv_bla_send_announce(bat_priv, entry);
+
+ /* this will be decreased in the worker thread */
+- atomic_inc(&entry->request_sent);
+- atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS);
+- atomic_inc(&bat_priv->bla.num_requests);
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (entry->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ entry->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
++ entry->wait_periods = BATADV_BLA_WAIT_PERIODS;
++ atomic_inc(&bat_priv->bla.num_requests);
++ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ return entry;
+@@ -651,10 +655,12 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw)
+ backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST);
+
+ /* no local broadcasts should be sent or received, for now. */
+- if (!atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_SYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_UNSYNCED;
+ atomic_inc(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 1);
+ }
++ spin_unlock_bh(&backbone_gw->bat_priv->bla.num_requests_lock);
+ }
+
+ /**
+@@ -875,10 +881,12 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ /* if we have sent a request and the crc was OK,
+ * we can allow traffic again.
+ */
+- if (atomic_read(&backbone_gw->request_sent)) {
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED) {
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
+ }
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+
+ batadv_backbone_gw_put(backbone_gw);
+@@ -1257,9 +1265,13 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ purged = true;
+
+ /* don't wait for the pending request anymore */
+- if (atomic_read(&backbone_gw->request_sent))
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state == BATADV_BLA_BACKBONE_GW_UNSYNCED)
+ atomic_dec(&bat_priv->bla.num_requests);
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_STOPPED;
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
++
+ batadv_bla_del_backbone_claims(backbone_gw);
+
+ hlist_del_rcu(&backbone_gw->hash_entry);
+@@ -1510,7 +1522,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ batadv_bla_send_loopdetect(bat_priv,
+ backbone_gw);
+
+- /* request_sent is only set after creation to avoid
++ /* state is only set to unsynced after creation to avoid
+ * problems when we are not yet known as backbone gw
+ * in the backbone.
+ *
+@@ -1519,14 +1531,21 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ * some grace time.
+ */
+
+- if (atomic_read(&backbone_gw->request_sent) == 0)
+- continue;
++ spin_lock_bh(&bat_priv->bla.num_requests_lock);
++ if (backbone_gw->state != BATADV_BLA_BACKBONE_GW_UNSYNCED)
++ goto unlock_next;
+
+- if (!atomic_dec_and_test(&backbone_gw->wait_periods))
+- continue;
++ if (backbone_gw->wait_periods > 0)
++ backbone_gw->wait_periods--;
++
++ if (backbone_gw->wait_periods > 0)
++ goto unlock_next;
+
++ backbone_gw->state = BATADV_BLA_BACKBONE_GW_SYNCED;
+ atomic_dec(&backbone_gw->bat_priv->bla.num_requests);
+- atomic_set(&backbone_gw->request_sent, 0);
++
++unlock_next:
++ spin_unlock_bh(&bat_priv->bla.num_requests_lock);
+ }
+ rcu_read_unlock();
+ }
+diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
+index 1bf1232a4f7595..680f729b3b0228 100644
+--- a/net/batman-adv/soft-interface.c
++++ b/net/batman-adv/soft-interface.c
+@@ -783,6 +783,7 @@ static int batadv_softif_init_late(struct net_device *dev)
+ atomic_set(&bat_priv->tt.ogm_append_cnt, 0);
+ #ifdef CONFIG_BATMAN_ADV_BLA
+ atomic_set(&bat_priv->bla.num_requests, 0);
++ spin_lock_init(&bat_priv->bla.num_requests_lock);
+ #endif
+ atomic_set(&bat_priv->tp_num, 0);
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index d333f9dfec55d0..2dfdc78d4ded99 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1027,6 +1027,12 @@ struct batadv_priv_bla {
+ /** @num_requests: number of bla requests in flight */
+ atomic_t num_requests;
+
++ /**
++ * @num_requests_lock: locks update num_requests +
++ * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
++ */
++ spinlock_t num_requests_lock;
++
+ /**
+ * @claim_hash: hash table containing mesh nodes this host has claimed
+ */
+@@ -1755,6 +1761,27 @@ struct batadv_priv {
+
+ #ifdef CONFIG_BATMAN_ADV_BLA
+
++enum batadv_bla_backbone_gw_state {
++ /**
++ * @BATADV_BLA_BACKBONE_GW_STOPPED: backbone gw is being removed
++ * and it must not longer work on requests
++ */
++ BATADV_BLA_BACKBONE_GW_STOPPED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_UNSYNCED: backbone was detected out
++ * of sync and a request was send. No traffic is forwarded until the
++ * situation is resolved
++ */
++ BATADV_BLA_BACKBONE_GW_UNSYNCED,
++
++ /**
++ * @BATADV_BLA_BACKBONE_GW_SYNCED: backbone is consider to be in
++ * sync. traffic can be forwarded
++ */
++ BATADV_BLA_BACKBONE_GW_SYNCED,
++};
++
+ /**
+ * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN
+ */
+@@ -1780,16 +1807,12 @@ struct batadv_bla_backbone_gw {
+ /**
+ * @wait_periods: grace time for bridge forward delays and bla group
+ * forming at bootup phase - no bcast traffic is formwared until it has
+- * elapsed
++ * elapsed. Must only be access with num_requests_lock.
+ */
+- atomic_t wait_periods;
++ u8 wait_periods;
+
+- /**
+- * @request_sent: if this bool is set to true we are out of sync with
+- * this backbone gateway - no bcast traffic is formwared until the
+- * situation was resolved
+- */
+- atomic_t request_sent;
++ /** @state: sync state. Must only be access with num_requests_lock. */
++ enum batadv_bla_backbone_gw_state state;
+
+ /** @crc: crc16 checksum over all claims */
+ u16 crc;
+--
+2.53.0
+
--- /dev/null
+From 4674fe10e8cd7155139266fd6797e45a0262fe45 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:20:23 +0200
+Subject: batman-adv: bla: avoid NULL-ptr deref for claim via dropped interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f80d3d98d2ff78d9e2fe5d68b1f45948c4f7bd24 upstream.
+
+Without rtnl_lock held, a hardif might be retrieved as primary interface of
+a meshif, but then (while operating on this interface) getting decoupled
+from the mesh interface. In this case, the meshif still exists but the
+pointer from the primary hardif to the meshif is set to NULL.
+
+The mesh_iface must be checked first to be non-NULL before continuing to
+send an ARP request using meshif.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Reported-by: Ido Schimmel <idosch@nvidia.com>
+Reported-by: syzbot+9fdcc9f05a98a540b816@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=9fdcc9f05a98a540b816
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index f614709e6cda74..76d8c91c156a3b 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -356,12 +356,14 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac,
+ sizeof(local_claim_dest));
+ local_claim_dest.type = claimtype;
+
+- soft_iface = primary_if->soft_iface;
++ soft_iface = READ_ONCE(primary_if->soft_iface);
++ if (!soft_iface)
++ goto out;
+
+ skb = arp_create(ARPOP_REPLY, ETH_P_ARP,
+ /* IP DST: 0.0.0.0 */
+ zeroip,
+- primary_if->soft_iface,
++ soft_iface,
+ /* IP SRC: 0.0.0.0 */
+ zeroip,
+ /* Ethernet DST: Broadcast */
+--
+2.53.0
+
--- /dev/null
+From 483fd0f4f26b416d40532d4415f07918adafaec1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:18:57 +0200
+Subject: batman-adv: iv: recover OGM scheduling after forward packet error
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit aa3153bd139a6c48667dcd02608d3b2c80bff02c upstream.
+
+When batadv_iv_ogm_schedule_buff() fails to allocate and queue a forward
+packet for OGM transmission, the work item that drives periodic OGM
+scheduling is never re-armed. This silently halts transmission of the
+node's own OGMs on the affected interface — only OGMs from other peers
+continue to be aggregated and forwarded.
+
+Fix this by tracking whether batadv_iv_ogm_queue_add() (and transitively
+batadv_iv_ogm_aggregate_new()) successfully scheduled a forward packet.
+When scheduling fails, batadv_iv_ogm_schedule_buff() falls back to queuing
+a dedicated recovery work item (reschedule_work) that fires after one
+originator interval and calls batadv_iv_ogm_schedule() again.
+
+Cc: stable@kernel.org
+Fixes: c6c8fea29769 ("net: Add batman-adv meshing protocol")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 76 +++++++++++++++++++++++++++----------
+ net/batman-adv/types.h | 3 ++
+ 2 files changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 42b687c1a76807..b37c9fb178ae50 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -223,6 +223,8 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ hard_iface->bat_iv.ogm_buff = NULL;
+
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
++
++ cancel_delayed_work_sync(&hard_iface->bat_iv.reschedule_work);
+ }
+
+ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface)
+@@ -527,8 +529,10 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet,
+ * @if_incoming: interface where the packet was received
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
++static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ int packet_len, unsigned long send_time,
+ bool direct_link,
+ struct batadv_hard_iface *if_incoming,
+@@ -552,13 +556,13 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+
+ skb = netdev_alloc_skb_ip_align(NULL, skb_size);
+ if (!skb)
+- return;
++ return false;
+
+ forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing,
+ queue_left, bat_priv, skb);
+ if (!forw_packet_aggr) {
+ kfree_skb(skb);
+- return;
++ return false;
+ }
+
+ forw_packet_aggr->skb->priority = TC_PRIO_CONTROL;
+@@ -580,6 +584,8 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
+ batadv_iv_send_outstanding_bat_ogm_packet);
+
+ batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
++
++ return true;
+ }
+
+ /* aggregate a new packet into the existing ogm packet */
+@@ -609,8 +615,10 @@ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr,
+ * @if_outgoing: interface for which the retransmission should be considered
+ * @own_packet: true if it is a self-generated ogm
+ * @send_time: timestamp (jiffies) when the packet is to be sent
++ *
++ * Return: whether forward packet was scheduled
+ */
+-static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
++static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ unsigned char *packet_buff,
+ int packet_len,
+ struct batadv_hard_iface *if_incoming,
+@@ -662,14 +670,16 @@ static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv,
+ if (!own_packet && atomic_read(&bat_priv->aggregated_ogms))
+ send_time += max_aggregation_jiffies;
+
+- batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
+- send_time, direct_link,
+- if_incoming, if_outgoing,
+- own_packet);
++ return batadv_iv_ogm_aggregate_new(packet_buff, packet_len,
++ send_time, direct_link,
++ if_incoming, if_outgoing,
++ own_packet);
+ } else {
+ batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff,
+ packet_len, direct_link);
+ spin_unlock_bh(&bat_priv->forw_bat_list_lock);
++
++ return true;
+ }
+ }
+
+@@ -781,6 +791,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ bool reschedule = false;
++ bool scheduled;
+ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+@@ -809,11 +821,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ ogm_buff_len,
+ BATADV_OGM_HLEN);
+ if (ret < 0) {
+- /* OGMs must be queued even when the buffer allocation for
+- * TVLVs failed. just fall back to the non-TVLV version
+- */
+- ret = 0;
+- *ogm_buff_len = BATADV_OGM_HLEN;
++ reschedule = true;
++ goto out;
+ }
+
+ tvlv_len = ret;
+@@ -835,8 +844,11 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ /* OGMs from secondary interfaces are only scheduled on their
+ * respective interfaces.
+ */
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
+- hard_iface, hard_iface, 1, send_time);
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff, *ogm_buff_len,
++ hard_iface, hard_iface, 1, send_time);
++ if (!scheduled)
++ reschedule = true;
++
+ goto out;
+ }
+
+@@ -851,15 +863,28 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ if (!kref_get_unless_zero(&tmp_hard_iface->refcount))
+ continue;
+
+- batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
+- *ogm_buff_len, hard_iface,
+- tmp_hard_iface, 1, send_time);
+-
++ scheduled = batadv_iv_ogm_queue_add(bat_priv, *ogm_buff,
++ *ogm_buff_len, hard_iface,
++ tmp_hard_iface, 1, send_time);
+ batadv_hardif_put(tmp_hard_iface);
++
++ if (!scheduled && tmp_hard_iface == hard_iface)
++ reschedule = true;
+ }
+ rcu_read_unlock();
+
+ out:
++ if (reschedule) {
++ /* there was a failure scheduling the own forward packet.
++ * as result, the batadv_iv_send_outstanding_bat_ogm_packet()
++ * work item is no longer scheduled. it is therefore necessary
++ * to reschedule it manually
++ */
++ queue_delayed_work(batadv_event_workqueue,
++ &hard_iface->bat_iv.reschedule_work,
++ msecs_to_jiffies(atomic_read(&bat_priv->orig_interval)));
++ }
++
+ batadv_hardif_put(primary_if);
+ }
+
+@@ -874,6 +899,17 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface)
+ mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex);
+ }
+
++static void batadv_iv_ogm_reschedule(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct batadv_hard_iface *hard_iface;
++
++ hard_iface = container_of(delayed_work,
++ struct batadv_hard_iface,
++ bat_iv.reschedule_work);
++ batadv_iv_ogm_schedule(hard_iface);
++}
++
+ /**
+ * batadv_iv_orig_ifinfo_sum() - Get bcast_own sum for originator over interface
+ * @orig_node: originator which reproadcasted the OGMs directly
+@@ -2277,6 +2313,8 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1,
+
+ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface)
+ {
++ INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule);
++
+ /* begin scheduling originator messages on that interface */
+ batadv_iv_ogm_schedule(hard_iface);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 957a70854157dc..d333f9dfec55d0 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -83,6 +83,9 @@ struct batadv_hard_iface_bat_iv {
+ /** @ogm_seqno: OGM sequence number - used to identify each OGM */
+ atomic_t ogm_seqno;
+
++ /** @reschedule_work: recover OGM schedule after schedule error */
++ struct delayed_work reschedule_work;
++
+ /** @ogm_buff_mutex: lock protecting ogm_buff and ogm_buff_len */
+ struct mutex ogm_buff_mutex;
+ };
+--
+2.53.0
+
--- /dev/null
+From 55f0d0967fc724139b51a79017aef58c8a82b36c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:18:27 +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: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 59 ++++++++++++++++++++++++---------------
+ 1 file changed, 36 insertions(+), 23 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 04a83d6be45bc0..bc3dc377f0bfd0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -255,6 +255,7 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason,
+ * 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_notify(enum batadv_tp_meter_reason reason,
+ * 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_list_find(struct batadv_priv *bat_priv,
+ 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
+@@ -286,12 +291,33 @@ static struct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv,
+ return tp_vars;
+ }
+
++/**
++ * 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_list_find(struct batadv_priv *bat_priv,
+ */
+ 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 batadv_priv *bat_priv, const u8 *dst,
+ 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
+@@ -671,13 +700,10 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+
+ /* 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;
+
+@@ -986,10 +1012,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ 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,
+@@ -1110,18 +1134,14 @@ void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
+ 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);
+@@ -1377,7 +1397,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_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;
+
+@@ -1448,7 +1468,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ } 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",
+@@ -1457,13 +1477,6 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+ }
+
+- 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
+--
+2.53.0
+
--- /dev/null
+From cced89522213d7463f127b96ffb4aaa475453363 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:23:16 +0200
+Subject: batman-adv: tp_meter: directly shut down timer on cleanup
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d5487249a81ea658717614009c8f46acc5b7101a upstream.
+
+batadv_tp_sender_cleanup() was calling timer_delete_sync() followed by
+timer_delete() to guard against the timer handler re-arming itself between
+the two calls. This double-deletion hack relied on the sending status being
+set to 0 to suppress re-arming.
+
+Replace both calls with a single timer_shutdown_sync(). This function both
+waits for any running timer callback to complete (like timer_delete_sync())
+and permanently disarms the timer so it cannot be re-armed afterwards,
+making re-arming prevention unconditional and self-documenting.
+
+The re-arming property is also required because otherwise:
+
+1. context 0 (batadv_tp_recv_ack()) checks in
+ batadv_tp_reset_sender_timer() if sending is still 1 -> it is
+2. context 1 changes in batadv_tp_sender_shutdown() sending to 0 and in
+ this process forces the kthread to stop timer in
+ batadv_tp_sender_cleanup()
+3. context 0 continues in batadv_tp_reset_sender_timer() and rearms the
+ timer -> but the reference for it is already gone
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ adapt pre-hunk to old del_timer* names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index bc3dc377f0bfd0..dfc3374549921f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -430,13 +430,7 @@ static void batadv_tp_sender_cleanup(struct batadv_tp_vars *tp_vars)
+ batadv_tp_list_detach(tp_vars);
+
+ /* kill the timer and remove its reference */
+- del_timer_sync(&tp_vars->timer);
+- /* the worker might have rearmed itself therefore we kill it again. Note
+- * that if the worker should run again before invoking the following
+- * del_timer(), it would not re-arm itself once again because the status
+- * is OFF now
+- */
+- del_timer(&tp_vars->timer);
++ timer_shutdown_sync(&tp_vars->timer);
+ batadv_tp_vars_put(tp_vars);
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 2bbc0178e9bc0d94a1d1123ba06a4374950eb064 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:15:14 +0200
+Subject: batman-adv: tt: avoid empty VLAN responses
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit fa1bd704940b5bcbc32c0b28db9167405c8ee5e0 upstream.
+
+The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
+sending inconsistent TT TLVLs") added checks to the local (direct) TT
+response code. But the response can also be done indirectly by another node
+using the global TT state. To avoid such inconsistency states reported in
+the original fix, also avoid sending empty VLANs for replies from the
+global TT state.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index d4cebe122e528a..4045ddefc29b47 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -843,17 +843,19 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ s32 *tt_len)
+ {
+ u16 num_vlan = 0;
+- u16 num_entries = 0;
+ u16 tvlv_len = 0;
+ unsigned int change_offset;
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_orig_node_vlan *vlan;
++ u16 total_entries = 0;
+ u8 *tt_change_ptr;
++ int vlan_entries;
+
+ spin_lock_bh(&orig_node->vlan_list_lock);
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ total_entries += vlan_entries;
+ num_vlan++;
+- num_entries += atomic_read(&vlan->tt.num_entries);
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -861,7 +863,7 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+
+ /* if tt_len is negative, allocate the space needed by the full table */
+ if (*tt_len < 0)
+- *tt_len = batadv_tt_len(num_entries);
++ *tt_len = batadv_tt_len(total_entries);
+
+ if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+ *tt_len = 0;
+@@ -882,14 +884,27 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
++ vlan_entries = atomic_read(&vlan->tt.num_entries);
++ if (vlan_entries < 1)
++ continue;
++
+ tt_vlan->vid = htons(vlan->vid);
+ tt_vlan->crc = htonl(vlan->tt.crc);
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 8cbc0d53ee0c7e4f066543233ea7fee557a01b87 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 22:59:34 +0200
+Subject: batman-adv: tt: fix TOCTOU race for reported vlans
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 94d27005016be15ffc638b2ecbc4d58805ad7b48 upstream.
+
+The local TT based TVLV is generated by first checking the number of VLANs
+which have at least one TT entry. A new buffer with the correct size for
+the VLANs is then allocated. Only then, the list of VLANs s used to fill
+the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
+is held. But the actual number of TT entries of each VLAN can still
+increase during this time - just not the number of VLANs in the list.
+
+But the prefilter used in the buffer size calculation might still cause an
+increase of the number of VLANs which need to be stored. Simply because a
+VLAN might now suddenly have at least one entry when it had none in the
+pre-alloc check - and then needs to occupy space which was not allocated.
+
+It is better to overestimate the buffer size at the beginning and then fill
+the buffer only with the VLANs which are not empty.
+
+Cc: stable@kernel.org
+Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
+[ Context, drop flex array dependency ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 8ffebece03c529..d4cebe122e528a 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -934,11 +934,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+- if (vlan_entries < 1)
+- continue;
+-
+- num_vlan++;
+ total_entries += vlan_entries;
++ num_vlan++;
+ }
+
+ change_offset = sizeof(**tt_data);
+@@ -964,6 +961,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ (*tt_data)->num_vlan = htons(num_vlan);
+
+ tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
++ num_vlan = 0;
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+@@ -974,8 +972,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ tt_vlan->reserved = 0;
+
+ tt_vlan++;
++ num_vlan++;
+ }
+
++ /* recalculate in case number of VLANs reduced */
++ change_offset = sizeof(**tt_data);
++ change_offset += num_vlan * sizeof(*tt_vlan);
++ tvlv_len = *tt_len + change_offset;
++
++ (*tt_data)->num_vlan = htons(num_vlan);
++
+ tt_change_ptr = (u8 *)*tt_data + change_offset;
+ *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
+
+--
+2.53.0
+
--- /dev/null
+From 6216a8dca3fbf71cd1d81c6bf1022591cecae8ae Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:13:27 +0200
+Subject: batman-adv: tt: reject oversized local TVLV buffers
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 1e9fab756f8395096d5bba7be0c373c4c8f5d165 upstream.
+
+The commit 3a359bf5c61d ("batman-adv: reject oversized global TT response
+buffers") added a check to ensure that a global return buffer size can be
+stored in an u16. The same buffer handling also exists for the local data
+buffer but was not touched.
+
+A similar check should be also be in place for the local TVLV buffer. It
+doesn't have the similar attack surface because it is only generated from
+locally discovered MAC addresses but the dynamic nature could still cause
+temporarily to large buffers.
+
+Cc: stable@kernel.org
+Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index d830ccf016697b..8ffebece03c529 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -924,12 +924,12 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_tt_vlan_data *tt_vlan;
+ struct batadv_softif_vlan *vlan;
++ size_t change_offset;
+ u16 num_vlan = 0;
+ u16 vlan_entries = 0;
+ u16 total_entries = 0;
+ u16 tvlv_len;
+ u8 *tt_change_ptr;
+- int change_offset;
+
+ spin_lock_bh(&bat_priv->softif_vlan_list_lock);
+ hlist_for_each_entry(vlan, &bat_priv->softif_vlan_list, list) {
+@@ -948,8 +948,10 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
+ if (*tt_len < 0)
+ *tt_len = batadv_tt_len(total_entries);
+
+- tvlv_len = *tt_len;
+- tvlv_len += change_offset;
++ if (check_add_overflow(*tt_len, change_offset, &tvlv_len)) {
++ tvlv_len = 0;
++ goto out;
++ }
+
+ *tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
+ if (!*tt_data) {
+--
+2.53.0
+
--- /dev/null
+From adfa8ec97ffd3a2d2101c893a08e02e6376bd680 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:48:30 +0200
+Subject: batman-adv: tvlv: abort OGM send on tvlv append failure
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 501368506563e151b322c8c3f228b796e615b90d upstream.
+
+batadv_tvlv_container_ogm_append() could fail in two ways: a memory
+allocation failure when resizing the packet buffer, or the tvlv data
+exceeding U16_MAX bytes. In both cases the function previously returned the
+old (now stale) tvlv_value_len rather than signalling an error, causing the
+OGM/OGM2 send path to transmit a packet whose TVLV length field no longer
+matched the actual buffer contents. And because it also didn't fill in the
+new TVLV data, sending either uninitialized or corrupted data on the wire.
+
+All errors in batadv_tvlv_container_ogm_append() must be forwarded to the
+caller. And the caller must abort the send of the OGM2. For B.A.T.M.A.N.
+IV, it is currently not allowed to abort the send. The non-TVLV part of the
+OGM must be queued up instead.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 16 +++++++++++++---
+ net/batman-adv/bat_v_ogm.c | 26 ++++++++++++++------------
+ net/batman-adv/tvlv.c | 17 ++++++++++++-----
+ net/batman-adv/tvlv.h | 2 +-
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 748188d3b878b2..42b687c1a76807 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -781,6 +781,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ u32 seqno;
+ u16 tvlv_len = 0;
+ unsigned long send_time;
++ int ret;
+
+ lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex);
+
+@@ -804,9 +805,18 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface)
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
+- ogm_buff_len,
+- BATADV_OGM_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM_HLEN);
++ if (ret < 0) {
++ /* OGMs must be queued even when the buffer allocation for
++ * TVLVs failed. just fall back to the non-TVLV version
++ */
++ ret = 0;
++ *ogm_buff_len = BATADV_OGM_HLEN;
++ }
++
++ tvlv_len = ret;
+ }
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff);
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 310248a5812c49..8cfc3944dcfd52 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -271,9 +271,9 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_ogm2_packet *ogm_packet;
+ struct sk_buff *skb, *skb_tmp;
+- unsigned char *ogm_buff;
+- int ogm_buff_len;
+- u16 tvlv_len = 0;
++ unsigned char **ogm_buff;
++ int *ogm_buff_len;
++ u16 tvlv_len;
+ int ret;
+
+ lockdep_assert_held(&bat_priv->bat_v.ogm_buff_mutex);
+@@ -281,25 +281,27 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING)
+ goto out;
+
+- ogm_buff = bat_priv->bat_v.ogm_buff;
+- ogm_buff_len = bat_priv->bat_v.ogm_buff_len;
++ ogm_buff = &bat_priv->bat_v.ogm_buff;
++ ogm_buff_len = &bat_priv->bat_v.ogm_buff_len;
++
+ /* tt changes have to be committed before the tvlv data is
+ * appended as it may alter the tt tvlv container
+ */
+ batadv_tt_local_commit_changes(bat_priv);
+- tvlv_len = batadv_tvlv_container_ogm_append(bat_priv, &ogm_buff,
+- &ogm_buff_len,
+- BATADV_OGM2_HLEN);
++ ret = batadv_tvlv_container_ogm_append(bat_priv, ogm_buff,
++ ogm_buff_len,
++ BATADV_OGM2_HLEN);
++ if (ret < 0)
++ goto reschedule;
+
+- bat_priv->bat_v.ogm_buff = ogm_buff;
+- bat_priv->bat_v.ogm_buff_len = ogm_buff_len;
++ tvlv_len = ret;
+
+- skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + ogm_buff_len);
++ skb = netdev_alloc_skb_ip_align(NULL, ETH_HLEN + *ogm_buff_len);
+ if (!skb)
+ goto reschedule;
+
+ skb_reserve(skb, ETH_HLEN);
+- skb_put_data(skb, ogm_buff, ogm_buff_len);
++ skb_put_data(skb, *ogm_buff, *ogm_buff_len);
+
+ ogm_packet = (struct batadv_ogm2_packet *)skb->data;
+ ogm_packet->seqno = htonl(atomic_read(&bat_priv->bat_v.ogm_seqno));
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 2a583215d439bd..76c6e0599694c7 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -8,6 +8,7 @@
+
+ #include <linux/byteorder/generic.h>
+ #include <linux/container_of.h>
++#include <linux/errno.h>
+ #include <linux/etherdevice.h>
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+@@ -306,9 +307,10 @@ static bool batadv_tvlv_realloc_packet_buff(unsigned char **packet_buff,
+ * The ogm packet might be enlarged or shrunk depending on the current size
+ * and the size of the to-be-appended tvlv containers.
+ *
+- * Return: size of all appended tvlv containers in bytes.
++ * Return: size of all appended tvlv containers in bytes (max U16_MAX), negative
++ * if operation failed
+ */
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len)
+ {
+@@ -316,6 +318,7 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_len;
+ void *tvlv_value;
++ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+@@ -323,9 +326,12 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+-
+- if (!ret)
++ if (!ret) {
++ tvlv_len_ret = -ENOMEM;
+ goto end;
++ }
++
++ tvlv_len_ret = tvlv_value_len;
+
+ if (!tvlv_value_len)
+ goto end;
+@@ -344,7 +350,8 @@ u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+
+ end:
+ spin_unlock_bh(&bat_priv->tvlv.container_list_lock);
+- return tvlv_value_len;
++
++ return tvlv_len_ret;
+ }
+
+ /**
+diff --git a/net/batman-adv/tvlv.h b/net/batman-adv/tvlv.h
+index e5697230d99173..f96f6b3f44a001 100644
+--- a/net/batman-adv/tvlv.h
++++ b/net/batman-adv/tvlv.h
+@@ -16,7 +16,7 @@
+ void batadv_tvlv_container_register(struct batadv_priv *bat_priv,
+ u8 type, u8 version,
+ void *tvlv_value, u16 tvlv_value_len);
+-u16 batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
++int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ unsigned char **packet_buff,
+ int *packet_buff_len, int packet_min_len);
+ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv,
+--
+2.53.0
+
--- /dev/null
+From 3fb3cc389028233ac522449e7fc8f0ecf0c986eb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 20:17:56 +0200
+Subject: batman-adv: tvlv: reject oversized TVLV packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f50487e3566358b2b982b7801945e858c78ad9ab upstream.
+
+batadv_tvlv_container_ogm_append() builds a TVLV packet section from
+the tvlv.container_list. The total size of this section is computed by
+batadv_tvlv_container_list_size(), which sums the sizes of all registered
+containers.
+
+The return type and accumulator in batadv_tvlv_container_list_size() were
+u16. If the accumulated size exceeds U16_MAX, the value wraps around,
+causing the subsequent allocation in batadv_tvlv_container_ogm_append()
+to be undersized. The memcpy-style copy that follows would then write
+beyond the end of the allocated buffer, corrupting kernel memory.
+
+Fix this by widening the return type of batadv_tvlv_container_list_size()
+to size_t. In batadv_tvlv_container_ogm_append(), check the computed length
+against U16_MAX before proceeding, and bail out as if the allocation had
+failed when the limit is exceeded.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 76c6e0599694c7..8d6b017c433cc9 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -13,6 +13,7 @@
+ #include <linux/gfp.h>
+ #include <linux/if_ether.h>
+ #include <linux/kref.h>
++#include <linux/limits.h>
+ #include <linux/list.h>
+ #include <linux/lockdep.h>
+ #include <linux/netdevice.h>
+@@ -160,10 +161,10 @@ batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version)
+ *
+ * Return: size of all currently registered tvlv containers in bytes.
+ */
+-static u16 batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
++static size_t batadv_tvlv_container_list_size(struct batadv_priv *bat_priv)
+ {
+ struct batadv_tvlv_container *tvlv;
+- u16 tvlv_len = 0;
++ size_t tvlv_len = 0;
+
+ lockdep_assert_held(&bat_priv->tvlv.container_list_lock);
+
+@@ -316,13 +317,17 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv,
+ {
+ struct batadv_tvlv_container *tvlv;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+- u16 tvlv_value_len;
++ size_t tvlv_value_len;
+ void *tvlv_value;
+ int tvlv_len_ret;
+ bool ret;
+
+ spin_lock_bh(&bat_priv->tvlv.container_list_lock);
+ tvlv_value_len = batadv_tvlv_container_list_size(bat_priv);
++ if (tvlv_value_len > U16_MAX) {
++ tvlv_len_ret = -E2BIG;
++ goto end;
++ }
+
+ ret = batadv_tvlv_realloc_packet_buff(packet_buff, packet_buff_len,
+ packet_min_len, tvlv_value_len);
+--
+2.53.0
+
--- /dev/null
+From efaac38d454c9d628bf4e6794e3767548c67d249 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:29:56 +0200
+Subject: batman-adv: v: stop OGMv2 on disabled interface
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f8ce8b8331a1bc44ad4905886a482214d428b253 upstream.
+
+When a batadv_hard_iface is disabled, its mesh_iface pointer is set to
+NULL. However, batadv_v_ogm_send_meshif() may still dispatch OGMs via
+batadv_v_ogm_queue_on_if() for interfaces that have since lost their
+mesh_iface association. This results in a NULL pointer dereference when
+batadv_v_ogm_queue_on_if() unconditionally calls netdev_priv() on the
+now NULL hard_iface->mesh_iface to retrieve the batadv_priv.
+
+It is necessary to ensure that the batadv_v_ogm_queue_on_if() checks that
+it is using the same mesh_iface for which batadv_v_ogm_send_meshif() was
+called.
+
+Cc: stable@kernel.org
+Fixes: 0da0035942d4 ("batman-adv: OGMv2 - add basic infrastructure")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Yuan Tan <yuantan098@gmail.com>
+[ switch to old "mesh_iface" name "soft_iface" ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v_ogm.c | 33 +++++++++++++++++++++------------
+ 1 file changed, 21 insertions(+), 12 deletions(-)
+
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 8f89ffe6020ced..310248a5812c49 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -115,14 +115,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
+
+ /**
+ * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to send
+ * @hard_iface: the interface to use to send the OGM
+ */
+-static void batadv_v_ogm_send_to_if(struct sk_buff *skb,
++static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+-
+ if (hard_iface->if_status != BATADV_IF_ACTIVE) {
+ kfree_skb(skb);
+ return;
+@@ -189,6 +189,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+
+ /**
+ * batadv_v_ogm_aggr_send() - flush & send aggregation queue
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @hard_iface: the interface with the aggregation queue to flush
+ *
+ * Aggregates all OGMv2 packets currently in the aggregation queue into a
+@@ -198,7 +199,8 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface)
+ *
+ * Caller needs to hold the hard_iface->bat_v.aggr_list.lock.
+ */
+-static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
++static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv,
++ struct batadv_hard_iface *hard_iface)
+ {
+ unsigned int aggr_len = hard_iface->bat_v.aggr_len;
+ struct sk_buff *skb_aggr;
+@@ -228,27 +230,32 @@ static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface)
+ consume_skb(skb);
+ }
+
+- batadv_v_ogm_send_to_if(skb_aggr, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface);
+ }
+
+ /**
+ * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface
++ * @bat_priv: the bat priv with all the mesh interface information
+ * @skb: the OGM to queue
+ * @hard_iface: the interface to queue the OGM on
+ */
+-static void batadv_v_ogm_queue_on_if(struct sk_buff *skb,
++static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
++ struct sk_buff *skb,
+ struct batadv_hard_iface *hard_iface)
+ {
+- struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
++ if (hard_iface->soft_iface != bat_priv->soft_iface) {
++ kfree_skb(skb);
++ return;
++ }
+
+ if (!atomic_read(&bat_priv->aggregated_ogms)) {
+- batadv_v_ogm_send_to_if(skb, hard_iface);
++ batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface);
+ return;
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
+@@ -347,7 +354,7 @@ static void batadv_v_ogm_send_softif(struct batadv_priv *bat_priv)
+ break;
+ }
+
+- batadv_v_ogm_queue_on_if(skb_tmp, hard_iface);
++ batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface);
+ batadv_hardif_put(hard_iface);
+ }
+ rcu_read_unlock();
+@@ -387,12 +394,14 @@ void batadv_v_ogm_aggr_work(struct work_struct *work)
+ {
+ struct batadv_hard_iface_bat_v *batv;
+ struct batadv_hard_iface *hard_iface;
++ struct batadv_priv *bat_priv;
+
+ batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work);
+ hard_iface = container_of(batv, struct batadv_hard_iface, bat_v);
++ bat_priv = netdev_priv(hard_iface->soft_iface);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
+- batadv_v_ogm_aggr_send(hard_iface);
++ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+
+ batadv_v_ogm_start_queue_timer(hard_iface);
+@@ -582,7 +591,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv,
+ if_outgoing->net_dev->name, ntohl(ogm_forward->throughput),
+ ogm_forward->ttl, if_incoming->net_dev->name);
+
+- batadv_v_ogm_queue_on_if(skb, if_outgoing);
++ batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing);
+
+ out:
+ batadv_orig_ifinfo_put(orig_ifinfo);
+--
+2.53.0
+
--- /dev/null
+From 33966379ae9feb7cc7a5b0daa73547275a5dc158 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:06:35 +0300
+Subject: drm/dp: Add eDP 1.5 bit definition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Suraj Kandpal <suraj.kandpal@intel.com>
+
+commit 5dfc37a6b77bf6beedbd30d70184b54e1a08ccac upstream.
+
+Add the eDP revision bit value for 1.5.
+
+Spec: eDPv1.5 Table 16-5
+Signed-off-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Reviewed-by: Arun R Murthy <arun.r.murthy@intel.com>
+Tested-by: Ben Kao <ben.kao@intel.com>
+Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Link: https://patchwork.freedesktop.org/patch/msgid/20250206063253.2827017-2-suraj.kandpal@intel.com
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/drm/display/drm_dp.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
+index e69cece404b3cf..861527bf9ccb7f 100644
+--- a/include/drm/display/drm_dp.h
++++ b/include/drm/display/drm_dp.h
+@@ -959,6 +959,7 @@
+ # define DP_EDP_14 0x03
+ # define DP_EDP_14a 0x04 /* eDP 1.4a */
+ # define DP_EDP_14b 0x05 /* eDP 1.4b */
++# define DP_EDP_15 0x06 /* eDP 1.5 */
+
+ #define DP_EDP_GENERAL_CAP_1 0x701
+ # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0)
+--
+2.53.0
+
--- /dev/null
+From 5eaa0004c1a4071fbf281711d29ed9478820f7e6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:18:17 +0800
+Subject: drm, fbcon, vga_switcheroo: Avoid race condition in fbcon setup
+
+From: Thomas Zimmermann <tzimmermann@suse.de>
+
+[ Upstream commit eb76d0f5553575599561010f24c277cc5b31d003 ]
+
+Protect vga_switcheroo_client_fb_set() with console lock. Avoids OOB
+access in fbcon_remap_all(). Without holding the console lock the call
+races with switching outputs.
+
+VGA switcheroo calls fbcon_remap_all() when switching clients. The fbcon
+function uses struct fb_info.node, which is set by register_framebuffer().
+As the fb-helper code currently sets up VGA switcheroo before registering
+the framebuffer, the value of node is -1 and therefore not a legal value.
+For example, fbcon uses the value within set_con2fb_map() [1] as an index
+into an array.
+
+Moving vga_switcheroo_client_fb_set() after register_framebuffer() can
+result in VGA switching that does not switch fbcon correctly.
+
+Therefore move vga_switcheroo_client_fb_set() under fbcon_fb_registered(),
+which already holds the console lock. Fbdev calls fbcon_fb_registered()
+from within register_framebuffer(). Serializes the helper with VGA
+switcheroo's call to fbcon_remap_all().
+
+Although vga_switcheroo_client_fb_set() takes an instance of struct fb_info
+as parameter, it really only needs the contained fbcon state. Moving the
+call to fbcon initialization is therefore cleaner than before. Only amdgpu,
+i915, nouveau and radeon support vga_switcheroo. For all other drivers,
+this change does nothing.
+
+Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
+Link: https://elixir.bootlin.com/linux/v6.17/source/drivers/video/fbdev/core/fbcon.c#L2942 # [1]
+Fixes: 6a9ee8af344e ("vga_switcheroo: initial implementation (v15)")
+Acked-by: Javier Martinez Canillas <javierm@redhat.com>
+Acked-by: Alex Deucher <alexander.deucher@amd.com>
+Cc: dri-devel@lists.freedesktop.org
+Cc: nouveau@lists.freedesktop.org
+Cc: amd-gfx@lists.freedesktop.org
+Cc: linux-fbdev@vger.kernel.org
+Cc: <stable@vger.kernel.org> # v2.6.34+
+Link: https://patch.msgid.link/20251105161549.98836-1-tzimmermann@suse.de
+[ Minor context conflict resolved. ]
+Signed-off-by: Wenshan Lan <jetlan9@163.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/drm_fb_helper.c | 14 --------------
+ drivers/video/fbdev/core/fbcon.c | 9 +++++++++
+ 2 files changed, 9 insertions(+), 14 deletions(-)
+
+diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
+index eee7b56d441f71..9691c93f19a0dd 100644
+--- a/drivers/gpu/drm/drm_fb_helper.c
++++ b/drivers/gpu/drm/drm_fb_helper.c
+@@ -30,9 +30,7 @@
+ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+ #include <linux/console.h>
+-#include <linux/pci.h>
+ #include <linux/sysrq.h>
+-#include <linux/vga_switcheroo.h>
+
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_drv.h>
+@@ -575,11 +573,6 @@ EXPORT_SYMBOL(drm_fb_helper_release_info);
+ */
+ void drm_fb_helper_unregister_info(struct drm_fb_helper *fb_helper)
+ {
+- struct fb_info *info = fb_helper->info;
+- struct device *dev = info->device;
+-
+- if (dev_is_pci(dev))
+- vga_switcheroo_client_fb_set(to_pci_dev(dev), NULL);
+ unregister_framebuffer(fb_helper->info);
+ }
+ EXPORT_SYMBOL(drm_fb_helper_unregister_info);
+@@ -1673,7 +1666,6 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper)
+ {
+ struct drm_client_dev *client = &fb_helper->client;
+ struct drm_fb_helper_surface_size sizes;
+- struct fb_info *info;
+ int ret;
+
+ ret = drm_fb_helper_find_sizes(fb_helper, &sizes);
+@@ -1691,12 +1683,6 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper)
+
+ strcpy(fb_helper->fb->comm, "[fbcon]");
+
+- info = fb_helper->info;
+-
+- /* Set the fb info for vgaswitcheroo clients. Does nothing otherwise. */
+- if (dev_is_pci(info->device))
+- vga_switcheroo_client_fb_set(to_pci_dev(info->device), info);
+-
+ return 0;
+ }
+
+diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
+index 703c4e851612c8..d1ac4e45eea68e 100644
+--- a/drivers/video/fbdev/core/fbcon.c
++++ b/drivers/video/fbdev/core/fbcon.c
+@@ -64,6 +64,7 @@
+ #include <linux/console.h>
+ #include <linux/string.h>
+ #include <linux/kd.h>
++#include <linux/pci.h>
+ #include <linux/slab.h>
+ #include <linux/fb.h>
+ #include <linux/fbcon.h>
+@@ -75,6 +76,7 @@
+ #include <linux/interrupt.h>
+ #include <linux/crc32.h> /* For counting font checksums */
+ #include <linux/uaccess.h>
++#include <linux/vga_switcheroo.h>
+ #include <asm/irq.h>
+
+ #include "fbcon.h"
+@@ -2914,6 +2916,9 @@ void fbcon_fb_unregistered(struct fb_info *info)
+
+ console_lock();
+
++ if (info->device && dev_is_pci(info->device))
++ vga_switcheroo_client_fb_set(to_pci_dev(info->device), NULL);
++
+ fbcon_registered_fb[info->node] = NULL;
+ fbcon_num_registered_fb--;
+
+@@ -3047,6 +3052,10 @@ static int do_fb_registered(struct fb_info *info)
+ }
+ }
+
++ /* Set the fb info for vga_switcheroo clients. Does nothing otherwise. */
++ if (info->device && dev_is_pci(info->device))
++ vga_switcheroo_client_fb_set(to_pci_dev(info->device), info);
++
+ return ret;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From d12b29d39cbc62bb933b2904b7c43cc20a0f6ea3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 21:18:16 +0800
+Subject: drm/fbdev-helper: Set and clear VGA switcheroo client from fb_info
+
+From: Thomas Zimmermann <tzimmermann@suse.de>
+
+[ Upstream commit 02257549daf7ff839e2be6d4f3cac975e522fd7a ]
+
+Call vga_switcheroo_client_fb_set() with the PCI device from the
+instance of struct fb_info. All fbdev clients now run these calls.
+For non-PCI devices or drivers without vga-switcheroo, this does
+nothing. For i915 and radeon, it allows these drivers to use a
+common fbdev client.
+
+The device is the same as the one stored in struct drm_client and
+struct drm_fb_helper, so there is no difference in behavior. Some
+NULL-pointer checks are being removed, where those pointers cannot
+be NULL.
+
+v4:
+- clarify call semantics for drm_fb_helper_unregister_info() (Javier)
+
+Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
+Reviewed-by: Javier Martinez Canillas <javierm@redhat.com>
+Link: https://patchwork.freedesktop.org/patch/msgid/20240924071734.98201-3-tzimmermann@suse.de
+[ The variable 'dev' in the function drm_fb_helper_single_fb_probe() is
+unused; remove it in v6.6. ]
+Signed-off-by: Wenshan Lan <jetlan9@163.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/drm_fb_helper.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
+index b507c1c008a3e9..eee7b56d441f71 100644
+--- a/drivers/gpu/drm/drm_fb_helper.c
++++ b/drivers/gpu/drm/drm_fb_helper.c
+@@ -567,7 +567,7 @@ EXPORT_SYMBOL(drm_fb_helper_release_info);
+
+ /**
+ * drm_fb_helper_unregister_info - unregister fb_info framebuffer device
+- * @fb_helper: driver-allocated fbdev helper, can be NULL
++ * @fb_helper: driver-allocated fbdev helper, must not be NULL
+ *
+ * A wrapper around unregister_framebuffer, to release the fb_info
+ * framebuffer device. This must be called before releasing all resources for
+@@ -575,8 +575,12 @@ EXPORT_SYMBOL(drm_fb_helper_release_info);
+ */
+ void drm_fb_helper_unregister_info(struct drm_fb_helper *fb_helper)
+ {
+- if (fb_helper && fb_helper->info)
+- unregister_framebuffer(fb_helper->info);
++ struct fb_info *info = fb_helper->info;
++ struct device *dev = info->device;
++
++ if (dev_is_pci(dev))
++ vga_switcheroo_client_fb_set(to_pci_dev(dev), NULL);
++ unregister_framebuffer(fb_helper->info);
+ }
+ EXPORT_SYMBOL(drm_fb_helper_unregister_info);
+
+@@ -1668,8 +1672,8 @@ static int drm_fb_helper_find_sizes(struct drm_fb_helper *fb_helper,
+ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper)
+ {
+ struct drm_client_dev *client = &fb_helper->client;
+- struct drm_device *dev = fb_helper->dev;
+ struct drm_fb_helper_surface_size sizes;
++ struct fb_info *info;
+ int ret;
+
+ ret = drm_fb_helper_find_sizes(fb_helper, &sizes);
+@@ -1687,9 +1691,11 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper)
+
+ strcpy(fb_helper->fb->comm, "[fbcon]");
+
++ info = fb_helper->info;
++
+ /* Set the fb info for vgaswitcheroo clients. Does nothing otherwise. */
+- if (dev_is_pci(dev->dev))
+- vga_switcheroo_client_fb_set(to_pci_dev(dev->dev), fb_helper->info);
++ if (dev_is_pci(info->device))
++ vga_switcheroo_client_fb_set(to_pci_dev(info->device), info);
+
+ return 0;
+ }
+--
+2.53.0
+
--- /dev/null
+From fe9c93a9f24bfc9ed637d9fad967d5517753f154 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:06:33 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From d04df89f279f3c07dfec4c47955fe07ad65fea95 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:06:36 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 27 +++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 49842f7877f435..416686a6566bab 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -1007,6 +1007,30 @@ static bool psr2_granularity_check(struct intel_dp *intel_dp,
+ return true;
+ }
+
++static bool apply_scanline_indication_wa(struct intel_dp *intel_dp,
++ struct intel_crtc_state *crtc_state)
++{
++ u8 early_scanline_support = intel_dp->intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
+ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+ struct intel_crtc_state *crtc_state)
+ {
+@@ -1028,7 +1052,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(intel_dp, crtc_state);
+ }
+
+ static bool _compute_psr2_wake_times(struct intel_dp *intel_dp,
+--
+2.53.0
+
--- /dev/null
+From dd0d64dd0c92a33edbb0c7b03ebf6863cd04e8ae Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:06:34 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index 1c23b186aff20c..3a228cfb1550f2 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -1719,6 +1719,7 @@ struct intel_dp {
+ u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE];
+ u8 fec_capable;
+ u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+ /* source rates */
+ int num_source_rates;
+ const int *source_rates;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index b0818dc8480edb..49842f7877f435 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -31,6 +31,7 @@
+ #include "intel_de.h"
+ #include "intel_display_types.h"
+ #include "intel_dp.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_hdmi.h"
+ #include "intel_psr.h"
+@@ -528,6 +529,12 @@ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
+ intel_dp_get_su_granularity(intel_dp);
+ }
+ }
++
++ if (intel_dp->psr.sink_psr2_support)
++ drm_dp_dpcd_read(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &intel_dp->intel_wa_dpcd,
++ sizeof(intel_dp->intel_wa_dpcd));
+ }
+
+ static void hsw_psr_setup_aux(struct intel_dp *intel_dp)
+--
+2.53.0
+
--- /dev/null
+From 4193c8738c7345b404b7e306e9011d8a6d70556e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 18:24:08 +0800
+Subject: inet: frags: add inet_frag_queue_flush()
+
+From: Jakub Kicinski <kuba@kernel.org>
+
+[ Upstream commit 1231eec6994be29d6bb5c303dfa54731ed9fc0e6 ]
+
+Instead of exporting inet_frag_rbtree_purge() which requires that
+caller takes care of memory accounting, add a new helper. We will
+need to call it from a few places in the next patch.
+
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20251207010942.1672972-3-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Rajani Kantha <681739313@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/inet_frag.h | 5 ++---
+ net/ipv4/inet_fragment.c | 15 ++++++++++++---
+ net/ipv4/ip_fragment.c | 6 +-----
+ 3 files changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h
+index 5af6eb14c5db15..94edc0e130d2c4 100644
+--- a/include/net/inet_frag.h
++++ b/include/net/inet_frag.h
+@@ -141,9 +141,8 @@ void inet_frag_kill(struct inet_frag_queue *q);
+ void inet_frag_destroy(struct inet_frag_queue *q);
+ struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key);
+
+-/* Free all skbs in the queue; return the sum of their truesizes. */
+-unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+- enum skb_drop_reason reason);
++void inet_frag_queue_flush(struct inet_frag_queue *q,
++ enum skb_drop_reason reason);
+
+ static inline void inet_frag_put(struct inet_frag_queue *q)
+ {
+diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c
+index 496308c0238485..1e390d24f11440 100644
+--- a/net/ipv4/inet_fragment.c
++++ b/net/ipv4/inet_fragment.c
+@@ -264,8 +264,8 @@ static void inet_frag_destroy_rcu(struct rcu_head *head)
+ kmem_cache_free(f->frags_cachep, q);
+ }
+
+-unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+- enum skb_drop_reason reason)
++static unsigned int
++inet_frag_rbtree_purge(struct rb_root *root, enum skb_drop_reason reason)
+ {
+ struct rb_node *p = rb_first(root);
+ unsigned int sum = 0;
+@@ -285,7 +285,16 @@ unsigned int inet_frag_rbtree_purge(struct rb_root *root,
+ }
+ return sum;
+ }
+-EXPORT_SYMBOL(inet_frag_rbtree_purge);
++
++void inet_frag_queue_flush(struct inet_frag_queue *q,
++ enum skb_drop_reason reason)
++{
++ unsigned int sum;
++
++ sum = inet_frag_rbtree_purge(&q->rb_fragments, reason);
++ sub_frag_mem_limit(q->fqdir, sum);
++}
++EXPORT_SYMBOL(inet_frag_queue_flush);
+
+ void inet_frag_destroy(struct inet_frag_queue *q)
+ {
+diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
+index 484edc8513e4b7..7214d5bcc647e5 100644
+--- a/net/ipv4/ip_fragment.c
++++ b/net/ipv4/ip_fragment.c
+@@ -253,16 +253,12 @@ static int ip_frag_too_far(struct ipq *qp)
+
+ static int ip_frag_reinit(struct ipq *qp)
+ {
+- unsigned int sum_truesize = 0;
+-
+ if (!mod_timer(&qp->q.timer, jiffies + qp->q.fqdir->timeout)) {
+ refcount_inc(&qp->q.refcnt);
+ return -ETIMEDOUT;
+ }
+
+- sum_truesize = inet_frag_rbtree_purge(&qp->q.rb_fragments,
+- SKB_DROP_REASON_FRAG_TOO_FAR);
+- sub_frag_mem_limit(qp->q.fqdir, sum_truesize);
++ inet_frag_queue_flush(&qp->q, SKB_DROP_REASON_FRAG_TOO_FAR);
+
+ qp->q.flags = 0;
+ qp->q.len = 0;
+--
+2.53.0
+
--- /dev/null
+From 6aa360db0b570312fae12f5544c7aa8e58599fd3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 18:24:09 +0800
+Subject: inet: frags: flush pending skbs in fqdir_pre_exit()
+
+From: Jakub Kicinski <kuba@kernel.org>
+
+[ Upstream commit 006a5035b495dec008805df249f92c22c89c3d2e ]
+
+We have been seeing occasional deadlocks on pernet_ops_rwsem since
+September in NIPA. The stuck task was usually modprobe (often loading
+a driver like ipvlan), trying to take the lock as a Writer.
+lockdep does not track readers for rwsems so the read wasn't obvious
+from the reports.
+
+On closer inspection the Reader holding the lock was conntrack looping
+forever in nf_conntrack_cleanup_net_list(). Based on past experience
+with occasional NIPA crashes I looked thru the tests which run before
+the crash and noticed that the crash follows ip_defrag.sh. An immediate
+red flag. Scouring thru (de)fragmentation queues reveals skbs sitting
+around, holding conntrack references.
+
+The problem is that since conntrack depends on nf_defrag_ipv6,
+nf_defrag_ipv6 will load first. Since nf_defrag_ipv6 loads first its
+netns exit hooks run _after_ conntrack's netns exit hook.
+
+Flush all fragment queue SKBs during fqdir_pre_exit() to release
+conntrack references before conntrack cleanup runs. Also flush
+the queues in timer expiry handlers when they discover fqdir->dead
+is set, in case packet sneaks in while we're running the pre_exit
+flush.
+
+The commit under Fixes is not exactly the culprit, but I think
+previously the timer firing would eventually unblock the spinning
+conntrack.
+
+Fixes: d5dd88794a13 ("inet: fix various use-after-free in defrags units")
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20251207010942.1672972-4-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Rajani Kantha <681739313@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/inet_frag.h | 13 +------------
+ include/net/ipv6_frag.h | 9 ++++++---
+ net/ipv4/inet_fragment.c | 36 ++++++++++++++++++++++++++++++++++++
+ net/ipv4/ip_fragment.c | 12 +++++++-----
+ 4 files changed, 50 insertions(+), 20 deletions(-)
+
+diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h
+index 94edc0e130d2c4..fcabb34fff35de 100644
+--- a/include/net/inet_frag.h
++++ b/include/net/inet_frag.h
+@@ -123,18 +123,7 @@ void inet_frags_fini(struct inet_frags *);
+
+ int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net);
+
+-static inline void fqdir_pre_exit(struct fqdir *fqdir)
+-{
+- /* Prevent creation of new frags.
+- * Pairs with READ_ONCE() in inet_frag_find().
+- */
+- WRITE_ONCE(fqdir->high_thresh, 0);
+-
+- /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire()
+- * and ip6frag_expire_frag_queue().
+- */
+- WRITE_ONCE(fqdir->dead, true);
+-}
++void fqdir_pre_exit(struct fqdir *fqdir);
+ void fqdir_exit(struct fqdir *fqdir);
+
+ void inet_frag_kill(struct inet_frag_queue *q);
+diff --git a/include/net/ipv6_frag.h b/include/net/ipv6_frag.h
+index 7321ffe3a108c1..df61b98b521531 100644
+--- a/include/net/ipv6_frag.h
++++ b/include/net/ipv6_frag.h
+@@ -68,9 +68,6 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)
+ struct sk_buff *head;
+
+ rcu_read_lock();
+- /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */
+- if (READ_ONCE(fq->q.fqdir->dead))
+- goto out_rcu_unlock;
+ spin_lock(&fq->q.lock);
+
+ if (fq->q.flags & INET_FRAG_COMPLETE)
+@@ -79,6 +76,12 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)
+ fq->q.flags |= INET_FRAG_DROP;
+ inet_frag_kill(&fq->q);
+
++ /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */
++ if (READ_ONCE(fq->q.fqdir->dead)) {
++ inet_frag_queue_flush(&fq->q, 0);
++ goto out;
++ }
++
+ dev = dev_get_by_index_rcu(net, fq->iif);
+ if (!dev)
+ goto out;
+diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c
+index 1e390d24f11440..8cf88882d65e3e 100644
+--- a/net/ipv4/inet_fragment.c
++++ b/net/ipv4/inet_fragment.c
+@@ -219,6 +219,41 @@ static int __init inet_frag_wq_init(void)
+
+ pure_initcall(inet_frag_wq_init);
+
++void fqdir_pre_exit(struct fqdir *fqdir)
++{
++ struct inet_frag_queue *fq;
++ struct rhashtable_iter hti;
++
++ /* Prevent creation of new frags.
++ * Pairs with READ_ONCE() in inet_frag_find().
++ */
++ WRITE_ONCE(fqdir->high_thresh, 0);
++
++ /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire()
++ * and ip6frag_expire_frag_queue().
++ */
++ WRITE_ONCE(fqdir->dead, true);
++
++ rhashtable_walk_enter(&fqdir->rhashtable, &hti);
++ rhashtable_walk_start(&hti);
++
++ while ((fq = rhashtable_walk_next(&hti))) {
++ if (IS_ERR(fq)) {
++ if (PTR_ERR(fq) != -EAGAIN)
++ break;
++ continue;
++ }
++ spin_lock_bh(&fq->lock);
++ if (!(fq->flags & INET_FRAG_COMPLETE))
++ inet_frag_queue_flush(fq, 0);
++ spin_unlock_bh(&fq->lock);
++ }
++
++ rhashtable_walk_stop(&hti);
++ rhashtable_walk_exit(&hti);
++}
++EXPORT_SYMBOL(fqdir_pre_exit);
++
+ void fqdir_exit(struct fqdir *fqdir)
+ {
+ INIT_WORK(&fqdir->destroy_work, fqdir_work_fn);
+@@ -291,6 +326,7 @@ void inet_frag_queue_flush(struct inet_frag_queue *q,
+ {
+ unsigned int sum;
+
++ reason = reason ?: SKB_DROP_REASON_FRAG_REASM_TIMEOUT;
+ sum = inet_frag_rbtree_purge(&q->rb_fragments, reason);
+ sub_frag_mem_limit(q->fqdir, sum);
+ }
+diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
+index 7214d5bcc647e5..f5b5aa036cc668 100644
+--- a/net/ipv4/ip_fragment.c
++++ b/net/ipv4/ip_fragment.c
+@@ -148,11 +148,6 @@ static void ip_expire(struct timer_list *t)
+ net = qp->q.fqdir->net;
+
+ rcu_read_lock();
+-
+- /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */
+- if (READ_ONCE(qp->q.fqdir->dead))
+- goto out_rcu_unlock;
+-
+ spin_lock(&qp->q.lock);
+
+ if (qp->q.flags & INET_FRAG_COMPLETE)
+@@ -160,6 +155,13 @@ static void ip_expire(struct timer_list *t)
+
+ qp->q.flags |= INET_FRAG_DROP;
+ ipq_kill(qp);
++
++ /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */
++ if (READ_ONCE(qp->q.fqdir->dead)) {
++ inet_frag_queue_flush(&qp->q, 0);
++ goto out;
++ }
++
+ __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS);
+ __IP_INC_STATS(net, IPSTATS_MIB_REASMTIMEOUT);
+
+--
+2.53.0
+
--- /dev/null
+From e715b068fd0d731c4eb71b319158ce50a417e517 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 20 Dec 2025 10:33:26 +0000
+Subject: media: rc: fix race between unregister and urb/irq callbacks
+
+From: Sean Young <sean@mess.org>
+
+[ Upstream commit dccc0c3ddf8f16071736f98a7d6dd46a2d43e037 ]
+
+Some rc device drivers have a race condition between rc_unregister_device()
+and irq or urb callbacks. This is because rc_unregister_device() does two
+things, it marks the device as unregistered so no new commands can be
+issued and then it calls rc_free_device(). This means the driver has no
+chance to cancel any pending urb callbacks or interrupts after the device
+has been marked as unregistered. Those callbacks may access struct rc_dev
+or its members (e.g. struct ir_raw_event_ctrl), which have been freed by
+rc_free_device().
+
+This change removes the implicit call to rc_free_device() from
+rc_unregister_device(). This means that device drivers can call
+rc_unregister_device() in their remove or disconnect function, then cancel
+all the urbs and interrupts before explicitly calling rc_free_device().
+
+Note this is an alternative fix for an issue found by Haotian Zhang, see
+the Closes: tags.
+
+Reported-by: Haotian Zhang <vulab@iscas.ac.cn>
+Closes: https://lore.kernel.org/linux-media/20251114101432.2566-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114101418.2548-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114101346.2530-1-vulab@iscas.ac.cn/
+Closes: https://lore.kernel.org/linux-media/20251114090605.2413-1-vulab@iscas.ac.cn/
+Reviewed-by: Patrice Chotard <patrice.chotard@foss.st.com>
+Signed-off-by: Sean Young <sean@mess.org>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Stable-dep-of: 646ebdd31058 ("media: rc: ttusbir: fix inverted error logic")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/bridge/sil-sii8620.c | 1 +
+ drivers/hid/hid-picolcd_cir.c | 1 +
+ drivers/media/cec/core/cec-core.c | 2 +-
+ drivers/media/common/siano/smsir.c | 1 +
+ drivers/media/i2c/ir-kbd-i2c.c | 2 ++
+ drivers/media/pci/bt8xx/bttv-input.c | 3 ++-
+ drivers/media/pci/cx23885/cx23885-input.c | 1 +
+ drivers/media/pci/cx88/cx88-input.c | 3 ++-
+ drivers/media/pci/dm1105/dm1105.c | 1 +
+ drivers/media/pci/mantis/mantis_input.c | 1 +
+ drivers/media/pci/saa7134/saa7134-input.c | 1 +
+ drivers/media/pci/smipcie/smipcie-ir.c | 1 +
+ drivers/media/pci/ttpci/budget-ci.c | 1 +
+ drivers/media/rc/ati_remote.c | 6 +++---
+ drivers/media/rc/ene_ir.c | 2 +-
+ drivers/media/rc/fintek-cir.c | 3 ++-
+ drivers/media/rc/igorplugusb.c | 1 +
+ drivers/media/rc/iguanair.c | 1 +
+ drivers/media/rc/img-ir/img-ir-hw.c | 3 ++-
+ drivers/media/rc/img-ir/img-ir-raw.c | 3 ++-
+ drivers/media/rc/imon.c | 3 ++-
+ drivers/media/rc/ir-hix5hd2.c | 2 +-
+ drivers/media/rc/ir_toy.c | 1 +
+ drivers/media/rc/ite-cir.c | 2 +-
+ drivers/media/rc/mceusb.c | 1 +
+ drivers/media/rc/rc-ir-raw.c | 5 -----
+ drivers/media/rc/rc-loopback.c | 1 +
+ drivers/media/rc/rc-main.c | 6 +-----
+ drivers/media/rc/redrat3.c | 4 +++-
+ drivers/media/rc/st_rc.c | 2 +-
+ drivers/media/rc/streamzap.c | 7 ++++---
+ drivers/media/rc/sunxi-cir.c | 1 +
+ drivers/media/rc/ttusbir.c | 2 +-
+ drivers/media/rc/winbond-cir.c | 2 +-
+ drivers/media/rc/xbox_remote.c | 5 +++--
+ drivers/media/usb/au0828/au0828-input.c | 1 +
+ drivers/media/usb/dvb-usb-v2/dvb_usb_core.c | 1 +
+ drivers/media/usb/dvb-usb/dvb-usb-remote.c | 6 ++++--
+ drivers/media/usb/em28xx/em28xx-input.c | 1 +
+ drivers/staging/media/av7110/av7110_ir.c | 1 +
+ include/media/rc-core.h | 2 --
+ 41 files changed, 58 insertions(+), 36 deletions(-)
+
+diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
+index 599164e3877dbd..93c41c8dedff84 100644
+--- a/drivers/gpu/drm/bridge/sil-sii8620.c
++++ b/drivers/gpu/drm/bridge/sil-sii8620.c
+@@ -2220,6 +2220,7 @@ static void sii8620_detach(struct drm_bridge *bridge)
+ return;
+
+ rc_unregister_device(ctx->rc_dev);
++ rc_free_device(ctx->rc_dev);
+ }
+
+ static int sii8620_is_packing_required(struct sii8620 *ctx,
+diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c
+index d6faa0e00f95ac..6d4c636e1c9f7e 100644
+--- a/drivers/hid/hid-picolcd_cir.c
++++ b/drivers/hid/hid-picolcd_cir.c
+@@ -134,5 +134,6 @@ void picolcd_exit_cir(struct picolcd_data *data)
+
+ data->rc_dev = NULL;
+ rc_unregister_device(rdev);
++ rc_free_device(rdev);
+ }
+
+diff --git a/drivers/media/cec/core/cec-core.c b/drivers/media/cec/core/cec-core.c
+index b4dcc352a3c6f1..74dff5f8d33658 100644
+--- a/drivers/media/cec/core/cec-core.c
++++ b/drivers/media/cec/core/cec-core.c
+@@ -366,8 +366,8 @@ int cec_register_adapter(struct cec_adapter *adap,
+ res = cec_devnode_register(&adap->devnode, adap->owner);
+ if (res) {
+ #ifdef CONFIG_MEDIA_CEC_RC
+- /* Note: rc_unregister also calls rc_free */
+ rc_unregister_device(adap->rc);
++ rc_free_device(adap->rc);
+ adap->rc = NULL;
+ #endif
+ return res;
+diff --git a/drivers/media/common/siano/smsir.c b/drivers/media/common/siano/smsir.c
+index d85c78c104b990..5f4c0aa7a0d72a 100644
+--- a/drivers/media/common/siano/smsir.c
++++ b/drivers/media/common/siano/smsir.c
+@@ -92,6 +92,7 @@ int sms_ir_init(struct smscore_device_t *coredev)
+ void sms_ir_exit(struct smscore_device_t *coredev)
+ {
+ rc_unregister_device(coredev->ir.dev);
++ rc_free_device(coredev->ir.dev);
+
+ pr_debug("\n");
+ }
+diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c
+index a8026f0f980f98..943e761c149643 100644
+--- a/drivers/media/i2c/ir-kbd-i2c.c
++++ b/drivers/media/i2c/ir-kbd-i2c.c
+@@ -355,6 +355,7 @@ static void ir_work(struct work_struct *work)
+ mutex_unlock(&ir->lock);
+ if (rc == -ENODEV) {
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ ir->rc = NULL;
+ return;
+ }
+@@ -972,6 +973,7 @@ static void ir_remove(struct i2c_client *client)
+ i2c_unregister_device(ir->tx_c);
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ }
+
+ static const struct i2c_device_id ir_kbd_id[] = {
+diff --git a/drivers/media/pci/bt8xx/bttv-input.c b/drivers/media/pci/bt8xx/bttv-input.c
+index 41226f1d0e5b64..d70e6282c48b2e 100644
+--- a/drivers/media/pci/bt8xx/bttv-input.c
++++ b/drivers/media/pci/bt8xx/bttv-input.c
+@@ -572,8 +572,9 @@ void bttv_input_fini(struct bttv *btv)
+ if (btv->remote == NULL)
+ return;
+
+- bttv_ir_stop(btv);
+ rc_unregister_device(btv->remote->dev);
++ bttv_ir_stop(btv);
++ rc_free_device(btv->remote->dev);
+ kfree(btv->remote);
+ btv->remote = NULL;
+ }
+diff --git a/drivers/media/pci/cx23885/cx23885-input.c b/drivers/media/pci/cx23885/cx23885-input.c
+index d2e84c6457e0ab..722329ef3fd2cc 100644
+--- a/drivers/media/pci/cx23885/cx23885-input.c
++++ b/drivers/media/pci/cx23885/cx23885-input.c
+@@ -402,6 +402,7 @@ void cx23885_input_fini(struct cx23885_dev *dev)
+ if (dev->kernel_ir == NULL)
+ return;
+ rc_unregister_device(dev->kernel_ir->rc);
++ rc_free_device(dev->kernel_ir->rc);
+ kfree(dev->kernel_ir->phys);
+ kfree(dev->kernel_ir->name);
+ kfree(dev->kernel_ir);
+diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c
+index a04a1d33fadb1b..74a8769dd6c79c 100644
+--- a/drivers/media/pci/cx88/cx88-input.c
++++ b/drivers/media/pci/cx88/cx88-input.c
+@@ -510,8 +510,9 @@ int cx88_ir_fini(struct cx88_core *core)
+ if (!ir)
+ return 0;
+
+- cx88_ir_stop(core);
+ rc_unregister_device(ir->dev);
++ cx88_ir_stop(core);
++ rc_free_device(ir->dev);
+ kfree(ir);
+
+ /* done */
+diff --git a/drivers/media/pci/dm1105/dm1105.c b/drivers/media/pci/dm1105/dm1105.c
+index 9e9c7c071accce..e1185aa669f480 100644
+--- a/drivers/media/pci/dm1105/dm1105.c
++++ b/drivers/media/pci/dm1105/dm1105.c
+@@ -763,6 +763,7 @@ static int dm1105_ir_init(struct dm1105_dev *dm1105)
+ static void dm1105_ir_exit(struct dm1105_dev *dm1105)
+ {
+ rc_unregister_device(dm1105->ir.dev);
++ rc_free_device(dm1105->ir.dev);
+ }
+
+ static int dm1105_hw_init(struct dm1105_dev *dev)
+diff --git a/drivers/media/pci/mantis/mantis_input.c b/drivers/media/pci/mantis/mantis_input.c
+index 34c0d979240fda..edb4cacf55d229 100644
+--- a/drivers/media/pci/mantis/mantis_input.c
++++ b/drivers/media/pci/mantis/mantis_input.c
+@@ -72,5 +72,6 @@ EXPORT_SYMBOL_GPL(mantis_input_init);
+ void mantis_input_exit(struct mantis_pci *mantis)
+ {
+ rc_unregister_device(mantis->rc);
++ rc_free_device(mantis->rc);
+ }
+ EXPORT_SYMBOL_GPL(mantis_input_exit);
+diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c
+index 8610eb473b39e1..8a0f26d94d1de8 100644
+--- a/drivers/media/pci/saa7134/saa7134-input.c
++++ b/drivers/media/pci/saa7134/saa7134-input.c
+@@ -834,6 +834,7 @@ void saa7134_input_fini(struct saa7134_dev *dev)
+ return;
+
+ rc_unregister_device(dev->remote->dev);
++ rc_free_device(dev->remote->dev);
+ kfree(dev->remote);
+ dev->remote = NULL;
+ }
+diff --git a/drivers/media/pci/smipcie/smipcie-ir.c b/drivers/media/pci/smipcie/smipcie-ir.c
+index c0604d9c70119a..0bbe4fa2d5a84e 100644
+--- a/drivers/media/pci/smipcie/smipcie-ir.c
++++ b/drivers/media/pci/smipcie/smipcie-ir.c
+@@ -181,5 +181,6 @@ void smi_ir_exit(struct smi_dev *dev)
+
+ rc_unregister_device(rc_dev);
+ smi_ir_stop(ir);
++ rc_free_device(rc_dev);
+ ir->rc_dev = NULL;
+ }
+diff --git a/drivers/media/pci/ttpci/budget-ci.c b/drivers/media/pci/ttpci/budget-ci.c
+index 66e1a004ee431c..2ad30f91bcd9ee 100644
+--- a/drivers/media/pci/ttpci/budget-ci.c
++++ b/drivers/media/pci/ttpci/budget-ci.c
+@@ -247,6 +247,7 @@ static void msp430_ir_deinit(struct budget_ci *budget_ci)
+ tasklet_kill(&budget_ci->ir.msp430_irq_tasklet);
+
+ rc_unregister_device(budget_ci->ir.dev);
++ rc_free_device(budget_ci->ir.dev);
+ }
+
+ static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address)
+diff --git a/drivers/media/rc/ati_remote.c b/drivers/media/rc/ati_remote.c
+index fff4dd48eacad2..1d68834ecd0b15 100644
+--- a/drivers/media/rc/ati_remote.c
++++ b/drivers/media/rc/ati_remote.c
+@@ -921,7 +921,6 @@ static int ati_remote_probe(struct usb_interface *interface,
+ input_free_device(input_dev);
+ exit_unregister_device:
+ rc_unregister_device(rc_dev);
+- rc_dev = NULL;
+ exit_kill_urbs:
+ usb_kill_urb(ati_remote->irq_urb);
+ usb_kill_urb(ati_remote->out_urb);
+@@ -941,18 +940,19 @@ static void ati_remote_disconnect(struct usb_interface *interface)
+ struct ati_remote *ati_remote;
+
+ ati_remote = usb_get_intfdata(interface);
+- usb_set_intfdata(interface, NULL);
+ if (!ati_remote) {
+ dev_warn(&interface->dev, "%s - null device?\n", __func__);
+ return;
+ }
+
++ rc_unregister_device(ati_remote->rdev);
++ usb_set_intfdata(interface, NULL);
+ usb_kill_urb(ati_remote->irq_urb);
+ usb_kill_urb(ati_remote->out_urb);
+ if (ati_remote->idev)
+ input_unregister_device(ati_remote->idev);
+- rc_unregister_device(ati_remote->rdev);
+ ati_remote_free_buffers(ati_remote);
++ rc_free_device(ati_remote->rdev);
+ kfree(ati_remote);
+ }
+
+diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c
+index 11ee21a7db8f00..9140e4f28c9955 100644
+--- a/drivers/media/rc/ene_ir.c
++++ b/drivers/media/rc/ene_ir.c
+@@ -1093,7 +1093,6 @@ static int ene_probe(struct pnp_dev *pnp_dev, const struct pnp_device_id *id)
+ release_region(dev->hw_io, ENE_IO_SIZE);
+ exit_unregister_device:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ exit_free_dev_rdev:
+ rc_free_device(rdev);
+ kfree(dev);
+@@ -1113,6 +1112,7 @@ static void ene_remove(struct pnp_dev *pnp_dev)
+ ene_rx_restore_hw_buffer(dev);
+ spin_unlock_irqrestore(&dev->hw_lock, flags);
+
++ rc_free_device(dev->rdev);
+ free_irq(dev->irq, dev);
+ release_region(dev->hw_io, ENE_IO_SIZE);
+ kfree(dev);
+diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c
+index 3fb0968efd57d3..9b789097cdd4c3 100644
+--- a/drivers/media/rc/fintek-cir.c
++++ b/drivers/media/rc/fintek-cir.c
+@@ -568,6 +568,7 @@ static void fintek_remove(struct pnp_dev *pdev)
+ struct fintek_dev *fintek = pnp_get_drvdata(pdev);
+ unsigned long flags;
+
++ rc_unregister_device(fintek->rdev);
+ spin_lock_irqsave(&fintek->fintek_lock, flags);
+ /* disable CIR */
+ fintek_disable_cir(fintek);
+@@ -580,7 +581,7 @@ static void fintek_remove(struct pnp_dev *pdev)
+ free_irq(fintek->cir_irq, fintek);
+ release_region(fintek->cir_addr, fintek->cir_port_len);
+
+- rc_unregister_device(fintek->rdev);
++ rc_free_device(fintek->rdev);
+
+ kfree(fintek);
+ }
+diff --git a/drivers/media/rc/igorplugusb.c b/drivers/media/rc/igorplugusb.c
+index f3616607d4f52b..8bf059b9a31b45 100644
+--- a/drivers/media/rc/igorplugusb.c
++++ b/drivers/media/rc/igorplugusb.c
+@@ -247,6 +247,7 @@ static void igorplugusb_disconnect(struct usb_interface *intf)
+ usb_set_intfdata(intf, NULL);
+ usb_unpoison_urb(ir->urb);
+ usb_free_urb(ir->urb);
++ rc_free_device(ir->rc);
+ kfree(ir->buf_in);
+ kfree(ir->request);
+ }
+diff --git a/drivers/media/rc/iguanair.c b/drivers/media/rc/iguanair.c
+index 8af94246e5916e..7bd6dd7254157a 100644
+--- a/drivers/media/rc/iguanair.c
++++ b/drivers/media/rc/iguanair.c
+@@ -500,6 +500,7 @@ static void iguanair_disconnect(struct usb_interface *intf)
+ usb_set_intfdata(intf, NULL);
+ usb_kill_urb(ir->urb_in);
+ usb_kill_urb(ir->urb_out);
++ rc_free_device(ir->rc);
+ usb_free_urb(ir->urb_in);
+ usb_free_urb(ir->urb_out);
+ usb_free_coherent(ir->udev, MAX_IN_PACKET, ir->buf_in, ir->dma_in);
+diff --git a/drivers/media/rc/img-ir/img-ir-hw.c b/drivers/media/rc/img-ir/img-ir-hw.c
+index 5da7479c1793b1..07f41372976eaf 100644
+--- a/drivers/media/rc/img-ir/img-ir-hw.c
++++ b/drivers/media/rc/img-ir/img-ir-hw.c
+@@ -1117,9 +1117,10 @@ void img_ir_remove_hw(struct img_ir_priv *priv)
+ struct rc_dev *rdev = hw->rdev;
+ if (!rdev)
+ return;
++ rc_unregister_device(rdev);
+ img_ir_set_decoder(priv, NULL, 0);
+ hw->rdev = NULL;
+- rc_unregister_device(rdev);
++ rc_free_device(rdev);
+ #ifdef CONFIG_COMMON_CLK
+ if (!IS_ERR(priv->clk))
+ clk_notifier_unregister(priv->clk, &hw->clk_nb);
+diff --git a/drivers/media/rc/img-ir/img-ir-raw.c b/drivers/media/rc/img-ir/img-ir-raw.c
+index 8b0bdd9603b3c5..533d40dae54224 100644
+--- a/drivers/media/rc/img-ir/img-ir-raw.c
++++ b/drivers/media/rc/img-ir/img-ir-raw.c
+@@ -136,6 +136,7 @@ void img_ir_remove_raw(struct img_ir_priv *priv)
+ if (!rdev)
+ return;
+
++ rc_unregister_device(rdev);
+ /* switch off and disable raw (edge) interrupts */
+ spin_lock_irq(&priv->lock);
+ raw->rdev = NULL;
+@@ -145,7 +146,7 @@ void img_ir_remove_raw(struct img_ir_priv *priv)
+ img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
+ spin_unlock_irq(&priv->lock);
+
+- rc_unregister_device(rdev);
++ rc_free_device(rdev);
+
+ del_timer_sync(&raw->timer);
+ }
+diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c
+index bb4aabb08c06ef..69e118863aa67c 100644
+--- a/drivers/media/rc/imon.c
++++ b/drivers/media/rc/imon.c
+@@ -2546,9 +2546,10 @@ static void imon_disconnect(struct usb_interface *interface)
+
+ if (ifnum == 0) {
+ ictx->dev_present_intf0 = false;
++ rc_unregister_device(ictx->rdev);
+ usb_kill_urb(ictx->rx_urb_intf0);
+ input_unregister_device(ictx->idev);
+- rc_unregister_device(ictx->rdev);
++ rc_free_device(ictx->rdev);
+ if (ictx->display_supported) {
+ if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
+ usb_deregister_dev(interface, &imon_lcd_class);
+diff --git a/drivers/media/rc/ir-hix5hd2.c b/drivers/media/rc/ir-hix5hd2.c
+index 0034f615b4660e..df347d2fb0ead3 100644
+--- a/drivers/media/rc/ir-hix5hd2.c
++++ b/drivers/media/rc/ir-hix5hd2.c
+@@ -331,7 +331,6 @@ static int hix5hd2_ir_probe(struct platform_device *pdev)
+
+ regerr:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ clkerr:
+ clk_disable_unprepare(priv->clock);
+ err:
+@@ -346,6 +345,7 @@ static void hix5hd2_ir_remove(struct platform_device *pdev)
+
+ clk_disable_unprepare(priv->clock);
+ rc_unregister_device(priv->rdev);
++ rc_free_device(priv->rdev);
+ }
+
+ #ifdef CONFIG_PM_SLEEP
+diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c
+index 69e630d85262f6..490de29b7ffbef 100644
+--- a/drivers/media/rc/ir_toy.c
++++ b/drivers/media/rc/ir_toy.c
+@@ -536,6 +536,7 @@ static void irtoy_disconnect(struct usb_interface *intf)
+ usb_free_urb(ir->urb_out);
+ usb_kill_urb(ir->urb_in);
+ usb_free_urb(ir->urb_in);
++ rc_free_device(ir->rc);
+ kfree(ir->in);
+ kfree(ir->out);
+ kfree(ir);
+diff --git a/drivers/media/rc/ite-cir.c b/drivers/media/rc/ite-cir.c
+index fcfadd7ea31cf3..c584dc6bfd7b48 100644
+--- a/drivers/media/rc/ite-cir.c
++++ b/drivers/media/rc/ite-cir.c
+@@ -1415,7 +1415,6 @@ static int ite_probe(struct pnp_dev *pdev, const struct pnp_device_id
+ release_region(itdev->cir_addr, itdev->params->io_region_size);
+ exit_unregister_device:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ exit_free_dev_rdev:
+ rc_free_device(rdev);
+ kfree(itdev);
+@@ -1440,6 +1439,7 @@ static void ite_remove(struct pnp_dev *pdev)
+ release_region(dev->cir_addr, dev->params->io_region_size);
+
+ rc_unregister_device(dev->rdev);
++ rc_free_device(dev->rdev);
+
+ kfree(dev);
+ }
+diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c
+index c76ba24c1f5595..4115a46929c473 100644
+--- a/drivers/media/rc/mceusb.c
++++ b/drivers/media/rc/mceusb.c
+@@ -1858,6 +1858,7 @@ static void mceusb_dev_disconnect(struct usb_interface *intf)
+ usb_free_urb(ir->urb_in);
+ usb_free_coherent(dev, ir->len_in, ir->buf_in, ir->dma_in);
+ usb_put_dev(dev);
++ rc_free_device(ir->rc);
+
+ kfree(ir);
+ }
+diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c
+index 16e33d7eaaa2d8..dd70f8ad526642 100644
+--- a/drivers/media/rc/rc-ir-raw.c
++++ b/drivers/media/rc/rc-ir-raw.c
+@@ -647,9 +647,6 @@ int ir_raw_event_register(struct rc_dev *dev)
+
+ void ir_raw_event_free(struct rc_dev *dev)
+ {
+- if (!dev)
+- return;
+-
+ kfree(dev->raw);
+ dev->raw = NULL;
+ }
+@@ -673,8 +670,6 @@ void ir_raw_event_unregister(struct rc_dev *dev)
+
+ lirc_bpf_free(dev);
+
+- ir_raw_event_free(dev);
+-
+ /*
+ * A user can be calling bpf(BPF_PROG_{QUERY|ATTACH|DETACH}), so
+ * ensure that the raw member is null on unlock; this is how
+diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c
+index b356041c5c00e7..79073aaabd9175 100644
+--- a/drivers/media/rc/rc-loopback.c
++++ b/drivers/media/rc/rc-loopback.c
+@@ -264,6 +264,7 @@ static int __init loop_init(void)
+ static void __exit loop_exit(void)
+ {
+ rc_unregister_device(loopdev.dev);
++ rc_free_device(loopdev.dev);
+ }
+
+ module_init(loop_init);
+diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
+index 6bdad6341844f5..10464f7942304e 100644
+--- a/drivers/media/rc/rc-main.c
++++ b/drivers/media/rc/rc-main.c
+@@ -1611,6 +1611,7 @@ static void rc_dev_release(struct device *device)
+ {
+ struct rc_dev *dev = to_rc_dev(device);
+
++ ir_raw_event_free(dev);
+ kfree(dev);
+ }
+
+@@ -1773,7 +1774,6 @@ struct rc_dev *devm_rc_allocate_device(struct device *dev,
+ }
+
+ rc->dev.parent = dev;
+- rc->managed_alloc = true;
+ *dr = rc;
+ devres_add(dev, dr);
+
+@@ -2042,11 +2042,7 @@ void rc_unregister_device(struct rc_dev *dev)
+ device_del(&dev->dev);
+
+ ida_free(&rc_ida, dev->minor);
+-
+- if (!dev->managed_alloc)
+- rc_free_device(dev);
+ }
+-
+ EXPORT_SYMBOL_GPL(rc_unregister_device);
+
+ /*
+diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c
+index 880981e1c507e1..d47f6e983ec67d 100644
+--- a/drivers/media/rc/redrat3.c
++++ b/drivers/media/rc/redrat3.c
+@@ -1133,11 +1133,13 @@ static void redrat3_dev_disconnect(struct usb_interface *intf)
+ {
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct redrat3_dev *rr3 = usb_get_intfdata(intf);
++ struct rc_dev *rc = rr3->rc;
+
+ usb_set_intfdata(intf, NULL);
+- rc_unregister_device(rr3->rc);
++ rc_unregister_device(rc);
+ led_classdev_unregister(&rr3->led);
+ redrat3_delete(rr3, udev);
++ rc_free_device(rc);
+ }
+
+ static int redrat3_dev_suspend(struct usb_interface *intf, pm_message_t message)
+diff --git a/drivers/media/rc/st_rc.c b/drivers/media/rc/st_rc.c
+index 8a9f31e15b0c01..bac1ac4e0f3a22 100644
+--- a/drivers/media/rc/st_rc.c
++++ b/drivers/media/rc/st_rc.c
+@@ -202,6 +202,7 @@ static void st_rc_remove(struct platform_device *pdev)
+ device_init_wakeup(&pdev->dev, false);
+ clk_disable_unprepare(rc_dev->sys_clock);
+ rc_unregister_device(rc_dev->rdev);
++ rc_free_device(rc_dev->rdev);
+ }
+
+ static int st_rc_open(struct rc_dev *rdev)
+@@ -333,7 +334,6 @@ static int st_rc_probe(struct platform_device *pdev)
+ return ret;
+ rcerr:
+ rc_unregister_device(rdev);
+- rdev = NULL;
+ clkerr:
+ clk_disable_unprepare(rc_dev->sys_clock);
+ err:
+diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c
+index 8e9b156e430022..8c85b9f30a3a96 100644
+--- a/drivers/media/rc/streamzap.c
++++ b/drivers/media/rc/streamzap.c
+@@ -392,15 +392,16 @@ static void streamzap_disconnect(struct usb_interface *interface)
+ struct streamzap_ir *sz = usb_get_intfdata(interface);
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+
+- usb_set_intfdata(interface, NULL);
+-
+ if (!sz)
+ return;
+
+- usb_kill_urb(sz->urb_in);
+ rc_unregister_device(sz->rdev);
++ usb_set_intfdata(interface, NULL);
++
++ usb_kill_urb(sz->urb_in);
+ usb_free_urb(sz->urb_in);
+ usb_free_coherent(usbdev, sz->buf_in_len, sz->buf_in, sz->dma_in);
++ rc_free_device(sz->rdev);
+
+ kfree(sz);
+ }
+diff --git a/drivers/media/rc/sunxi-cir.c b/drivers/media/rc/sunxi-cir.c
+index bf58c965ead8cf..e2477e724637fa 100644
+--- a/drivers/media/rc/sunxi-cir.c
++++ b/drivers/media/rc/sunxi-cir.c
+@@ -370,6 +370,7 @@ static void sunxi_ir_remove(struct platform_device *pdev)
+ struct sunxi_ir *ir = platform_get_drvdata(pdev);
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+ sunxi_ir_hw_exit(&pdev->dev);
+ }
+
+diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c
+index dde446a95eaa93..a670d4b008cb0d 100644
+--- a/drivers/media/rc/ttusbir.c
++++ b/drivers/media/rc/ttusbir.c
+@@ -336,7 +336,6 @@ static int ttusbir_probe(struct usb_interface *intf,
+ return 0;
+ out3:
+ rc_unregister_device(rc);
+- rc = NULL;
+ out2:
+ led_classdev_unregister(&tt->led);
+ out:
+@@ -378,6 +377,7 @@ static void ttusbir_disconnect(struct usb_interface *intf)
+ usb_kill_urb(tt->bulk_urb);
+ usb_free_urb(tt->bulk_urb);
+ kfree(tt->bulk_buffer);
++ rc_free_device(tt->rc);
+ usb_set_intfdata(intf, NULL);
+ kfree(tt);
+ }
+diff --git a/drivers/media/rc/winbond-cir.c b/drivers/media/rc/winbond-cir.c
+index 25884a79985c8a..14d8b58e283980 100644
+--- a/drivers/media/rc/winbond-cir.c
++++ b/drivers/media/rc/winbond-cir.c
+@@ -1132,7 +1132,6 @@ wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+ release_region(data->wbase, WAKEUP_IOMEM_LEN);
+ exit_unregister_device:
+ rc_unregister_device(data->dev);
+- data->dev = NULL;
+ exit_free_rc:
+ rc_free_device(data->dev);
+ exit_unregister_led:
+@@ -1163,6 +1162,7 @@ wbcir_remove(struct pnp_dev *device)
+ wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+ rc_unregister_device(data->dev);
++ rc_free_device(data->dev);
+
+ led_classdev_unregister(&data->led);
+
+diff --git a/drivers/media/rc/xbox_remote.c b/drivers/media/rc/xbox_remote.c
+index 0c9c855ced729c..80b7c247932a8f 100644
+--- a/drivers/media/rc/xbox_remote.c
++++ b/drivers/media/rc/xbox_remote.c
+@@ -283,14 +283,15 @@ static void xbox_remote_disconnect(struct usb_interface *interface)
+ struct xbox_remote *xbox_remote;
+
+ xbox_remote = usb_get_intfdata(interface);
+- usb_set_intfdata(interface, NULL);
+ if (!xbox_remote) {
+ dev_warn(&interface->dev, "%s - null device?\n", __func__);
+ return;
+ }
+
+- usb_kill_urb(xbox_remote->irq_urb);
+ rc_unregister_device(xbox_remote->rdev);
++ usb_set_intfdata(interface, NULL);
++ usb_kill_urb(xbox_remote->irq_urb);
++ rc_free_device(xbox_remote->rdev);
+ usb_free_urb(xbox_remote->irq_urb);
+ kfree(xbox_remote->inbuf);
+ kfree(xbox_remote);
+diff --git a/drivers/media/usb/au0828/au0828-input.c b/drivers/media/usb/au0828/au0828-input.c
+index 3d3368202cd018..283ad2c6288cd5 100644
+--- a/drivers/media/usb/au0828/au0828-input.c
++++ b/drivers/media/usb/au0828/au0828-input.c
+@@ -357,6 +357,7 @@ void au0828_rc_unregister(struct au0828_dev *dev)
+ return;
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+
+ /* done */
+ kfree(ir);
+diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
+index f1c79f351ec8de..17e8961179d14b 100644
+--- a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
++++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
+@@ -187,6 +187,7 @@ static int dvb_usbv2_remote_exit(struct dvb_usb_device *d)
+ if (d->rc_dev) {
+ cancel_delayed_work_sync(&d->rc_query_work);
+ rc_unregister_device(d->rc_dev);
++ rc_free_device(d->rc_dev);
+ d->rc_dev = NULL;
+ }
+
+diff --git a/drivers/media/usb/dvb-usb/dvb-usb-remote.c b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
+index 65e2c9e2cdc99f..6dc11718dfb985 100644
+--- a/drivers/media/usb/dvb-usb/dvb-usb-remote.c
++++ b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
+@@ -347,10 +347,12 @@ int dvb_usb_remote_exit(struct dvb_usb_device *d)
+ {
+ if (d->state & DVB_USB_STATE_REMOTE) {
+ cancel_delayed_work_sync(&d->rc_query_work);
+- if (d->props.rc.mode == DVB_RC_LEGACY)
++ if (d->props.rc.mode == DVB_RC_LEGACY) {
+ input_unregister_device(d->input_dev);
+- else
++ } else {
+ rc_unregister_device(d->rc_dev);
++ rc_free_device(d->rc_dev);
++ }
+ }
+ d->state &= ~DVB_USB_STATE_REMOTE;
+ return 0;
+diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c
+index 5f3b00869bdbc9..26f333b5be7325 100644
+--- a/drivers/media/usb/em28xx/em28xx-input.c
++++ b/drivers/media/usb/em28xx/em28xx-input.c
+@@ -853,6 +853,7 @@ static int em28xx_ir_fini(struct em28xx *dev)
+ goto ref_put;
+
+ rc_unregister_device(ir->rc);
++ rc_free_device(ir->rc);
+
+ kfree(ir->i2c_client);
+
+diff --git a/drivers/staging/media/av7110/av7110_ir.c b/drivers/staging/media/av7110/av7110_ir.c
+index a851ba328e4a3e..c0a32325e2cc98 100644
+--- a/drivers/staging/media/av7110/av7110_ir.c
++++ b/drivers/staging/media/av7110/av7110_ir.c
+@@ -152,6 +152,7 @@ int av7110_ir_init(struct av7110 *av7110)
+ void av7110_ir_exit(struct av7110 *av7110)
+ {
+ rc_unregister_device(av7110->ir.rcdev);
++ rc_free_device(av7110->ir.rcdev);
+ }
+
+ //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
+diff --git a/include/media/rc-core.h b/include/media/rc-core.h
+index 803349599c272b..b487b0d5ffd1d0 100644
+--- a/include/media/rc-core.h
++++ b/include/media/rc-core.h
+@@ -81,7 +81,6 @@ struct lirc_fh {
+ /**
+ * struct rc_dev - represents a remote control device
+ * @dev: driver model's view of this device
+- * @managed_alloc: devm_rc_allocate_device was used to create rc_dev
+ * @sysfs_groups: sysfs attribute groups
+ * @device_name: name of the rc child device
+ * @input_phys: physical path to the input child device
+@@ -157,7 +156,6 @@ struct lirc_fh {
+ */
+ struct rc_dev {
+ struct device dev;
+- bool managed_alloc;
+ const struct attribute_group *sysfs_groups[5];
+ const char *device_name;
+ const char *input_phys;
+--
+2.53.0
+
--- /dev/null
+From c4dab51a93d185c810698f405fe6816d9077e4f3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Apr 2026 23:03:09 +0200
+Subject: media: rc: ttusbir: fix inverted error logic
+
+From: Oliver Neukum <oneukum@suse.com>
+
+[ Upstream commit 646ebdd3105809d84ed04aa9e92e47e89cc44502 ]
+
+We have to report ENOMEM if no buffer is allocated.
+Typo dropped a "!". Restore it.
+
+Fixes: 50acaad3d202 ("media: rc: ttusbir: respect DMA coherency rules")
+Cc: stable@vger.kernel.org
+Signed-off-by: Oliver Neukum <oneukum@suse.com>
+Signed-off-by: Sean Young <sean@mess.org>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/media/rc/ttusbir.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c
+index a670d4b008cb0d..3452b5aefd2848 100644
+--- a/drivers/media/rc/ttusbir.c
++++ b/drivers/media/rc/ttusbir.c
+@@ -191,7 +191,7 @@ static int ttusbir_probe(struct usb_interface *intf,
+ tt = kzalloc(sizeof(*tt), GFP_KERNEL);
+ buffer = kzalloc(5, GFP_KERNEL);
+ rc = rc_allocate_device(RC_DRIVER_IR_RAW);
+- if (!tt || !rc || buffer) {
++ if (!tt || !rc || !buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+--
+2.53.0
+
--- /dev/null
+From 295402a6f7657051ac4775609e6dcb5b7803f8e2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 13:02:30 +0800
+Subject: mm/page_alloc: clear page->private in free_pages_prepare()
+
+From: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+
+[ Upstream commit ac1ea219590c09572ed5992dc233bbf7bb70fef9 ]
+
+Several subsystems (slub, shmem, ttm, etc.) use page->private but don't
+clear it before freeing pages. When these pages are later allocated as
+high-order pages and split via split_page(), tail pages retain stale
+page->private values.
+
+This causes a use-after-free in the swap subsystem. The swap code uses
+page->private to track swap count continuations, assuming freshly
+allocated pages have page->private == 0. When stale values are present,
+swap_count_continued() incorrectly assumes the continuation list is valid
+and iterates over uninitialized page->lru containing LIST_POISON values,
+causing a crash:
+
+ KASAN: maybe wild-memory-access in range [0xdead000000000100-0xdead000000000107]
+ RIP: 0010:__do_sys_swapoff+0x1151/0x1860
+
+Fix this by clearing page->private in free_pages_prepare(), ensuring all
+freed pages have clean state regardless of previous use.
+
+Link: https://lkml.kernel.org/r/20260207173615.146159-1-mikhail.v.gavrilov@gmail.com
+Fixes: 3b8000ae185c ("mm/vmalloc: huge vmalloc backing pages should be split rather than compound")
+Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
+Suggested-by: Zi Yan <ziy@nvidia.com>
+Acked-by: Zi Yan <ziy@nvidia.com>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Reviewed-by: Vlastimil Babka <vbabka@suse.cz>
+Cc: Brendan Jackman <jackmanb@google.com>
+Cc: Chris Li <chrisl@kernel.org>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Kairui Song <ryncsn@gmail.com>
+Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+[backport: context only]
+Signed-off-by: Li Wang <li.wang@windriver.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ mm/page_alloc.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mm/page_alloc.c b/mm/page_alloc.c
+index b617fb364b15d1..ffdc71103c2ad2 100644
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -1178,6 +1178,7 @@ static __always_inline bool free_pages_prepare(struct page *page,
+
+ page_cpupid_reset_last(page);
+ page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
++ page->private = 0;
+ reset_page_owner(page, order);
+ page_table_check_free(page, order);
+
+--
+2.53.0
+
--- /dev/null
+From 3245660a79bf938f31ad7ec45fe4a97ff0500401 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 14:09:45 +0800
+Subject: net: af_key: zero aligned sockaddr tail in PF_KEY exports
+
+From: Zhengchuan Liang <zcliangcn@gmail.com>
+
+[ Upstream commit 426c355742f02cf743b347d9d7dbdc1bfbfa31ef ]
+
+PF_KEY export paths use `pfkey_sockaddr_size()` when reserving sockaddr
+payload space, so IPv6 addresses occupy 32 bytes on the wire. However,
+`pfkey_sockaddr_fill()` initializes only the first 28 bytes of
+`struct sockaddr_in6`, leaving the final 4 aligned bytes uninitialized.
+
+Not every PF_KEY message is affected. The state and policy dump builders
+already zero the whole message buffer before filling the sockaddr
+payloads. Keep the fix to the export paths that still append aligned
+sockaddr payloads with plain `skb_put()`:
+
+ - `SADB_ACQUIRE`
+ - `SADB_X_NAT_T_NEW_MAPPING`
+ - `SADB_X_MIGRATE`
+
+Fix those paths by clearing only the aligned sockaddr tail after
+`pfkey_sockaddr_fill()`.
+
+Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
+Fixes: 08de61beab8a ("[PFKEYV2]: Extension for dynamic update of endpoint address(es)")
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Co-developed-by: Yuan Tan <yuantan098@gmail.com>
+Signed-off-by: Yuan Tan <yuantan098@gmail.com>
+Suggested-by: Xin Liu <bird@lzu.edu.cn>
+Tested-by: Xiao Liu <lx24@stu.ynu.edu.cn>
+Signed-off-by: Zhengchuan Liang <zcliangcn@gmail.com>
+Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
+Signed-off-by: Miles Wang <13621186580@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/key/af_key.c | 52 +++++++++++++++++++++++++++++++-----------------
+ 1 file changed, 34 insertions(+), 18 deletions(-)
+
+diff --git a/net/key/af_key.c b/net/key/af_key.c
+index 4849407da86d6e..486c9a2e1f52f3 100644
+--- a/net/key/af_key.c
++++ b/net/key/af_key.c
+@@ -757,6 +757,22 @@ static unsigned int pfkey_sockaddr_fill(const xfrm_address_t *xaddr, __be16 port
+ return 0;
+ }
+
++static unsigned int pfkey_sockaddr_fill_zero_tail(const xfrm_address_t *xaddr,
++ __be16 port,
++ struct sockaddr *sa,
++ unsigned short family)
++{
++ unsigned int prefixlen;
++ int sockaddr_len = pfkey_sockaddr_len(family);
++ int sockaddr_size = pfkey_sockaddr_size(family);
++
++ prefixlen = pfkey_sockaddr_fill(xaddr, port, sa, family);
++ if (sockaddr_size > sockaddr_len)
++ memset((u8 *)sa + sockaddr_len, 0, sockaddr_size - sockaddr_len);
++
++ return prefixlen;
++}
++
+ static struct sk_buff *__pfkey_xfrm_state2msg(const struct xfrm_state *x,
+ int add_keys, int hsc)
+ {
+@@ -3205,9 +3221,9 @@ static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct
+ addr->sadb_address_proto = 0;
+ addr->sadb_address_reserved = 0;
+ addr->sadb_address_prefixlen =
+- pfkey_sockaddr_fill(&x->props.saddr, 0,
+- (struct sockaddr *) (addr + 1),
+- x->props.family);
++ pfkey_sockaddr_fill_zero_tail(&x->props.saddr, 0,
++ (struct sockaddr *)(addr + 1),
++ x->props.family);
+ if (!addr->sadb_address_prefixlen)
+ BUG();
+
+@@ -3220,9 +3236,9 @@ static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct
+ addr->sadb_address_proto = 0;
+ addr->sadb_address_reserved = 0;
+ addr->sadb_address_prefixlen =
+- pfkey_sockaddr_fill(&x->id.daddr, 0,
+- (struct sockaddr *) (addr + 1),
+- x->props.family);
++ pfkey_sockaddr_fill_zero_tail(&x->id.daddr, 0,
++ (struct sockaddr *)(addr + 1),
++ x->props.family);
+ if (!addr->sadb_address_prefixlen)
+ BUG();
+
+@@ -3420,9 +3436,9 @@ static int pfkey_send_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr,
+ addr->sadb_address_proto = 0;
+ addr->sadb_address_reserved = 0;
+ addr->sadb_address_prefixlen =
+- pfkey_sockaddr_fill(&x->props.saddr, 0,
+- (struct sockaddr *) (addr + 1),
+- x->props.family);
++ pfkey_sockaddr_fill_zero_tail(&x->props.saddr, 0,
++ (struct sockaddr *)(addr + 1),
++ x->props.family);
+ if (!addr->sadb_address_prefixlen)
+ BUG();
+
+@@ -3442,9 +3458,9 @@ static int pfkey_send_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr,
+ addr->sadb_address_proto = 0;
+ addr->sadb_address_reserved = 0;
+ addr->sadb_address_prefixlen =
+- pfkey_sockaddr_fill(ipaddr, 0,
+- (struct sockaddr *) (addr + 1),
+- x->props.family);
++ pfkey_sockaddr_fill_zero_tail(ipaddr, 0,
++ (struct sockaddr *)(addr + 1),
++ x->props.family);
+ if (!addr->sadb_address_prefixlen)
+ BUG();
+
+@@ -3473,15 +3489,15 @@ static int set_sadb_address(struct sk_buff *skb, int sasize, int type,
+ switch (type) {
+ case SADB_EXT_ADDRESS_SRC:
+ addr->sadb_address_prefixlen = sel->prefixlen_s;
+- pfkey_sockaddr_fill(&sel->saddr, 0,
+- (struct sockaddr *)(addr + 1),
+- sel->family);
++ pfkey_sockaddr_fill_zero_tail(&sel->saddr, 0,
++ (struct sockaddr *)(addr + 1),
++ sel->family);
+ break;
+ case SADB_EXT_ADDRESS_DST:
+ addr->sadb_address_prefixlen = sel->prefixlen_d;
+- pfkey_sockaddr_fill(&sel->daddr, 0,
+- (struct sockaddr *)(addr + 1),
+- sel->family);
++ pfkey_sockaddr_fill_zero_tail(&sel->daddr, 0,
++ (struct sockaddr *)(addr + 1),
++ sel->family);
+ break;
+ default:
+ return -EINVAL;
+--
+2.53.0
+
--- /dev/null
+From 8372446a216ced29b6cee5b08567fe4d3519f67f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 28 May 2026 23:06:58 -0700
+Subject: perf: Fix dangling cgroup pointer in cpuctx
+
+From: Yeoreum Yun <yeoreum.yun@arm.com>
+
+[ Upstream commit 3b7a34aebbdf2a4b7295205bf0c654294283ec82 ]
+
+Commit a3c3c6667("perf/core: Fix child_total_time_enabled accounting
+bug at task exit") moves the event->state update to before
+list_del_event(). This makes the event->state test in list_del_event()
+always false; never calling perf_cgroup_event_disable().
+
+As a result, cpuctx->cgrp won't be cleared properly; causing havoc.
+
+Fixes: a3c3c6667("perf/core: Fix child_total_time_enabled accounting bug at task exit")
+Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Tested-by: David Wang <00107082@163.com>
+Link: https://lore.kernel.org/all/aD2TspKH%2F7yvfYoO@e129823.arm.com/
+Signed-off-by: Ian Klatzco <iklatzco@gmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ kernel/events/core.c | 16 ++++------------
+ 1 file changed, 4 insertions(+), 12 deletions(-)
+
+diff --git a/kernel/events/core.c b/kernel/events/core.c
+index eba5eb6fcb8762..a4187dea6402a7 100644
+--- a/kernel/events/core.c
++++ b/kernel/events/core.c
+@@ -2056,18 +2056,6 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx)
+ if (event->group_leader == event)
+ del_event_from_groups(event, ctx);
+
+- /*
+- * If event was in error state, then keep it
+- * that way, otherwise bogus counts will be
+- * returned on read(). The only way to get out
+- * of error state is by explicit re-enabling
+- * of the event
+- */
+- if (event->state > PERF_EVENT_STATE_OFF) {
+- perf_cgroup_event_disable(event, ctx);
+- perf_event_set_state(event, PERF_EVENT_STATE_OFF);
+- }
+-
+ ctx->generation++;
+ event->pmu_ctx->nr_events--;
+ }
+@@ -2401,6 +2389,10 @@ __perf_remove_from_context(struct perf_event *event,
+ state = PERF_EVENT_STATE_DEAD;
+ }
+ event_sched_out(event, ctx);
++
++ if (event->state > PERF_EVENT_STATE_OFF)
++ perf_cgroup_event_disable(event, ctx);
++
+ perf_event_set_state(event, min(event->state, state));
+ if (flags & DETACH_GROUP)
+ perf_group_detach(event);
+--
+2.53.0
+
ipv6-fix-possible-infinite-loop-in-rt6_fill_node.patch
ipv6-fix-possible-infinite-loop-in-fib6_select_path.patch
net-skbuff-fix-pskb_carve-leaking-zcopy-pages.patch
+perf-fix-dangling-cgroup-pointer-in-cpuctx.patch
+batman-adv-v-stop-ogmv2-on-disabled-interface.patch
+batman-adv-tvlv-abort-ogm-send-on-tvlv-append-failur.patch
+batman-adv-tt-reject-oversized-local-tvlv-buffers.patch
+batman-adv-bla-avoid-null-ptr-deref-for-claim-via-dr.patch
+batman-adv-tvlv-reject-oversized-tvlv-packets.patch
+batman-adv-iv-recover-ogm-scheduling-after-forward-p.patch
+batman-adv-tp_meter-avoid-role-confusion-in-tp_list.patch
+net-af_key-zero-aligned-sockaddr-tail-in-pf_key-expo.patch
+batman-adv-tp_meter-directly-shut-down-timer-on-clea.patch
+batman-adv-tt-fix-toctou-race-for-reported-vlans.patch
+batman-adv-tt-avoid-empty-vlan-responses.patch
+batman-adv-bla-avoid-double-decrement-of-bla.num_req.patch
+mm-page_alloc-clear-page-private-in-free_pages_prepa.patch
+media-rc-fix-race-between-unregister-and-urb-irq-cal.patch
+media-rc-ttusbir-fix-inverted-error-logic.patch
+drm-fbdev-helper-set-and-clear-vga-switcheroo-client.patch
+drm-fbcon-vga_switcheroo-avoid-race-condition-in-fbc.patch
+inet-frags-add-inet_frag_queue_flush.patch
+inet-frags-flush-pending-skbs-in-fqdir_pre_exit.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-dp-add-edp-1.5-bit-definition.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch
--- /dev/null
+From 8d7a860d1781bcc6bc2ff36ea8c3c19078ae5bf5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:38:35 +0300
+Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD
+ register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 upstream.
+
+EDP specification says:
+
+"If either VSC SDP is unable to be transmitted 100 ns before the SU region,
+the Source device may optionally transmit the VSC SDP during the prior
+video scan line’s HBlank period There is a Intel specific drm dp register
+currently containing bits related how TCON can support PSR2 with SDP on
+prior line."
+
+Unfortunately many panels are having problems in implementing this. So
+there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to
+figure out if this is properly implemented on a panel or if panel doesn't
+require that 100 ns delay before the SU region. Here are the definitions in
+this custom DPCD address:
+
+0 = Panel doesn't support SDP on prior line
+1 = Panel supports SDP on prior line
+2 = Panel doesn't have 100ns requirement
+3 = Reserved
+
+Add definitions for this new register and it's values into new header
+intel_dpcd.h.
+
+v2: add INTEL_DPCD_ prefix to definitions
+
+Bspec: 74741
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com
+(cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+ create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h
+
+diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h
+new file mode 100644
+index 00000000000000..4aea5326f2ed48
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dpcd.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2026 Intel Corporation
++ */
++
++#ifndef __INTEL_DPCD_H__
++#define __INTEL_DPCD_H__
++
++#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0)
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1
++# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2
++
++#endif /* __INTEL_DPCD_H__ */
+--
+2.53.0
+
--- /dev/null
+From 6dddf4c53e8a54295d7b483c05bd72f8f196d7d3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:38:37 +0300
+Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit 4703049f768fc1c1caac754134118bee1a3af189 upstream.
+
+There is Intel specific workaround DPCD address containing workaround for
+case where SDP is on prior line. Apply this workaround according to values
+in the offset.
+
+Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline")
+Cc: <stable@vger.kernel.org> # v5.15+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com
+(cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_psr.c | 35 +++++++++++++++++++++---
+ 1 file changed, 31 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index aa2ef49afa67a9..6709c434beb990 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -1357,9 +1357,35 @@ static bool psr2_granularity_check(struct intel_crtc_state *crtc_state,
+ return true;
+ }
+
+-static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp,
+- struct intel_crtc_state *crtc_state)
++static bool apply_scanline_indication_wa(struct intel_crtc_state *crtc_state,
++ struct intel_connector *connector)
+ {
++ struct intel_dp *intel_dp = intel_attached_dp(connector);
++ u8 early_scanline_support = connector->dp.psr_caps.intel_wa_dpcd &
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK;
++
++ if (intel_dp->edp_dpcd[0] >= DP_EDP_15)
++ return true;
++
++ switch (early_scanline_support) {
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return false;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE:
++ return true;
++ case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE:
++ crtc_state->req_psr2_sdp_prior_scanline = false;
++ return true;
++ default:
++ MISSING_CASE(early_scanline_support);
++ return false;
++ }
++}
++
++static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_crtc_state *crtc_state,
++ struct intel_connector *connector)
++{
++ struct intel_dp *intel_dp = intel_attached_dp(connector);
+ struct intel_display *display = to_intel_display(intel_dp);
+ const struct drm_display_mode *adjusted_mode = &crtc_state->uapi.adjusted_mode;
+ u32 hblank_total, hblank_ns, req_ns;
+@@ -1378,7 +1404,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d
+ return false;
+
+ crtc_state->req_psr2_sdp_prior_scanline = true;
+- return true;
++
++ return apply_scanline_indication_wa(crtc_state, connector);
+ }
+
+ static int intel_psr_entry_setup_frames(struct intel_dp *intel_dp,
+@@ -1660,7 +1687,7 @@ static bool intel_sel_update_config_valid(struct intel_crtc_state *crtc_state,
+ conn_state))
+ goto unsupported;
+
+- if (!_compute_psr2_sdp_prior_scanline_indication(intel_dp, crtc_state)) {
++ if (!_compute_psr2_sdp_prior_scanline_indication(crtc_state, connector)) {
+ drm_dbg_kms(display->drm,
+ "Selective update not enabled, SDP indication do not fit in hblank\n");
+ goto unsupported;
+--
+2.53.0
+
--- /dev/null
+From baa645c232de6d5e84293966ab6c28cb861f8fa2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 29 May 2026 12:38:36 +0300
+Subject: drm/i915/psr: Read Intel DPCD workaround register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Jouni Högander <jouni.hogander@intel.com>
+
+commit f30bece421a4ae34359254e1dc2a187a42b6af9b upstream.
+
+Read Intel DPCD workaround register and store it into
+intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains
+only PSR workaround for PSR2 SDP on prior scanline implementation.
+
+Signed-off-by: Jouni Högander <jouni.hogander@intel.com>
+Reviewed-by: Suraj Kandpal <suraj.kandpal@intel.com>
+Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com
+(cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c)
+Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/gpu/drm/i915/display/intel_display_types.h | 1 +
+ drivers/gpu/drm/i915/display/intel_psr.c | 9 ++++++++-
+ 2 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index ced0e5a5989b85..3596ed0ff151bf 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -583,6 +583,7 @@ struct intel_connector {
+
+ struct {
+ u8 dpcd[EDP_PSR_RECEIVER_CAP_SIZE];
++ u8 intel_wa_dpcd;
+
+ bool support;
+ bool su_support;
+diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
+index 8a7075c4a2480f..aa2ef49afa67a9 100644
+--- a/drivers/gpu/drm/i915/display/intel_psr.c
++++ b/drivers/gpu/drm/i915/display/intel_psr.c
+@@ -43,6 +43,7 @@
+ #include "intel_display_utils.h"
+ #include "intel_dmc.h"
+ #include "intel_dp.h"
++#include "intel_dpcd.h"
+ #include "intel_dp_aux.h"
+ #include "intel_dsb.h"
+ #include "intel_frontbuffer.h"
+@@ -708,8 +709,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co
+ connector->dp.psr_caps.su_support ? "" : "not ");
+ }
+
+- if (connector->dp.psr_caps.su_support)
++ if (connector->dp.psr_caps.su_support) {
++ ret = drm_dp_dpcd_read_byte(&intel_dp->aux,
++ INTEL_DPCD_INTEL_WA_REGISTER_CAPS,
++ &connector->dp.psr_caps.intel_wa_dpcd);
++ if (ret < 0)
++ return;
+ _psr_compute_su_granularity(intel_dp, connector);
++ }
+ }
+
+ void intel_psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector)
+--
+2.53.0
+
--- /dev/null
+From 932b19138ce89cb0bb2f4e94f26f022114d0a2bd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 20:50:43 +0000
+Subject: Revert "x86/fpu: Refine and simplify the magic number check during
+ signal return"
+
+From: Andrei Vagin <avagin@google.com>
+
+[ Upstream commit 44eeff9bc467bc7d1fec34fc3f6001f385fe462c ]
+
+This reverts
+
+ dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").
+
+The aforementioned commit broke applications that construct signal frames in
+userspace (such as CRIU and gVisor) if the frame's xstate size is smaller than
+the kernel's fpstate->user_size.
+
+Furthermore, this introduces a critical issue for checkpoint/restore tools
+like CRIU. If a process is checkpointed while inside a signal handler, its
+stack contains a signal frame formatted according to the source host's xstate
+capabilities.
+
+If that process is later restored on a destination host with larger xstate
+capabilities (e.g., a newer CPU with more features enabled, resulting in
+a larger fpstate->user_size), the kernel will look for FP_XSTATE_MAGIC2 at the
+destination host's larger user_size offset instead of the offset encoded in
+the frame's fx_sw->xstate_size.
+
+This causes the magic2 check to fail, forcing sigreturn to silently fall back
+to "FX-only" mode. Upon return from the signal handler, the process's extended
+state is reset to initial values instead of being restored, leading to silent
+data corruption.
+
+The aforementioned commit cited
+
+ d877550eaf2d ("x86/fpu: Stop relying on userspace for info to fault in xsave buffer")
+
+as justification to stop relying on userspace for the magic number check.
+
+However, these two changes are fundamentally different. The last one only
+changed how much memory the kernel ensures is paged-in before running XRSTOR
+to prevent an infinite loop. It did not change the signal frame format or how
+the layout is validated.
+
+Reverting this change restores the use of fx_sw->xstate_size for
+locating magic2 and restores the necessary sanity checks, ensuring that
+the signal frame remains self-describing and portable.
+
+ [ bp: Massage commit message. ]
+
+Fixes: dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return")
+Signed-off-by: Andrei Vagin <avagin@google.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Acked-by: Chang S. Bae <chang.seok.bae@intel.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/all/20260429000623.3356606-1-avagin@google.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/x86/kernel/fpu/signal.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
+index c3ec2512f2bbe4..20b638c507ca2d 100644
+--- a/arch/x86/kernel/fpu/signal.c
++++ b/arch/x86/kernel/fpu/signal.c
+@@ -27,14 +27,19 @@
+ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
+ struct _fpx_sw_bytes *fx_sw)
+ {
++ int min_xstate_size = sizeof(struct fxregs_state) +
++ sizeof(struct xstate_header);
+ void __user *fpstate = fxbuf;
+ unsigned int magic2;
+
+ if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
+ return false;
+
+- /* Check for the first magic field */
+- if (fx_sw->magic1 != FP_XSTATE_MAGIC1)
++ /* Check for the first magic field and other error scenarios. */
++ if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
++ fx_sw->xstate_size < min_xstate_size ||
++ fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
++ fx_sw->xstate_size > fx_sw->extended_size)
+ goto setfx;
+
+ /*
+@@ -43,7 +48,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
+ * fpstate layout with out copying the extended state information
+ * in the memory layout.
+ */
+- if (__get_user(magic2, (__u32 __user *)(fpstate + x86_task_fpu(current)->fpstate->user_size)))
++ if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
+ return false;
+
+ if (likely(magic2 == FP_XSTATE_MAGIC2))
+--
+2.53.0
+
ipv6-fix-possible-infinite-loop-in-fib6_select_path.patch
net-skbuff-fix-pskb_carve-leaking-zcopy-pages.patch
revert-ipv6-preserve-insertion-order-for-same-scope-.patch
+revert-x86-fpu-refine-and-simplify-the-magic-number-.patch
+drm-i915-psr-add-defininitions-for-intel_wa_register.patch
+drm-i915-psr-read-intel-dpcd-workaround-register.patch
+drm-i915-psr-apply-intel-dpcd-workaround-when-sdp-on.patch