* [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm()
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
In-Reply-To: <20260603064659.3867-1-petr.wozniak@gmail.com>
This series fixes how the async crypto path (-EINPROGRESS from ->xmit())
is handled in validate_xmit_xfrm() and its callers.
Patch 1 (previously sent on its own, v1-v4) makes validate_xmit_xfrm()
return ERR_PTR(-EINPROGRESS) instead of NULL when a packet is stolen by
async crypto, so __dev_queue_xmit() can tell it apart from a real drop
and stop reporting -ENOMEM on noqueue/bridge interfaces. v5 also covers
the GSO segment loop, as Sabrina pointed out.
Patch 2 fixes a use-after-free found while looking at that GSO loop:
validate_xmit_xfrm() unlinks async-stolen segments but never updates the
list head ->prev, which validate_xmit_skb_list() later dereferences.
Changes in v5:
- 1/2: also propagate ERR_PTR(-EINPROGRESS) from the GSO segment loop
(the 2nd ->xmit() call); v4 only handled the single-skb path. Restore
the blank line in validate_xmit_skb_list(). Add the missing
maintainers to Cc. (Sabrina Dubroca)
- 2/2: new patch -- fix the stale skb->prev use-after-free (also flagged
by Sashiko)
Changes in v4:
- Drop bool stolen tracking and the ERR_PTR return in
validate_xmit_skb_list(); use IS_ERR_OR_NULL() so stolen skbs are
silently skipped (Sabrina Dubroca)
- Drop ERR_PTR(-EINPROGRESS) handling in __dev_direct_xmit() (Sabrina Dubroca)
- Move validate_xmit_skb() return-value comment above the function
(Sabrina Dubroca)
Changes in v3:
- validate_xmit_skb_list(): set stolen=true only for -EINPROGRESS
(Sabrina Dubroca)
Changes in v2:
- Reset rc to NET_XMIT_SUCCESS only when PTR_ERR(skb) == -EINPROGRESS
(Sabrina Dubroca)
Petr Wozniak (2):
xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
xfrm: fix stale skb->prev after async crypto steals a GSO segment
net/core/dev.c | 10 ++++++++--
net/xfrm/xfrm_device.c | 12 ++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
--
2.51.0
^ permalink raw reply
* [PATCH net v5 1/2] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
In-Reply-To: <20260621100327.40203-1-petr.wozniak@gmail.com>
validate_xmit_xfrm() returns NULL both when a packet is dropped and
when it is stolen by async crypto (-EINPROGRESS from ->xmit()).
Callers cannot distinguish the two cases.
f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
changed the semantics of a NULL return from "dropped" to "stolen or
dropped", but __dev_queue_xmit() was not updated. On virtual/bridge
interfaces (noqueue qdisc) __dev_queue_xmit() initialises rc=-ENOMEM
and jumps to out: when skb is NULL, returning -ENOMEM to the caller
even though the packet will be delivered correctly via xfrm_dev_resume().
Return ERR_PTR(-EINPROGRESS) from validate_xmit_xfrm() for the async
case so callers can tell it apart from a real drop. Update
__dev_queue_xmit() to handle ERR_PTR(-EINPROGRESS) from
validate_xmit_skb() correctly. Update validate_xmit_skb_list() to
use IS_ERR_OR_NULL() so that ERR_PTR(-EINPROGRESS) is not mistakenly
added to the transmitted list.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Suggested-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
net/core/dev.c | 10 ++++++++--
net/xfrm/xfrm_device.c | 4 ++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 5c01dfaa6c44..f7ffc4d29597 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4018,6 +4018,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
return NULL;
}
+/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
+ * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
+ */
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
netdev_features_t features;
@@ -4089,7 +4092,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
skb->prev = skb;
skb = validate_xmit_skb(skb, dev, again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb))
continue;
if (!head)
@@ -4860,8 +4863,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
goto recursion_alert;
skb = validate_xmit_skb(skb, dev, &again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb)) {
+ if (PTR_ERR(skb) == -EINPROGRESS)
+ rc = NET_XMIT_SUCCESS;
goto out;
+ }
HARD_TX_LOCK(dev, txq, cpu);
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 630f3dd31cc5..19c77f09acc9 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
- return NULL;
+ return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
@@ -224,7 +224,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
- return skb;
+ return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.51.0
^ permalink raw reply related
* [PATCH net v5 2/2] xfrm: fix stale skb->prev after async crypto steals a GSO segment
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
In-Reply-To: <20260621100327.40203-1-petr.wozniak@gmail.com>
skb_gso_segment() leaves the segment list head with ->prev pointing at
the last segment, an invariant validate_xmit_skb_list() relies on when
it sets its tail pointer (tail = skb->prev).
When validate_xmit_xfrm() walks a GSO list and some segments are stolen
by async crypto (->xmit() returns -EINPROGRESS), those segments are
unlinked from the list but the head ->prev is never updated. If the
last segment is the one stolen, the returned head still has ->prev
pointing at it, even though it is now owned by the crypto engine and may
be freed. validate_xmit_skb_list() later does tail->next = skb, writing
through that stale pointer -- a use-after-free.
Repoint skb->prev at the last retained segment before returning.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
net/xfrm/xfrm_device.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 19c77f09acc9..aec1e1184a71 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -224,6 +224,14 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
+ /* skb_gso_segment() set skb->prev to the last segment, but async
+ * crypto may have stolen it above without updating ->prev. Repoint
+ * it at the last retained segment so validate_xmit_skb_list() does
+ * not chain onto a segment now owned by the crypto engine.
+ */
+ if (skb)
+ skb->prev = pskb;
+
return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.51.0
^ permalink raw reply related
* Re: [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
From: Petr Wozniak @ 2026-06-21 10:06 UTC (permalink / raw)
To: netdev, sd
Cc: steffen.klassert, herbert, kuba, horms, pabeni, edumazet, davem
In-Reply-To: <20260603064659.3867-1-petr.wozniak@gmail.com>
Reposting on the list, as you asked.
Apologies for missing your comment about the 2nd x->type_offload->xmit()
call across several versions -- entirely my fault.
You're right: in the skb_list_walk_safe() loop, if all GSO segments
return -EINPROGRESS, skb is advanced to NULL and the function returns
NULL instead of ERR_PTR(-EINPROGRESS). v5 1/2 fixes it:
- return skb;
+ return skb ? skb : ERR_PTR(-EINPROGRESS);
At that point NULL can only mean all segments were stolen -- the error
path (err != -EINPROGRESS) returns NULL directly from inside the loop.
v5 1/2 also restores the blank line in validate_xmit_skb_list() and adds
the missing maintainers to Cc.
For the use-after-free I mentioned: I confirmed it. validate_xmit_xfrm()
unlinks async-stolen segments but never updates the list head ->prev, so
when the last segment is stolen, validate_xmit_skb_list() chains onto it
via tail->next. v5 2/2 fixes it by repointing skb->prev at the last
retained segment. As you suggested, the two fixes go as a series.
I could not confirm the head-list leak on a closer look, so I left it
out; I'll send a separate patch if I find it.
The v5 series has been sent.
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH v2] net: add sock_open() with flags for socket creation
From: Alex Goltsev @ 2026-06-21 11:05 UTC (permalink / raw)
To: davem, netdev; +Cc: linux-kernel, Al Viro
In-Reply-To: <CAEKmD4KSvAGWEod3h8mPKQ-UYhKqakxfakt4gXrsU8sWuAO77g@mail.gmail.com>
From a9316957e594708dfb4258ad968fe88666c9b736 Mon Sep 17 00:00:00 2001
From: 0-x-0-0 <sasha.goltsev777@gmail.com>
Date: Sun, 21 Jun 2026 13:24:29 +0300
Subject: [PATCH v2] net: add sock_open() with flags for socket creation
---
Changes in V2:
- Replaced the use of plain integer constants for flags with proper
enums to improve readability and type safety.
- `sock_open` is intentionally left as a regular exported symbol rather
than being moved to a header as `static inline`. This is because it
dereferences `current->nsproxy->net_ns`, which would require pulling
in heavy headers like <linux/sched.h> and <linux/nsproxy.h> into the
already widely-used <linux/net.h>, causing unnecessary header bloat
and potential circular dependencies.
- Introduced two new creation flags for specialized use cases within
kernel modules:
* SOCK_CREATE_NOLSM: This flag allows a kernel module to bypass
LSM hooks during socket creation. This
enables a micro-optimization for kernel-internal sockets where
the security check is known *a priori* to be a no-op (e.g., for
specific configurations or high-performance paths).
This is safe because the API is restricted to in-kernel (LKM)
contexts only, and does not weaken the security boundary for
user-triggered socket creation.
* SOCK_CREATE_NOWARN: This flag suppresses the standard warning
messages on creation failure. This is useful for callers in the
kernel that probe for protocol support and handle the error
gracefully, without wanting to pollute the kernel log with
misleading warnings.
Signed-off-by: Alexander Goltsev <sasha.goltsev777@gmail.com>
---
include/linux/net.h | 22 ++++++++
net/socket.c | 133 +++++++++++++++++++++++++++++++++++++-------
2 files changed, 134 insertions(+), 21 deletions(-)
diff --git a/include/linux/net.h b/include/linux/net.h
index f268f395c..6367c00db 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -116,6 +116,22 @@ enum sock_shutdown_cmd {
SHUT_RDWR,
};
+/**
+ * enum sock_create_flags - socket creation flags
+ * @SOCK_CREATE_KERN: creates a kernel socket
+ * @SOCK_CREATE_USER: creates a regular socket
+ * @SOCK_CREATE_LITE: creates a lite socket
+ * @SOCK_CREATE_NOLSM: disables LSM
+ * @SOCK_CREATE_NOWARN: disables warning
+ */
+enum sock_create_flags {
+ SOCK_CREATE_KERN = BIT(0),
+ SOCK_CREATE_USER = BIT(1),
+ SOCK_CREATE_LITE = BIT(2),
+ SOCK_CREATE_NOLSM = BIT(3),
+ SOCK_CREATE_NOWARN = BIT(4),
+};
+
struct socket_wq {
/* Note: wait MUST be first field of socket_wq */
wait_queue_head_t wait;
@@ -275,6 +291,12 @@ void sock_unregister(int family);
bool sock_is_registered(int family);
int __sock_create(struct net *net, int family, int type, int proto,
struct socket **res, int kern);
+int __sock_create_flags(struct net *net, int family, int type, int protocol,
+ struct socket **res, int kern, int flags);
+int __sock_create_lite_flags(int family, int type, int protocol,
+ struct socket **res, int flags);
+int sock_open(struct net *net, int family,
+ int type, int protocol, struct socket **res, int flags);
int sock_create(int family, int type, int proto, struct socket **res);
int sock_create_kern(struct net *net, int family, int type, int proto,
struct socket **res);
int sock_create_lite(int family, int type, int proto, struct socket **res);
diff --git a/net/socket.c b/net/socket.c
index 63c69a0fa..2359fd5bf 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1425,26 +1425,28 @@ static long sock_ioctl(struct file *file,
unsigned cmd, unsigned long arg)
}
/**
- * sock_create_lite - creates a socket
+ * __sock_create_lite_flags - creates a socket (with flags)
* @family: protocol family (AF_INET, ...)
* @type: communication type (SOCK_STREAM, ...)
* @protocol: protocol (0, ...)
* @res: new socket
+ * @flags: defines socket creation flags
*
* Creates a new socket and assigns it to @res, passing through LSM.
* The new socket initialization is not complete, see kernel_accept().
* Returns 0 or an error. On failure @res is set to %NULL.
* This function internally uses GFP_KERNEL.
*/
-
-int sock_create_lite(int family, int type, int protocol, struct socket **res)
+int __sock_create_lite_flags(int family, int type, int protocol,
struct socket **res, int flags)
{
int err;
struct socket *sock = NULL;
- err = security_socket_create(family, type, protocol, 1);
- if (err)
- goto out;
+ if (!(flags & SOCK_CREATE_NOLSM)) {
+ err = security_socket_create(family, type, protocol, 1);
+ if (err)
+ goto out;
+ }
sock = sock_alloc();
if (!sock) {
@@ -1453,9 +1455,11 @@ int sock_create_lite(int family, int type, int
protocol, struct socket **res)
}
sock->type = type;
- err = security_socket_post_create(sock, family, type, protocol, 1);
- if (err)
- goto out_release;
+ if (!(flags & SOCK_CREATE_NOLSM)) {
+ err = security_socket_post_create(sock, family, type, protocol, 1);
+ if (err)
+ goto out_release;
+ }
out:
*res = sock;
@@ -1465,6 +1469,25 @@ int sock_create_lite(int family, int type, int
protocol, struct socket **res)
sock = NULL;
goto out;
}
+EXPORT_SYMBOL(__sock_create_lite_flags);
+
+/**
+ * sock_create_lite - creates a socket
+ * @family: protocol family (AF_INET, ...)
+ * @type: communication type (SOCK_STREAM, ...)
+ * @protocol: protocol (0, ...)
+ * @res: new socket
+ *
+ * Creates a new socket and assigns it to @res, passing through LSM.
+ * The new socket initialization is not complete, see kernel_accept().
+ * Returns 0 or an error. On failure @res is set to %NULL.
+ * This function internally uses GFP_KERNEL.
+ */
+
+int sock_create_lite(int family, int type, int protocol, struct socket **res)
+{
+ return __sock_create_lite_flags(family, type, protocol, res, 0);
+}
EXPORT_SYMBOL(sock_create_lite);
/* No kernel lock held - perfect */
@@ -1563,22 +1586,23 @@ int sock_wake_async(struct socket_wq *wq, int
how, int band)
EXPORT_SYMBOL(sock_wake_async);
/**
- * __sock_create - creates a socket
+ * __sock_create_flags - creates a socket (with flags)
* @net: net namespace
* @family: protocol family (AF_INET, ...)
* @type: communication type (SOCK_STREAM, ...)
* @protocol: protocol (0, ...)
* @res: new socket
* @kern: boolean for kernel space sockets
+ * @flags: defines socket creation flags
*
* Creates a new socket and assigns it to @res, passing through LSM.
* Returns 0 or an error. On failure @res is set to %NULL. @kern must
* be set to true if the socket resides in kernel space.
* This function internally uses GFP_KERNEL.
*/
-
-int __sock_create(struct net *net, int family, int type, int protocol,
- struct socket **res, int kern)
+int __sock_create_flags(struct net *net, int family,
+ int type, int protocol, struct socket **res,
+ int kern, int flags)
{
int err;
struct socket *sock;
@@ -1598,14 +1622,18 @@ int __sock_create(struct net *net, int family,
int type, int protocol,
deadlock in module load.
*/
if (family == PF_INET && type == SOCK_PACKET) {
- pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
+ if (!(flags & SOCK_CREATE_NOWARN)) {
+ pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
current->comm);
+ }
family = PF_PACKET;
}
- err = security_socket_create(family, type, protocol, kern);
- if (err)
- return err;
+ if (!(flags & SOCK_CREATE_NOLSM)) {
+ err = security_socket_create(family, type, protocol, kern);
+ if (err)
+ return err;
+ }
/*
* Allocate the socket and allow the family to set things up. if
@@ -1614,7 +1642,8 @@ int __sock_create(struct net *net, int family,
int type, int protocol,
*/
sock = sock_alloc();
if (!sock) {
- net_warn_ratelimited("socket: no more sockets\n");
+ if (!(flags & SOCK_CREATE_NOWARN))
+ net_warn_ratelimited("socket: no more sockets\n");
return -ENFILE; /* Not exactly a match, but its the
closest posix thing */
}
@@ -1671,9 +1700,12 @@ int __sock_create(struct net *net, int family,
int type, int protocol,
* module can have its refcnt decremented
*/
module_put(pf->owner);
- err = security_socket_post_create(sock, family, type, protocol, kern);
- if (err)
- goto out_sock_release;
+
+ if (!(flags & SOCK_CREATE_NOLSM)) {
+ err = security_socket_post_create(sock, family, type, protocol, kern);
+ if (err)
+ goto out_sock_release;
+ }
*res = sock;
return 0;
@@ -1691,6 +1723,28 @@ int __sock_create(struct net *net, int family,
int type, int protocol,
rcu_read_unlock();
goto out_sock_release;
}
+EXPORT_SYMBOL(__sock_create_flags);
+
+/**
+ * __sock_create - creates a socket
+ * @net: net namespace
+ * @family: protocol family (AF_INET, ...)
+ * @type: communication type (SOCK_STREAM, ...)
+ * @protocol: protocol (0, ...)
+ * @res: new socket
+ * @kern: boolean for kernel space sockets
+ *
+ * Creates a new socket and assigns it to @res, passing through LSM.
+ * Returns 0 or an error. On failure @res is set to %NULL. @kern must
+ * be set to true if the socket resides in kernel space.
+ * This function internally uses GFP_KERNEL.
+ */
+
+int __sock_create(struct net *net, int family, int type, int protocol,
+ struct socket **res, int kern)
+{
+ return __sock_create_flags(net, family, type, protocol, res, kern, 0);
+}
EXPORT_SYMBOL(__sock_create);
/**
@@ -1710,6 +1764,43 @@ int sock_create(int family, int type, int
protocol, struct socket **res)
}
EXPORT_SYMBOL(sock_create);
+/**
+ * sock_open - creates a socket (with flags)
+ * @net: net namespace (may be NULL in non-SOCK_CREATE_KERN modes)
+ * @family: protocol family (AF_INET, ...)
+ * @type: communication type (SOCK_STREAM, ...)
+ * @protocol: protocol (0, ...)
+ * @res: new socket
+ * @flags: socket creation flags
+ *
+ * Unified entry point for socket creation with flags.
+ * Returns 0 or an error. This function internally uses GFP_KERNEL.
+ */
+int sock_open(struct net *net, int family,
+ int type, int protocol, struct socket **res,
+ int flags)
+{
+ int type_bits = flags & (SOCK_CREATE_KERN | SOCK_CREATE_USER |
SOCK_CREATE_LITE);
+ int optional_flags = flags & ~(SOCK_CREATE_KERN | SOCK_CREATE_USER |
SOCK_CREATE_LITE);
+
+ if (type_bits == 0 || (type_bits & (type_bits - 1)) != 0)
+ return -EINVAL;
+
+ if (optional_flags & ~(SOCK_CREATE_NOLSM | SOCK_CREATE_NOWARN))
+ return -EINVAL;
+
+ switch (type_bits) {
+ case SOCK_CREATE_KERN: return __sock_create_flags(net, family, type, protocol,
+ res, 1, optional_flags);
+ case SOCK_CREATE_USER: return __sock_create_flags(current->nsproxy->net_ns,
+ family, type, protocol, res, 0, optional_flags);
+ case SOCK_CREATE_LITE: return __sock_create_lite_flags(family,
+ type, protocol, res, optional_flags);
+ default: return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(sock_open);
+
/**
* sock_create_kern - creates a socket (kernel space)
* @net: net namespace
--
2.47.3
^ permalink raw reply related
* Fwd: HTML message rejected: qcom_ppe: direct (non-DSA) MAC port - host-bound RX dropped at EDMA (src_info_type != PORTID)?
From: perceival percy @ 2026-06-21 11:18 UTC (permalink / raw)
Cc: netdev
In-Reply-To: <1782040471-27241-mlmmj-64bd9d5a@vger.kernel.org>
Hi Luo Jie, netdev,
I'm porting the GL.iNet GL-BE9300 (IPQ5332) to mainline OpenWrt and have run
into what looks like a gap in qcom_ppe for a direct (non-DSA) MAC port. The
question is at the bottom; I'm happy to provide the full DT, register dumps, or
test patches.
Setup
-----
SoC: IPQ5332, kernel 6.12, drivers/net/ethernet/qualcomm/ppe. Two ports:
- port@1: 10G SerDes, DSA conduit to an external RTL8372N switch (eth0). RX
works fine; the DSA tagger demuxes the switch ports.
- port@2: 2.5G, direct on-board RTL8221B PHY (phy-mode = "2500base-x"), used as
a standalone L3 "WAN" (eth1, no bridge, no DSA). RX is broken.
Symptom (port@2)
----------------
Link comes up at 2.5G/full. The MAC receives - the PPE ingress counters
advance:
PORT_RX_CNT ... 4/1(port=0002)
VPORT_RX_CNT ... 4/0(port=0002)
...but eth1 rx_packets stays 0, and the EDMA RX-ring error stat shows the drop:
/sys/kernel/debug/ppe/edma/stats/rx_ring_stats:
rxdesc[15]:src_port_inval_type = 1
So the frame reaches the EDMA RX descriptor ring, but in edma_rx_get_src_dev()
the source is not tagged as PORTID:
if ((src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK) ==
EDMA_RXDESC_SRCINFO_TYPE_PORTID) {
src_port_num = src_info & EDMA_RXDESC_PORTNUM_BITS;
...
} else {
... ++rxdesc_stats->src_port_inval_type; /* port@2: type == 0 */
return NULL; /* dropped */
}
so the netdev lookup never runs.
What I have checked
-------------------
- netdev_arr[port_id - 1] is populated for port@2 (edma_port_setup), so the
PORTID -> netdev mapping would resolve if the type were PORTID.
- port@2's L2 forwarding is the fallback path: PPE_L2_VP_PORT_TBL with
INVALID_VSI_FWD_EN = 1, DST_INFO = 0 (CPU port0) - set for every physical
port in ppe_config.c. That controls the destination; the RX descriptor
src_info type is still 0.
- Bridging eth1 (to give it a VSI) does not change it.
- port@1 (the DSA conduit) gets SRCINFO_TYPE_PORTID and is delivered correctly,
so the tagging clearly happens for that port's path.
Question
--------
What configures a port so the PPE tags its host-bound frames with
SRCINFO_TYPE_PORTID + src_port? Is there a per-port setup (an EG service code,
a source-port profile, a physical-vs-virtual-port distinction) that the
DSA-conduit path establishes but a standalone direct MAC port does not?
And more generally: is "deliver a direct MAC port's own ingress to the host
CPU" (a plain WAN/L3 port - no DSA, no bridge offload) an intended/supported
configuration today, or does the EDMA RX path assume all host-bound traffic
arrives via the DSA conduit or an offloaded bridge VSI?
Thanks,
Kamil Bienkiewicz (perceival on the OpenWrt forum)
^ permalink raw reply
* [PATCH 1/2 net-next,v1] i40e: move ATR sample rate from ring to PF level
From: mheib @ 2026-06-21 12:56 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, jiri, davem, edumazet, kuba, pabeni, horms, corbet,
anthony.l.nguyen, przemyslaw.kitszel, andrew+netdev,
Mohammad Heib
From: Mohammad Heib <mheib@redhat.com>
The ATR sample rate is currently stored per-ring and initialized when each
TX ring is configured. Since the sample rate is a global policy that
applies uniformly across all rings, it makes more sense to store it at
the PF level.
Move atr_sample_rate from struct i40e_ring to struct i40e_pf and initialize
it once during i40e_sw_init(). Update i40e_atr() to reference the PF-level
field. Change atr_count from u8 to u32 to match the sample rate type.
Signed-off-by: Mohammad Heib <mheib@redhat.com>
---
drivers/net/ethernet/intel/i40e/i40e.h | 1 +
drivers/net/ethernet/intel/i40e/i40e_main.c | 9 +++------
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 4 ++--
drivers/net/ethernet/intel/i40e/i40e_txrx.h | 3 +--
4 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h
index 1b6a8fbaa648..88eb40ee45f0 100644
--- a/drivers/net/ethernet/intel/i40e/i40e.h
+++ b/drivers/net/ethernet/intel/i40e/i40e.h
@@ -487,6 +487,7 @@ struct i40e_pf {
u16 rss_size_max; /* HW defined max RSS queues */
u16 fdir_pf_filter_count; /* num of guaranteed filters for this PF */
u16 num_alloc_vsi; /* num VSIs this driver supports */
+ u32 atr_sample_rate;
bool wol_en;
struct hlist_head fdir_filter_list;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index d59750c490f4..9695d160bc59 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -3457,12 +3457,7 @@ static int i40e_configure_tx_ring(struct i40e_ring *ring)
ring->xsk_pool = i40e_xsk_pool(ring);
/* some ATR related tx ring init */
- if (test_bit(I40E_FLAG_FD_ATR_ENA, vsi->back->flags)) {
- ring->atr_sample_rate = I40E_DEFAULT_ATR_SAMPLE_RATE;
- ring->atr_count = 0;
- } else {
- ring->atr_sample_rate = 0;
- }
+ ring->atr_count = 0;
/* configure XPS */
i40e_config_xps_tx_ring(ring);
@@ -12745,6 +12740,8 @@ static int i40e_sw_init(struct i40e_pf *pf)
}
}
+ pf->atr_sample_rate = I40E_DEFAULT_ATR_SAMPLE_RATE;
+
if ((pf->hw.func_caps.fd_filters_guaranteed > 0) ||
(pf->hw.func_caps.fd_filters_best_effort > 0)) {
set_bit(I40E_FLAG_FD_ATR_ENA, pf->flags);
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
index 61525ab7d21e..da94cb2ce94d 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
@@ -2882,7 +2882,7 @@ static void i40e_atr(struct i40e_ring *tx_ring, struct sk_buff *skb,
return;
/* if sampling is disabled do nothing */
- if (!tx_ring->atr_sample_rate)
+ if (!pf->atr_sample_rate)
return;
/* Currently only IPv4/IPv6 with TCP is supported */
@@ -2934,7 +2934,7 @@ static void i40e_atr(struct i40e_ring *tx_ring, struct sk_buff *skb,
if (!th->fin &&
!th->syn &&
!th->rst &&
- (tx_ring->atr_count < tx_ring->atr_sample_rate))
+ (tx_ring->atr_count < pf->atr_sample_rate))
return;
tx_ring->atr_count = 0;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.h b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
index bb741ff3e5f2..be587f804e7a 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.h
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
@@ -372,8 +372,7 @@ struct i40e_ring {
u16 next_to_clean;
u16 xdp_tx_active;
- u8 atr_sample_rate;
- u8 atr_count;
+ u32 atr_count;
bool ring_active; /* is ring online or not */
bool arm_wb; /* do something to arm write back */
--
2.53.0
^ permalink raw reply related
* [PATCH 2/2 net-next,v2] i40e: add devlink parameter for Flow Director ATR sample rate
From: mheib @ 2026-06-21 12:56 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, jiri, davem, edumazet, kuba, pabeni, horms, corbet,
anthony.l.nguyen, przemyslaw.kitszel, andrew+netdev,
Mohammad Heib
In-Reply-To: <20260621125644.253844-1-mheib@redhat.com>
From: Mohammad Heib <mheib@redhat.com>
The i40e driver uses Flow Director ATR to periodically update flow
steering information for active TCP flows. The update frequency is
currently controlled by I40E_DEFAULT_ATR_SAMPLE_RATE and is fixed at
driver build time.
On systems with a large number of queues and high-rate TCP workloads,
the default sampling interval can result in frequent Flow Director
reprogramming for long-lived flows.
The amount of TCP packet reordering observed on some systems is
sensitive to the ATR sampling interval. Increasing the interval reduces
Flow Director programming activity and can significantly reduce the
associated reordering.
Since the optimal sampling interval depends on the workload and system
configuration, a single fixed value is not suitable for all deployments.
Add a devlink parameter to allow administrators to tune the ATR sample
rate at runtime without rebuilding the driver or disabling ATR
functionality entirely.
Signed-off-by: Mohammad Heib <mheib@redhat.com>
---
Documentation/networking/devlink/i40e.rst | 20 +++++++++++
.../net/ethernet/intel/i40e/i40e_devlink.c | 36 +++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/Documentation/networking/devlink/i40e.rst b/Documentation/networking/devlink/i40e.rst
index 51c887f0dc83..2cea98b631ba 100644
--- a/Documentation/networking/devlink/i40e.rst
+++ b/Documentation/networking/devlink/i40e.rst
@@ -40,6 +40,26 @@ Parameters
The default value is ``0`` (internal calculation is used).
+.. list-table:: Driver specific parameters implemented
+ :widths: 5 5 90
+
+ * - Name
+ - Mode
+ - Description
+ * - ``atr_sample_rate``
+ - runtime
+ - Controls how frequently Flow Director ATR updates flow steering
+ information for active TCP flows.
+
+ ATR programs Flow Director entries based on sampled transmitted
+ packets. The sampling interval is specified as the number of
+ transmitted packets between ATR updates.
+
+ Lower values increase Flow Director programming activity, while
+ higher values reduce the update frequency.
+
+ Setting to ``0`` disables ATR sampling (no filters will be programmed)
+ The default value is ``20``.
Info versions
=============
diff --git a/drivers/net/ethernet/intel/i40e/i40e_devlink.c b/drivers/net/ethernet/intel/i40e/i40e_devlink.c
index 229179ccc131..cf487efdd803 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_devlink.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_devlink.c
@@ -33,12 +33,48 @@ static int i40e_max_mac_per_vf_get(struct devlink *devlink,
return 0;
}
+static int i40e_atr_sample_rate_set(struct devlink *devlink,
+ u32 id,
+ struct devlink_param_gset_ctx *ctx,
+ struct netlink_ext_ack *extack)
+{
+ struct i40e_pf *pf = devlink_priv(devlink);
+ u32 sample_rate = ctx->val.vu32;
+
+ pf->atr_sample_rate = sample_rate;
+ return 0;
+}
+
+static int i40e_atr_sample_rate_get(struct devlink *devlink,
+ u32 id,
+ struct devlink_param_gset_ctx *ctx,
+ struct netlink_ext_ack *extack)
+{
+ struct i40e_pf *pf = devlink_priv(devlink);
+
+ ctx->val.vu32 = pf->atr_sample_rate;
+
+ return 0;
+}
+
+enum i40e_dl_param_id {
+ I40E_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
+ I40E_DEVLINK_PARAM_ID_ATR_SAMPLE_RATE,
+};
+
static const struct devlink_param i40e_dl_params[] = {
DEVLINK_PARAM_GENERIC(MAX_MAC_PER_VF,
BIT(DEVLINK_PARAM_CMODE_RUNTIME),
i40e_max_mac_per_vf_get,
i40e_max_mac_per_vf_set,
NULL),
+ DEVLINK_PARAM_DRIVER(I40E_DEVLINK_PARAM_ID_ATR_SAMPLE_RATE,
+ "atr_sample_rate",
+ DEVLINK_PARAM_TYPE_U32,
+ BIT(DEVLINK_PARAM_CMODE_RUNTIME),
+ i40e_atr_sample_rate_get,
+ i40e_atr_sample_rate_set,
+ NULL),
};
static void i40e_info_get_dsn(struct i40e_pf *pf, char *buf, size_t len)
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2] net: add sock_open() with flags for socket creation
From: David Laight @ 2026-06-21 12:57 UTC (permalink / raw)
To: Alex Goltsev; +Cc: davem, netdev, linux-kernel, Al Viro
In-Reply-To: <CAEKmD4K-v_srabQDJCfqaqA6vssk-Hg-mLHEdTsphTBLmQVjnw@mail.gmail.com>
On Sun, 21 Jun 2026 14:05:40 +0300
Alex Goltsev <sasha.goltsev777@gmail.com> wrote:
> From a9316957e594708dfb4258ad968fe88666c9b736 Mon Sep 17 00:00:00 2001
> From: 0-x-0-0 <sasha.goltsev777@gmail.com>
> Date: Sun, 21 Jun 2026 13:24:29 +0300
> Subject: [PATCH v2] net: add sock_open() with flags for socket creation
A) There is no info here.
B) You've not said why this is of any use.
C) It isn't a bug fix so would go into net-next
D) net-next is closed.
David
>
> ---
> Changes in V2:
> - Replaced the use of plain integer constants for flags with proper
> enums to improve readability and type safety.
> - `sock_open` is intentionally left as a regular exported symbol rather
> than being moved to a header as `static inline`. This is because it
> dereferences `current->nsproxy->net_ns`, which would require pulling
> in heavy headers like <linux/sched.h> and <linux/nsproxy.h> into the
> already widely-used <linux/net.h>, causing unnecessary header bloat
> and potential circular dependencies.
> - Introduced two new creation flags for specialized use cases within
> kernel modules:
>
> * SOCK_CREATE_NOLSM: This flag allows a kernel module to bypass
> LSM hooks during socket creation. This
> enables a micro-optimization for kernel-internal sockets where
> the security check is known *a priori* to be a no-op (e.g., for
> specific configurations or high-performance paths).
> This is safe because the API is restricted to in-kernel (LKM)
> contexts only, and does not weaken the security boundary for
> user-triggered socket creation.
>
> * SOCK_CREATE_NOWARN: This flag suppresses the standard warning
> messages on creation failure. This is useful for callers in the
> kernel that probe for protocol support and handle the error
> gracefully, without wanting to pollute the kernel log with
> misleading warnings.
>
> Signed-off-by: Alexander Goltsev <sasha.goltsev777@gmail.com>
> ---
> include/linux/net.h | 22 ++++++++
> net/socket.c | 133 +++++++++++++++++++++++++++++++++++++-------
> 2 files changed, 134 insertions(+), 21 deletions(-)
>
> diff --git a/include/linux/net.h b/include/linux/net.h
> index f268f395c..6367c00db 100644
> --- a/include/linux/net.h
> +++ b/include/linux/net.h
> @@ -116,6 +116,22 @@ enum sock_shutdown_cmd {
> SHUT_RDWR,
> };
> +/**
> + * enum sock_create_flags - socket creation flags
> + * @SOCK_CREATE_KERN: creates a kernel socket
> + * @SOCK_CREATE_USER: creates a regular socket
> + * @SOCK_CREATE_LITE: creates a lite socket
> + * @SOCK_CREATE_NOLSM: disables LSM
> + * @SOCK_CREATE_NOWARN: disables warning
> + */
> +enum sock_create_flags {
> + SOCK_CREATE_KERN = BIT(0),
> + SOCK_CREATE_USER = BIT(1),
> + SOCK_CREATE_LITE = BIT(2),
> + SOCK_CREATE_NOLSM = BIT(3),
> + SOCK_CREATE_NOWARN = BIT(4),
> +};
> +
> struct socket_wq {
> /* Note: wait MUST be first field of socket_wq */
> wait_queue_head_t wait;
> @@ -275,6 +291,12 @@ void sock_unregister(int family);
> bool sock_is_registered(int family);
> int __sock_create(struct net *net, int family, int type, int proto,
> struct socket **res, int kern);
> +int __sock_create_flags(struct net *net, int family, int type, int protocol,
> + struct socket **res, int kern, int flags);
> +int __sock_create_lite_flags(int family, int type, int protocol,
> + struct socket **res, int flags);
> +int sock_open(struct net *net, int family,
> + int type, int protocol, struct socket **res, int flags);
> int sock_create(int family, int type, int proto, struct socket **res);
> int sock_create_kern(struct net *net, int family, int type, int proto,
> struct socket **res);
> int sock_create_lite(int family, int type, int proto, struct socket **res);
> diff --git a/net/socket.c b/net/socket.c
> index 63c69a0fa..2359fd5bf 100644
> --- a/net/socket.c
> +++ b/net/socket.c
> @@ -1425,26 +1425,28 @@ static long sock_ioctl(struct file *file,
> unsigned cmd, unsigned long arg)
> }
> /**
> - * sock_create_lite - creates a socket
> + * __sock_create_lite_flags - creates a socket (with flags)
> * @family: protocol family (AF_INET, ...)
> * @type: communication type (SOCK_STREAM, ...)
> * @protocol: protocol (0, ...)
> * @res: new socket
> + * @flags: defines socket creation flags
> *
> * Creates a new socket and assigns it to @res, passing through LSM.
> * The new socket initialization is not complete, see kernel_accept().
> * Returns 0 or an error. On failure @res is set to %NULL.
> * This function internally uses GFP_KERNEL.
> */
> -
> -int sock_create_lite(int family, int type, int protocol, struct socket **res)
> +int __sock_create_lite_flags(int family, int type, int protocol,
> struct socket **res, int flags)
> {
> int err;
> struct socket *sock = NULL;
> - err = security_socket_create(family, type, protocol, 1);
> - if (err)
> - goto out;
> + if (!(flags & SOCK_CREATE_NOLSM)) {
> + err = security_socket_create(family, type, protocol, 1);
> + if (err)
> + goto out;
> + }
> sock = sock_alloc();
> if (!sock) {
> @@ -1453,9 +1455,11 @@ int sock_create_lite(int family, int type, int
> protocol, struct socket **res)
> }
> sock->type = type;
> - err = security_socket_post_create(sock, family, type, protocol, 1);
> - if (err)
> - goto out_release;
> + if (!(flags & SOCK_CREATE_NOLSM)) {
> + err = security_socket_post_create(sock, family, type, protocol, 1);
> + if (err)
> + goto out_release;
> + }
> out:
> *res = sock;
> @@ -1465,6 +1469,25 @@ int sock_create_lite(int family, int type, int
> protocol, struct socket **res)
> sock = NULL;
> goto out;
> }
> +EXPORT_SYMBOL(__sock_create_lite_flags);
> +
> +/**
> + * sock_create_lite - creates a socket
> + * @family: protocol family (AF_INET, ...)
> + * @type: communication type (SOCK_STREAM, ...)
> + * @protocol: protocol (0, ...)
> + * @res: new socket
> + *
> + * Creates a new socket and assigns it to @res, passing through LSM.
> + * The new socket initialization is not complete, see kernel_accept().
> + * Returns 0 or an error. On failure @res is set to %NULL.
> + * This function internally uses GFP_KERNEL.
> + */
> +
> +int sock_create_lite(int family, int type, int protocol, struct socket **res)
> +{
> + return __sock_create_lite_flags(family, type, protocol, res, 0);
> +}
> EXPORT_SYMBOL(sock_create_lite);
> /* No kernel lock held - perfect */
> @@ -1563,22 +1586,23 @@ int sock_wake_async(struct socket_wq *wq, int
> how, int band)
> EXPORT_SYMBOL(sock_wake_async);
> /**
> - * __sock_create - creates a socket
> + * __sock_create_flags - creates a socket (with flags)
> * @net: net namespace
> * @family: protocol family (AF_INET, ...)
> * @type: communication type (SOCK_STREAM, ...)
> * @protocol: protocol (0, ...)
> * @res: new socket
> * @kern: boolean for kernel space sockets
> + * @flags: defines socket creation flags
> *
> * Creates a new socket and assigns it to @res, passing through LSM.
> * Returns 0 or an error. On failure @res is set to %NULL. @kern must
> * be set to true if the socket resides in kernel space.
> * This function internally uses GFP_KERNEL.
> */
> -
> -int __sock_create(struct net *net, int family, int type, int protocol,
> - struct socket **res, int kern)
> +int __sock_create_flags(struct net *net, int family,
> + int type, int protocol, struct socket **res,
> + int kern, int flags)
> {
> int err;
> struct socket *sock;
> @@ -1598,14 +1622,18 @@ int __sock_create(struct net *net, int family,
> int type, int protocol,
> deadlock in module load.
> */
> if (family == PF_INET && type == SOCK_PACKET) {
> - pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
> + if (!(flags & SOCK_CREATE_NOWARN)) {
> + pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
> current->comm);
> + }
> family = PF_PACKET;
> }
> - err = security_socket_create(family, type, protocol, kern);
> - if (err)
> - return err;
> + if (!(flags & SOCK_CREATE_NOLSM)) {
> + err = security_socket_create(family, type, protocol, kern);
> + if (err)
> + return err;
> + }
> /*
> * Allocate the socket and allow the family to set things up. if
> @@ -1614,7 +1642,8 @@ int __sock_create(struct net *net, int family,
> int type, int protocol,
> */
> sock = sock_alloc();
> if (!sock) {
> - net_warn_ratelimited("socket: no more sockets\n");
> + if (!(flags & SOCK_CREATE_NOWARN))
> + net_warn_ratelimited("socket: no more sockets\n");
> return -ENFILE; /* Not exactly a match, but its the
> closest posix thing */
> }
> @@ -1671,9 +1700,12 @@ int __sock_create(struct net *net, int family,
> int type, int protocol,
> * module can have its refcnt decremented
> */
> module_put(pf->owner);
> - err = security_socket_post_create(sock, family, type, protocol, kern);
> - if (err)
> - goto out_sock_release;
> +
> + if (!(flags & SOCK_CREATE_NOLSM)) {
> + err = security_socket_post_create(sock, family, type, protocol, kern);
> + if (err)
> + goto out_sock_release;
> + }
> *res = sock;
> return 0;
> @@ -1691,6 +1723,28 @@ int __sock_create(struct net *net, int family,
> int type, int protocol,
> rcu_read_unlock();
> goto out_sock_release;
> }
> +EXPORT_SYMBOL(__sock_create_flags);
> +
> +/**
> + * __sock_create - creates a socket
> + * @net: net namespace
> + * @family: protocol family (AF_INET, ...)
> + * @type: communication type (SOCK_STREAM, ...)
> + * @protocol: protocol (0, ...)
> + * @res: new socket
> + * @kern: boolean for kernel space sockets
> + *
> + * Creates a new socket and assigns it to @res, passing through LSM.
> + * Returns 0 or an error. On failure @res is set to %NULL. @kern must
> + * be set to true if the socket resides in kernel space.
> + * This function internally uses GFP_KERNEL.
> + */
> +
> +int __sock_create(struct net *net, int family, int type, int protocol,
> + struct socket **res, int kern)
> +{
> + return __sock_create_flags(net, family, type, protocol, res, kern, 0);
> +}
> EXPORT_SYMBOL(__sock_create);
> /**
> @@ -1710,6 +1764,43 @@ int sock_create(int family, int type, int
> protocol, struct socket **res)
> }
> EXPORT_SYMBOL(sock_create);
> +/**
> + * sock_open - creates a socket (with flags)
> + * @net: net namespace (may be NULL in non-SOCK_CREATE_KERN modes)
> + * @family: protocol family (AF_INET, ...)
> + * @type: communication type (SOCK_STREAM, ...)
> + * @protocol: protocol (0, ...)
> + * @res: new socket
> + * @flags: socket creation flags
> + *
> + * Unified entry point for socket creation with flags.
> + * Returns 0 or an error. This function internally uses GFP_KERNEL.
> + */
> +int sock_open(struct net *net, int family,
> + int type, int protocol, struct socket **res,
> + int flags)
> +{
> + int type_bits = flags & (SOCK_CREATE_KERN | SOCK_CREATE_USER |
> SOCK_CREATE_LITE);
> + int optional_flags = flags & ~(SOCK_CREATE_KERN | SOCK_CREATE_USER |
> SOCK_CREATE_LITE);
> +
> + if (type_bits == 0 || (type_bits & (type_bits - 1)) != 0)
> + return -EINVAL;
> +
> + if (optional_flags & ~(SOCK_CREATE_NOLSM | SOCK_CREATE_NOWARN))
> + return -EINVAL;
> +
> + switch (type_bits) {
> + case SOCK_CREATE_KERN: return __sock_create_flags(net, family, type, protocol,
> + res, 1, optional_flags);
> + case SOCK_CREATE_USER: return __sock_create_flags(current->nsproxy->net_ns,
> + family, type, protocol, res, 0, optional_flags);
> + case SOCK_CREATE_LITE: return __sock_create_lite_flags(family,
> + type, protocol, res, optional_flags);
> + default: return -EINVAL;
> + }
> +}
> +EXPORT_SYMBOL(sock_open);
> +
> /**
> * sock_create_kern - creates a socket (kernel space)
> * @net: net namespace
^ permalink raw reply
* Re: [Intel-wired-lan] e1000e: Report link down after "Detected Hardware Unit Hang" ?
From: Ruinskiy, Dima @ 2026-06-21 13:22 UTC (permalink / raw)
To: Andrew Lunn, Helge Deller, Helge Deller
Cc: Tony Nguyen, Przemek Kitszel, intel-wired-lan, netdev
In-Reply-To: <d86c0dd8-8bd8-495a-b750-2a0036fbbee4@lunn.ch>
On 17/06/2026 0:59, Andrew Lunn wrote:
>> This does not seem like the right direction to me.
>>
>> The "Detected Hardware Unit Hang" print does not indicate that the interface
>> is dead, but that the transmitter is stalled.
>>
>> This can be due to an unusually high load, or a HW fault / race condition
>> with another component, etc.
>>
>> When a hang is detected, the transmitter is stopped with netif_stop_queue()
>> and eventually ndo_tx_timeout triggers a full reset to the device, which in
>> many cases recovers it from the hang.
>
> Does a full reset cause the link to be negotiated again? If so, there
> is no harm in setting the carrier down. If the reset is successful,
> the carrier will be restored. However, if the reset does not recover
> the system, does the carrier say down?
>
> Andrew
>
The way it is written - a reset triggered by the Tx timeout path will go
through e1000e_reinit_locked(), which calls e1000e_down() followed by
e1000e_up().
e1000e_down() calls netif_carrier_off() at the start, and e1000e_reset()
later. e1000e_up() triggers a link state recheck, which should restore
the carrier.
So if everything works as it should, the change proposed here would be
both redundant and unnecessary. However, we have been getting reports of
these unrecoverable hangs from time-to-time, so I suspect things do not
always work as they should.
There is one issue under investigation at present, where a persistent
hang was reported following an aborted hibernation attempt. We are
testing a patch against it.
I did not see anything in the original description of this report tying
the hang to a power state change, but I will happily share the patch
once we get preliminary positive results.
--Dima
^ permalink raw reply
* Re: [PATCH v2] net: add sock_open() with flags for socket creation
From: Andrew Lunn @ 2026-06-21 13:59 UTC (permalink / raw)
To: David Laight; +Cc: Alex Goltsev, davem, netdev, linux-kernel, Al Viro
In-Reply-To: <20260621135746.067a93be@pumpkin>
On Sun, Jun 21, 2026 at 01:57:46PM +0100, David Laight wrote:
> On Sun, 21 Jun 2026 14:05:40 +0300
> Alex Goltsev <sasha.goltsev777@gmail.com> wrote:
>
> > From a9316957e594708dfb4258ad968fe88666c9b736 Mon Sep 17 00:00:00 2001
> > From: 0-x-0-0 <sasha.goltsev777@gmail.com>
> > Date: Sun, 21 Jun 2026 13:24:29 +0300
> > Subject: [PATCH v2] net: add sock_open() with flags for socket creation
>
> A) There is no info here.
> B) You've not said why this is of any use.
> C) It isn't a bug fix so would go into net-next
> D) net-next is closed.
E) The patch has had all its whitespace corrupted.
Please "submit" the patch to yourself and ensure you can cleanly apply
it.
Andrew
^ permalink raw reply
* Re: "ip help" output is an error
From: Stephen Hemminger @ 2026-06-21 15:21 UTC (permalink / raw)
To: Dmitri Seletski; +Cc: netdev
In-Reply-To: <62f09fe8-899c-4d22-b7a1-67e2745613df@gmail.com>
On Sat, 20 Jun 2026 10:36:31 +0100
Dmitri Seletski <drjoms@gmail.com> wrote:
> Hello iproute2 maintainers,
>
> I am reporting an inconsistency regarding the exit status of the ip help
> command.
>
> Current Behavior:
> When running ip help, the command prints the help documentation to
> stdout, but exits with a non-zero status (error). This causes issues in
> shell scripts that rely on exit codes for control flow.
>
> Steps to reproduce:
> bash
>
> # This returns "FAIL" because the exit code is non-zero
> if ip help > /dev/null; then
> echo "SUCCESS"
> else
> echo "FAIL"
> fi
>
> Expected Behavior:
> Since the command successfully performs the requested task (displaying
> help information) and does not encounter a system error, it should
> return an exit code of 0.
>
> Context:
> This behavior breaks standard Bash logic for automation. For example:
> ip help && echo "This will not execute"
>
> "ip help |grep br" - this will bring no result.
>
> Current version tested: iproute2-6.19.0
>
> Thank you for your time and for maintaining this tool.
>
> Regards,
> Dmitri Seletski
>
>
Yes iproute2 doesn't do a great job of handling error codes
with usage vs help. Its a bug and no one has bothered to fix it.
^ permalink raw reply
* [syzbot] [net?] WARNING in __ethtool_get_link_ksettings
From: syzbot @ 2026-06-21 15:22 UTC (permalink / raw)
To: andrew, davem, edumazet, horms, kuba, linux-kernel, netdev,
pabeni, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: 4fa3f5fabb30 Add linux-next specific files for 20260616
git tree: linux-next
console output: https://syzkaller.appspot.com/x/log.txt?x=1039ffec580000
kernel config: https://syzkaller.appspot.com/x/.config?x=6c414e1864e61ef6
dashboard link: https://syzkaller.appspot.com/bug?extid=09da62a8b78959ceb8bb
compiler: Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/bf5b803a695d/disk-4fa3f5fa.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/47871e7c589e/vmlinux-4fa3f5fa.xz
kernel image: https://storage.googleapis.com/syzbot-assets/53cd9ef32a2b/bzImage-4fa3f5fa.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+09da62a8b78959ceb8bb@syzkaller.appspotmail.com
netlink: 'syz.3.4736': attribute type 10 has an invalid length.
------------[ cut here ]------------
rtmutex deadlock detected
WARNING: kernel/locking/rtmutex.c:1698 at rt_mutex_handle_deadlock+0x21/0xb0 kernel/locking/rtmutex.c:1698, CPU#0: syz.3.4736/19861
Modules linked in:
CPU: 0 UID: 0 PID: 19861 Comm: syz.3.4736 Tainted: G L syzkaller #0 PREEMPT_{RT,(full)}
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:rt_mutex_handle_deadlock+0x21/0xb0 kernel/locking/rtmutex.c:1698
Code: 90 90 90 90 90 90 90 90 90 41 57 41 56 41 55 41 54 53 83 ff dd 0f 85 81 00 00 00 48 89 f7 e8 16 40 01 00 48 8d 3d 3f 2f 5d 04 <67> 48 0f b9 3a 4c 8d 3d 00 00 00 00 65 48 8b 1d 13 2d 42 07 4c 8d
RSP: 0018:ffffc90003d760f0 EFLAGS: 00010286
RAX: 0000000080000000 RBX: 00000000ffffffdd RCX: 0000000000000000
RDX: 0000000000000000 RSI: ffffffff8baa3a60 RDI: ffffffff8f8fd220
RBP: ffffc90003d762a0 R08: ffffffff8f8c5ff7 R09: 1ffffffff1f18bfe
R10: dffffc0000000000 R11: fffffbfff1f18bff R12: 1ffff920007aec2c
R13: ffffffff8b329d22 R14: ffff88805dbb1008 R15: dffffc0000000000
FS: 00007fd5506ae6c0(0000) GS:ffff888125ed3000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000000 CR3: 000000003cd08000 CR4: 00000000003526f0
Call Trace:
<TASK>
__rt_mutex_slowlock kernel/locking/rtmutex.c:1760 [inline]
__rt_mutex_slowlock_locked kernel/locking/rtmutex.c:1787 [inline]
rt_mutex_slowlock+0x73c/0x780 kernel/locking/rtmutex.c:1827
__rt_mutex_lock kernel/locking/rtmutex.c:1842 [inline]
__mutex_lock_common kernel/locking/rtmutex_api.c:560 [inline]
mutex_lock_nested+0x168/0x1d0 kernel/locking/rtmutex_api.c:578
netdev_lock include/linux/netdevice.h:2846 [inline]
netdev_lock_ops include/net/netdev_lock.h:42 [inline]
__ethtool_get_link_ksettings+0x109/0x250 net/ethtool/ioctl.c:463
bond_update_speed_duplex drivers/net/bonding/bond_main.c:801 [inline]
bond_slave_netdev_event drivers/net/bonding/bond_main.c:3982 [inline]
bond_netdev_event+0x643/0xf80 drivers/net/bonding/bond_main.c:4089
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
call_netdevice_notifiers_extack net/core/dev.c:2289 [inline]
call_netdevice_notifiers net/core/dev.c:2303 [inline]
__dev_notify_flags+0x1aa/0x310 net/core/dev.c:9792
netif_change_flags+0xde/0x1b0 net/core/dev.c:9821
dev_change_flags+0x128/0x260 net/core/dev_api.c:68
vlan_device_event+0x1b4e/0x1f00 net/8021q/vlan.c:494
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
call_netdevice_notifiers_extack net/core/dev.c:2289 [inline]
call_netdevice_notifiers net/core/dev.c:2303 [inline]
netif_open+0x10f/0x190 net/core/dev.c:1730
dev_open+0x101/0x220 net/core/dev_api.c:202
bond_enslave+0xeb2/0x3b70 drivers/net/bonding/bond_main.c:2089
do_set_master+0x563/0x720 net/core/rtnetlink.c:3012
do_setlink+0xe7b/0x4670 net/core/rtnetlink.c:3214
rtnl_changelink net/core/rtnetlink.c:3841 [inline]
__rtnl_newlink net/core/rtnetlink.c:4014 [inline]
rtnl_newlink+0x15c2/0x1bd0 net/core/rtnetlink.c:4151
rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7068
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7f5/0x990 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:785
__sock_sendmsg net/socket.c:800 [inline]
____sys_sendmsg+0x565/0x870 net/socket.c:2702
___sys_sendmsg+0x2a5/0x360 net/socket.c:2756
__sys_sendmsg net/socket.c:2788 [inline]
__do_sys_sendmsg net/socket.c:2793 [inline]
__se_sys_sendmsg net/socket.c:2791 [inline]
__x64_sys_sendmsg+0x1b7/0x290 net/socket.c:2791
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fd55245ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fd5506ae028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007fd5526d5fa0 RCX: 00007fd55245ce59
RDX: 0000000000008084 RSI: 0000200000000600 RDI: 0000000000000003
RBP: 00007fd5524f2d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fd5526d6038 R14: 00007fd5526d5fa0 R15: 00007ffdbadd34a8
</TASK>
----------------
Code disassembly (best guess):
0: 90 nop
1: 90 nop
2: 90 nop
3: 90 nop
4: 90 nop
5: 90 nop
6: 90 nop
7: 90 nop
8: 90 nop
9: 41 57 push %r15
b: 41 56 push %r14
d: 41 55 push %r13
f: 41 54 push %r12
11: 53 push %rbx
12: 83 ff dd cmp $0xffffffdd,%edi
15: 0f 85 81 00 00 00 jne 0x9c
1b: 48 89 f7 mov %rsi,%rdi
1e: e8 16 40 01 00 call 0x14039
23: 48 8d 3d 3f 2f 5d 04 lea 0x45d2f3f(%rip),%rdi # 0x45d2f69
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: 4c 8d 3d 00 00 00 00 lea 0x0(%rip),%r15 # 0x36
36: 65 48 8b 1d 13 2d 42 mov %gs:0x7422d13(%rip),%rbx # 0x7422d51
3d: 07
3e: 4c rex.WR
3f: 8d .byte 0x8d
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* Re: [PATCH net v4] nfc: llcp: bound SNL TLV parsing to the skb and add length checks
From: David Heidelberg @ 2026-06-21 16:34 UTC (permalink / raw)
To: Doruk Tan Ozturk, oe-linux-nfc
Cc: david.laight.linux, horms, netdev, linux-kernel
In-Reply-To: <20260609202543.42282-1-doruk@0sec.ai>
On Tue, 09 Jun 2026 22:25:43 +0200, Doruk Tan Ozturk wrote:
> nfc: llcp: bound SNL TLV parsing to the skb and add length checks
Applied, thanks!
[1/1] nfc: llcp: bound SNL TLV parsing to the skb and add length checks
commit: ed85d4cbbfaa4e630c5aa0d607348b42620d976b
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply
* Re: [PATCH net] nfc: nci: validate packet length when parsing NCI 2.x RF interfaces
From: David Heidelberg @ 2026-06-21 16:46 UTC (permalink / raw)
To: Zijing Yin
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, oe-linux-nfc, netdev, linux-kernel, stable
In-Reply-To: <20260611162718.2301552-1-yzjaurora@gmail.com>
On 11/06/2026 18:27, Zijing Yin wrote:
> nci_core_init_rsp_packet_v2() parses the variable-length list of
> supported RF interfaces carried in an NCI 2.x CORE_INIT_RSP without ever
> validating the controller-supplied lengths against the size of the
> received packet.
>
> Each list entry is a (RF interface, RF extension count, RF extensions[])
> tuple. The loop walks the list using the per-entry extension count
> (rf_extension_cnt, up to 255) taken straight from the packet, so a
> malformed CORE_INIT_RSP can advance the read pointer far past the end of
> the skb data buffer. The stored interface count is clamped to
> NCI_MAX_SUPPORTED_RF_INTERFACES so the write side is bounded, but the
> read side runs off the end of the buffer.
>
> A malformed CORE_INIT_RSP from the controller, also reachable from user
> space through the virtual NCI device (CONFIG_NFC_VIRTUAL_NCI) once the
> device has entered NCI 2.x mode, therefore makes the parser read past the
> end of the response buffer while walking the interface list, copying the
> out-of-bounds bytes into ndev->supported_rf_interfaces[].
>
> Reject responses shorter than the fixed part of the structure, and make
> sure each interface entry and its extension bytes lie within the received
> packet before dereferencing them. A truncated or malformed list is
> treated as a syntax error, which fails the CORE_INIT request instead of
> reading out of bounds.
>
> Fixes: bcd684aace34 ("net/nfc/nci: Support NCI 2.x initial sequence")
> Cc: stable@vger.kernel.org
> Signed-off-by: Zijing Yin <yzjaurora@gmail.com>
> ---
> net/nfc/nci/rsp.c | 18 +++++++++++++++++-
> 1 file changed, 17 insertions(+), 1 deletion(-)
>
Hello Zijing,
in meanwhile [1] got merged earlier patch, which also seems to address the issue.
Could you review that the issue is now fixed for you?
The NFC tree is here [2].
Thank you and sorry for the late reply
David
[1] https://lore.kernel.org/all/20260527052625.3309581-1-yun.zhou@windriver.com/
[2] https://codeberg.org/linux-nfc/linux
^ permalink raw reply
* Re: [PATCH net 0/2] nfc: llcp: fix OOB reads and integer bugs in TLV parsers
From: David Heidelberg @ 2026-06-21 16:52 UTC (permalink / raw)
To: Muhammad Bilal, netdev
Cc: linux-kernel, oe-linux-nfc, david+nfc, davem, edumazet, kuba,
pabeni, horms, stable
In-Reply-To: <20260519011937.12903-1-meatuni001@gmail.com>
On 19/05/2026 03:19, Muhammad Bilal wrote:
> This series fixes memory safety bugs in the NFC LLCP TLV parsing code,
> reachable from a remote NFC peer via crafted LLCP frames.
>
> Patch 1 fixes nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv():
> - u8 offset wraps to zero after 255 (widened to u16)
> - OOB read of TLV header on truncated buffer
> - OOB read of value field via attacker-controlled length byte
>
> Patch 2 fixes nfc_llcp_recv_snl():
> - OOB read of TLV header when tlv_len - offset == 1
> - OOB read of SDREQ value via attacker-controlled length
> - SIZE_MAX underflow when length == 0 in service_name_len,
> bypassing the sn_len == 0 guard in nfc_llcp_sock_from_sn()
>
> Previously reported to security@kernel.org on 2026-05-15. Willy Tarreau
> advised posting to public lists as NFC is currently orphaned.
>
> Muhammad Bilal (2):
> nfc: llcp: fix OOB read and u8 offset wrap in TLV parsers
> nfc: llcp: add missing bounds checks in nfc_llcp_recv_snl()
>
> net/nfc/llcp_commands.c | 28 ++++++++++++++++++++++++++--
> net/nfc/llcp_core.c | 23 +++++++++++++++++++++--
> 2 files changed, 47 insertions(+), 4 deletions(-)
>
Hello Muhammad,
could I ask for the patches rebase against for-next [1]?
Thank you much for your work!
David
[1] https://codeberg.org/linux-nfc/linux
^ permalink raw reply
* [PATCH stable 6.6.y v4 0/4] bpf: linked scalar precision fixes
From: Zhenzhong Wu @ 2026-06-21 17:27 UTC (permalink / raw)
To: bpf
Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
martin.lau, song, yonghong.song, kpsingh, haoluo, jolsa,
menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird
Hi,
This v4 targets 6.6.y and keeps the v3 backport strategy: use the full
upstream linked-scalar precision-tracking series, instead of the earlier
d028f87517d6/9e314f5d8682 not-equal refinement backport path.
The original observed failure was found in Rust/Aya-generated eBPF
around helper calls. Rust match lowering can keep a helper return value
and a scalar filled through a by-reference helper argument in the same
enum-style control flow. That makes it easy for the verifier-visible
scalar values to become linked by scalar id.
The relevant verifier-log bytecode from the original reproducer is
below. The later instructions only store r7 into a map so user space can
observe which branch the verifier kept.
15: (85) call bpf_get_func_ret#184 ; R0_w=scalar() fp-8_w=mmmmmmmm
16: (79) r7 = *(u64 *)(r10 -8) ; R7_w=scalar() R10=fp0
17: (15) if r0 == 0x0 goto pc+1 ; R0_w=scalar()
18: (bf) r7 = r0 ; R0=scalar(id=1) R7=scalar(id=1)
19: (55) if r0 != 0x0 goto pc+6 ; R0=0
20: (67) r7 <<= 32 ; R7_w=0
21: (77) r7 >>= 32 ; R7_w=0
22: (b7) r1 = 1 ; R1_w=1
23: (55) if r7 != 0xf goto pc+1
The important verifier state shape is:
1. The program checks "if r0 == 0". The jump target is the success
path, and the fallthrough path is the failure path.
2. On the failure path, "r7 = r0" gives r0 and r7 the same scalar id.
The real success path skips that assignment, so r7 is independent
there.
3. At the later "if r0 != 0" check, an affected verifier can explore
an impossible continuation where r0 is zero and r7 is narrowed
through the shared scalar id as well.
4. That impossible continuation reaches the return-value comparison
with the wrong r7 value. When the real success path is analyzed
later, state pruning can consider it safe against the earlier
cached verifier state and skip the real continuation.
The root cause is verifier scalar state tracking, not helper-specific
behavior. A helper return value in r0 and another scalar can become
linked by scalar id on one branch. The real success path can skip that
assignment, so the two verifier states are not equivalent.
The relevant pruning point is that regsafe()/states_equal() accepted
the real success-path state against an earlier cached state where r0 was
an imprecise scalar and r7 constraints were loose enough to cover the
current r7. In the impossible path, r0 and r7 are linked by scalar id
after instruction 18. In the real success path, instruction 18 is
skipped and that scalar-id relationship does not exist. These states
should therefore not be treated as equivalent for pruning.
The upstream linked-scalar precision series fixes that root cause by
recording, in jmp_history, which linked registers were synchronized at
each conditional jump and by using that per-instruction history during
precision backtracking. This covers both the original r0 == 0 /
r0 != 0 shape and the r0 == 1 / r0 != 1 shape used by the separate
runtime selftest.
A Rust/Aya-specific runtime reproducer/selftest discussed in the v2
thread has been submitted separately to bpf-next for review:
https://lore.kernel.org/bpf/20260611160749.391279-1-jt26wzz@gmail.com/
That reproducer keeps the same helper-return/control-flow shape but
shifts the success value to 1 before branching. This avoids depending
on the not-equal-zero refinement and exercises linked scalar precision
during state pruning directly. It uses bpf_skb_load_bytes() in the
normal tc test-run path and does not require fexit attach or
bpf_get_func_ret(). It is not included in this stable series because
per review feedback it should go through bpf-next first before being
considered for stable.
Targeted results for that separate helper-status runtime reproducer are:
v6.6.142 + reproducer: FAIL
v6.6.142 + v2 d028/9e backport path + reproducer: FAIL
v6.6.142 + this linked-scalars series + reproducer: PASS
bpf-next + reproducer: PASS
Changes since v3:
- add the tools/testing/selftests/bpf/verifier/precise.c expected-log
update for "precise: test 1";
- drop the v3-only collect_linked_regs() singleton cleanup and use
the final upstream linked_regs.cnt > 1 history-recording check in
patch 1.
v3:
https://lore.kernel.org/r/cover.1781194510.git.jt26wzz@gmail.com/
v2:
https://lore.kernel.org/r/20260607170959.823755-1-jt26wzz@gmail.com/
RFC v1:
https://lore.kernel.org/r/20260601180400.1381736-1-jt26wzz@gmail.com/
Backport details:
This series is based on v6.6.142 / stable/linux-6.6.y commit
924b4a879cbb ("Linux 6.6.142"). I would like it applied to 6.6.y first.
The same issue is reproducible on 6.1.y, 5.15.y, and 5.10.y, but those
trees need separate older-layout adaptations.
Instead of backporting the d028f87517d6 not-equal refinement plus the
9e314f5d8682 range-combining prerequisite, this series backports the
full upstream linked-scalar precision-tracking series:
4bf79f9be434
bpf: Track equal scalars history on per-instruction level
842edb5507a1
bpf: Remove mark_precise_scalar_ids()
bebc17b1c03b
selftests/bpf: Tests for per-insn sync_linked_regs() precision
tracking
cfbf25481d6d
selftests/bpf: Update comments find_equal_scalars->sync_linked_regs
Upstream series:
https://lore.kernel.org/r/20240718202357.1746514-1-eddyz87@gmail.com/
Patches 1 and 2 are the verifier changes from that upstream series. The
main 6.6.y-specific verifier adaptation is in patch 1: 6.6.y does not
yet have the newer BPF_ADD_CONST scalar-id representation, so
sync_linked_regs() is adapted to the older scalar-id layout. Patch 1
otherwise keeps the upstream linked_regs.cnt > 1 history-recording
condition.
Patch 1 also carries the matching upstream test_verifier expected-log
change for tools/testing/selftests/bpf/verifier/precise.c that v3
missed. On 6.6.y this is a one-line update to the "precise: test 1"
parent-state log, from regs=r2 to regs=r2,r9. Patch 2 then follows on
top of the adapted layout.
Patches 3 and 4 bring the upstream verifier selftests and comment
updates. Patch 3 has one 6.6.y-specific log adaptation:
linked_regs_broken_link_2 keeps the "div by zero" reject check, but
drops the upstream mark_precise log expectations because 6.6.y does not
derive the scalar-vs-scalar range for that non-constant JMP_X
comparison. Patch 4 only updates the two pre-existing comments that are
present in 6.6.y.
Relevant selftest results on 6.6.y with this v4 backport:
test_verifier:
788 PASSED, 0 SKIPPED, 0 FAILED
test_progs -t verifier_scalar_ids:
all 18 verifier_scalar_ids subtests passed
Thanks to Shung-Hsi Yu for reviewing v2/v3 and suggesting the upstream
linked-scalar precision series as the preferred backport direction.
Eduard Zingerman (4):
bpf: Track equal scalars history on per-instruction level
bpf: Remove mark_precise_scalar_ids()
selftests/bpf: Tests for per-insn sync_linked_regs() precision
tracking
selftests/bpf: Update comments find_equal_scalars->sync_linked_regs
include/linux/bpf_verifier.h | 4 +
kernel/bpf/verifier.c | 364 +++++++++++-------
.../selftests/bpf/progs/verifier_scalar_ids.c | 253 ++++++++----
.../selftests/bpf/progs/verifier_spill_fill.c | 4 +-
.../bpf/progs/verifier_subprog_precision.c | 2 +-
.../testing/selftests/bpf/verifier/precise.c | 4 +-
6 files changed, 415 insertions(+), 216 deletions(-)
base-commit: 924b4a879cbb75aef37c160b955b92f6894b11a4
--
2.43.0
^ permalink raw reply
* [PATCH stable 6.6.y v4 1/4] bpf: Track equal scalars history on per-instruction level
From: Zhenzhong Wu @ 2026-06-21 17:27 UTC (permalink / raw)
To: bpf
Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
martin.lau, song, yonghong.song, kpsingh, haoluo, jolsa,
menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird,
Hao Sun
In-Reply-To: <20260621172735.409355-1-jt26wzz@gmail.com>
From: Eduard Zingerman <eddyz87@gmail.com>
[ Upstream commit 4bf79f9be434e000c8e12fe83b2f4402480f1460 ]
Use bpf_verifier_state->jmp_history to track which registers were
updated by find_equal_scalars() (renamed to collect_linked_regs())
when conditional jump was verified. Use recorded information in
backtrack_insn() to propagate precision.
E.g. for the following program:
while verifying instructions
1: r1 = r0 |
2: if r1 < 8 goto ... | push r0,r1 as linked registers in jmp_history
3: if r0 > 16 goto ... | push r0,r1 as linked registers in jmp_history
4: r2 = r10 |
5: r2 += r0 v mark_chain_precision(r0)
while doing mark_chain_precision(r0)
5: r2 += r0 | mark r0 precise
4: r2 = r10 |
3: if r0 > 16 goto ... | mark r0,r1 as precise
2: if r1 < 8 goto ... | mark r0,r1 as precise
1: r1 = r0 v
Technically, do this as follows:
- Use 10 bits to identify each register that gains range because of
sync_linked_regs():
- 3 bits for frame number;
- 6 bits for register or stack slot number;
- 1 bit to indicate if register is spilled.
- Use u64 as a vector of 6 such records + 4 bits for vector length.
- Augment struct bpf_jmp_history_entry with a field 'linked_regs'
representing such vector.
- When doing check_cond_jmp_op() remember up to 6 registers that
gain range because of sync_linked_regs() in such a vector.
- Don't propagate range information and reset IDs for registers that
don't fit in 6-value vector.
- Push a pair {instruction index, linked registers vector}
to bpf_verifier_state->jmp_history.
- When doing backtrack_insn() check if any of recorded linked
registers is currently marked precise, if so mark all linked
registers as precise.
This also requires fixes for two test_verifier tests:
- precise: test 1
- precise: test 2
Both tests contain the following instruction sequence:
19: (bf) r2 = r9 ; R2=scalar(id=3) R9=scalar(id=3)
20: (a5) if r2 < 0x8 goto pc+1 ; R2=scalar(id=3,umin=8)
21: (95) exit
22: (07) r2 += 1 ; R2_w=scalar(id=3+1,...)
23: (bf) r1 = r10 ; R1_w=fp0 R10=fp0
24: (07) r1 += -8 ; R1_w=fp-8
25: (b7) r3 = 0 ; R3_w=0
26: (85) call bpf_probe_read_kernel#113
The call to bpf_probe_read_kernel() at (26) forces r2 to be precise.
Previously, this forced all registers with same id to become precise
immediately when mark_chain_precision() is called.
After this change, the precision is propagated to registers sharing
same id only when 'if' instruction is backtracked.
Hence verification log for both tests is changed:
regs=r2,r9 -> regs=r2 for instructions 25..20.
Fixes: 904e6ddf4133 ("bpf: Use scalar ids in mark_chain_precision()")
Reported-by: Hao Sun <sunhao.th@gmail.com>
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20240718202357.1746514-2-eddyz87@gmail.com
Closes: https://lore.kernel.org/bpf/CAEf4BzZ0xidVCqB47XnkXcNhkPWF6_nTV7yt+_Lf0kcFEut2Mg@mail.gmail.com/
[ zhenzhong: backport to 6.6.y verifier layout and adapt
sync_linked_regs() to the pre-BPF_ADD_CONST scalar-id code. ]
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
include/linux/bpf_verifier.h | 4 +
kernel/bpf/verifier.c | 253 ++++++++++++++++--
.../bpf/progs/verifier_subprog_precision.c | 2 +-
.../testing/selftests/bpf/verifier/precise.c | 2 +-
4 files changed, 237 insertions(+), 24 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index dba211d3bb9a..9a3b93c24f19 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -345,6 +345,10 @@ struct bpf_jmp_history_entry {
u32 prev_idx : 22;
/* special flags, e.g., whether insn is doing register stack spill/load */
u32 flags : 10;
+ /* additional registers that need precision tracking when this
+ * jump is backtracked, vector of six 10-bit records
+ */
+ u64 linked_regs;
};
/* Maximum number of register states that can exist at once */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 0d90236d0ad9..2268f095203e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3461,9 +3461,87 @@ static bool is_jmp_point(struct bpf_verifier_env *env, int insn_idx)
return env->insn_aux_data[insn_idx].jmp_point;
}
+#define LR_FRAMENO_BITS 3
+#define LR_SPI_BITS 6
+#define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1)
+#define LR_SIZE_BITS 4
+#define LR_FRAMENO_MASK ((1ull << LR_FRAMENO_BITS) - 1)
+#define LR_SPI_MASK ((1ull << LR_SPI_BITS) - 1)
+#define LR_SIZE_MASK ((1ull << LR_SIZE_BITS) - 1)
+#define LR_SPI_OFF LR_FRAMENO_BITS
+#define LR_IS_REG_OFF (LR_SPI_BITS + LR_FRAMENO_BITS)
+#define LINKED_REGS_MAX 6
+
+struct linked_reg {
+ u8 frameno;
+ union {
+ u8 spi;
+ u8 regno;
+ };
+ bool is_reg;
+};
+
+struct linked_regs {
+ int cnt;
+ struct linked_reg entries[LINKED_REGS_MAX];
+};
+
+static struct linked_reg *linked_regs_push(struct linked_regs *s)
+{
+ if (s->cnt < LINKED_REGS_MAX)
+ return &s->entries[s->cnt++];
+
+ return NULL;
+}
+
+/* Use u64 as a vector of 6 10-bit values, use first 4-bits to track
+ * number of elements currently in stack.
+ * Pack one history entry for linked registers as 10 bits in the following format:
+ * - 3-bits frameno
+ * - 6-bits spi_or_reg
+ * - 1-bit is_reg
+ */
+static u64 linked_regs_pack(struct linked_regs *s)
+{
+ u64 val = 0;
+ int i;
+
+ for (i = 0; i < s->cnt; ++i) {
+ struct linked_reg *e = &s->entries[i];
+ u64 tmp = 0;
+
+ tmp |= e->frameno;
+ tmp |= e->spi << LR_SPI_OFF;
+ tmp |= (e->is_reg ? 1 : 0) << LR_IS_REG_OFF;
+
+ val <<= LR_ENTRY_BITS;
+ val |= tmp;
+ }
+ val <<= LR_SIZE_BITS;
+ val |= s->cnt;
+ return val;
+}
+
+static void linked_regs_unpack(u64 val, struct linked_regs *s)
+{
+ int i;
+
+ s->cnt = val & LR_SIZE_MASK;
+ val >>= LR_SIZE_BITS;
+
+ for (i = 0; i < s->cnt; ++i) {
+ struct linked_reg *e = &s->entries[i];
+
+ e->frameno = val & LR_FRAMENO_MASK;
+ e->spi = (val >> LR_SPI_OFF) & LR_SPI_MASK;
+ e->is_reg = (val >> LR_IS_REG_OFF) & 0x1;
+ val >>= LR_ENTRY_BITS;
+ }
+}
+
/* for any branch, call, exit record the history of jmps in the given state */
static int push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state *cur,
- int insn_flags)
+ int insn_flags, u64 linked_regs)
{
u32 cnt = cur->jmp_history_cnt;
struct bpf_jmp_history_entry *p;
@@ -3479,6 +3557,10 @@ static int push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_st
"verifier insn history bug: insn_idx %d cur flags %x new flags %x\n",
env->insn_idx, env->cur_hist_ent->flags, insn_flags);
env->cur_hist_ent->flags |= insn_flags;
+ WARN_ONCE(env->cur_hist_ent->linked_regs != 0,
+ "verifier insn history bug: insn_idx %d linked_regs != 0: %#llx\n",
+ env->insn_idx, env->cur_hist_ent->linked_regs);
+ env->cur_hist_ent->linked_regs = linked_regs;
return 0;
}
@@ -3493,6 +3575,7 @@ static int push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_st
p->idx = env->insn_idx;
p->prev_idx = env->prev_insn_idx;
p->flags = insn_flags;
+ p->linked_regs = linked_regs;
cur->jmp_history_cnt = cnt;
env->cur_hist_ent = p;
@@ -3668,6 +3751,11 @@ static inline bool bt_is_reg_set(struct backtrack_state *bt, u32 reg)
return bt->reg_masks[bt->frame] & (1 << reg);
}
+static inline bool bt_is_frame_reg_set(struct backtrack_state *bt, u32 frame, u32 reg)
+{
+ return bt->reg_masks[frame] & (1 << reg);
+}
+
static inline bool bt_is_frame_slot_set(struct backtrack_state *bt, u32 frame, u32 slot)
{
return bt->stack_masks[frame] & (1ull << slot);
@@ -3717,6 +3805,42 @@ static void fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask)
}
}
+/* If any register R in hist->linked_regs is marked as precise in bt,
+ * do bt_set_frame_{reg,slot}(bt, R) for all registers in hist->linked_regs.
+ */
+static void bt_sync_linked_regs(struct backtrack_state *bt, struct bpf_jmp_history_entry *hist)
+{
+ struct linked_regs linked_regs;
+ bool some_precise = false;
+ int i;
+
+ if (!hist || hist->linked_regs == 0)
+ return;
+
+ linked_regs_unpack(hist->linked_regs, &linked_regs);
+ for (i = 0; i < linked_regs.cnt; ++i) {
+ struct linked_reg *e = &linked_regs.entries[i];
+
+ if ((e->is_reg && bt_is_frame_reg_set(bt, e->frameno, e->regno)) ||
+ (!e->is_reg && bt_is_frame_slot_set(bt, e->frameno, e->spi))) {
+ some_precise = true;
+ break;
+ }
+ }
+
+ if (!some_precise)
+ return;
+
+ for (i = 0; i < linked_regs.cnt; ++i) {
+ struct linked_reg *e = &linked_regs.entries[i];
+
+ if (e->is_reg)
+ bt_set_frame_reg(bt, e->frameno, e->regno);
+ else
+ bt_set_frame_slot(bt, e->frameno, e->spi);
+ }
+}
+
static bool calls_callback(struct bpf_verifier_env *env, int insn_idx);
/* For given verifier state backtrack_insn() is called from the last insn to
@@ -3756,6 +3880,12 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
}
+ /* If there is a history record that some registers gained range at this insn,
+ * propagate precision marks to those registers, so that bt_is_reg_set()
+ * accounts for these registers.
+ */
+ bt_sync_linked_regs(bt, hist);
+
if (class == BPF_ALU || class == BPF_ALU64) {
if (!bt_is_reg_set(bt, dreg))
return 0;
@@ -3985,7 +4115,8 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
*/
bt_set_reg(bt, dreg);
bt_set_reg(bt, sreg);
- /* else dreg <cond> K
+ } else if (BPF_SRC(insn->code) == BPF_K) {
+ /* dreg <cond> K
* Only dreg still needs precision before
* this insn, so for the K-based conditional
* there is nothing new to be marked.
@@ -4003,6 +4134,10 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
/* to be analyzed */
return -ENOTSUPP;
}
+ /* Propagate precision marks to linked registers, to account for
+ * registers marked as precise in this function.
+ */
+ bt_sync_linked_regs(bt, hist);
return 0;
}
@@ -4354,7 +4489,7 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
/* If some register with scalar ID is marked as precise,
* make sure that all registers sharing this ID are also precise.
- * This is needed to estimate effect of find_equal_scalars().
+ * This is needed to estimate effect of sync_linked_regs().
* Do this at the last instruction of each state,
* bpf_reg_state::id fields are valid for these instructions.
*
@@ -4368,7 +4503,7 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
* ...
* --- state #1 {r1.id = A, r2.id = A} ---
* ...
- * if (r2 > 10) goto exit; // find_equal_scalars() assigns range to r1
+ * if (r2 > 10) goto exit; // sync_linked_regs() assigns range to r1
* ...
* --- state #2 {r1.id = A, r2.id = A} ---
* r3 = r10
@@ -4736,7 +4871,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
}
if (insn_flags)
- return push_jmp_history(env, env->cur_state, insn_flags);
+ return push_jmp_history(env, env->cur_state, insn_flags, 0);
return 0;
}
@@ -5032,7 +5167,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
insn_flags = 0; /* we are not restoring spilled register */
}
if (insn_flags)
- return push_jmp_history(env, env->cur_state, insn_flags);
+ return push_jmp_history(env, env->cur_state, insn_flags, 0);
return 0;
}
@@ -13540,7 +13675,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
ptr_reg = dst_reg;
else
/* Make sure ID is cleared otherwise dst_reg min/max could be
- * incorrectly propagated into other registers by find_equal_scalars()
+ * incorrectly propagated into other registers by sync_linked_regs()
*/
dst_reg->id = 0;
if (BPF_SRC(insn->code) == BPF_X) {
@@ -13700,7 +13835,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
*/
if (need_id)
/* Assign src and dst registers the same ID
- * that will be used by find_equal_scalars()
+ * that will be used by sync_linked_regs()
* to propagate min/max range.
*/
src_reg->id = ++env->id_gen;
@@ -13746,7 +13881,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
copy_register_state(dst_reg, src_reg);
/* Make sure ID is cleared if src_reg is not in u32
* range otherwise dst_reg min/max could be incorrectly
- * propagated into src_reg by find_equal_scalars()
+ * propagated into src_reg by sync_linked_regs()
*/
if (!is_src_reg_u32)
dst_reg->id = 0;
@@ -14564,19 +14699,75 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
return true;
}
-static void find_equal_scalars(struct bpf_verifier_state *vstate,
- struct bpf_reg_state *known_reg)
+static void __collect_linked_regs(struct linked_regs *reg_set, struct bpf_reg_state *reg,
+ u32 id, u32 frameno, u32 spi_or_reg, bool is_reg)
{
- struct bpf_func_state *state;
+ struct linked_reg *e;
+
+ if (reg->type != SCALAR_VALUE || reg->id != id)
+ return;
+
+ e = linked_regs_push(reg_set);
+ if (e) {
+ e->frameno = frameno;
+ e->is_reg = is_reg;
+ e->regno = spi_or_reg;
+ } else {
+ reg->id = 0;
+ }
+}
+
+/* For all R being scalar registers or spilled scalar registers
+ * in verifier state, save R in linked_regs if R->id == id.
+ * If there are too many Rs sharing same id, reset id for leftover Rs.
+ */
+static void collect_linked_regs(struct bpf_verifier_state *vstate, u32 id,
+ struct linked_regs *linked_regs)
+{
+ struct bpf_func_state *func;
struct bpf_reg_state *reg;
+ int i, j;
- bpf_for_each_reg_in_vstate(vstate, state, reg, ({
- if (reg->type == SCALAR_VALUE && reg->id == known_reg->id) {
+ for (i = vstate->curframe; i >= 0; i--) {
+ func = vstate->frame[i];
+ for (j = 0; j < BPF_REG_FP; j++) {
+ reg = &func->regs[j];
+ __collect_linked_regs(linked_regs, reg, id, i, j, true);
+ }
+ for (j = 0; j < func->allocated_stack / BPF_REG_SIZE; j++) {
+ if (!is_spilled_reg(&func->stack[j]))
+ continue;
+ reg = &func->stack[j].spilled_ptr;
+ __collect_linked_regs(linked_regs, reg, id, i, j, false);
+ }
+ }
+}
+
+/* For all R in linked_regs, copy known_reg range into R
+ * if R->id == known_reg->id.
+ */
+static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_state *known_reg,
+ struct linked_regs *linked_regs)
+{
+ struct bpf_reg_state *reg;
+ struct linked_reg *e;
+ int i;
+
+ for (i = 0; i < linked_regs->cnt; ++i) {
+ e = &linked_regs->entries[i];
+ reg = e->is_reg ? &vstate->frame[e->frameno]->regs[e->regno]
+ : &vstate->frame[e->frameno]->stack[e->spi].spilled_ptr;
+ if (reg->type != SCALAR_VALUE || reg == known_reg)
+ continue;
+ if (reg->id != known_reg->id)
+ continue;
+ {
s32 saved_subreg_def = reg->subreg_def;
+
copy_register_state(reg, known_reg);
reg->subreg_def = saved_subreg_def;
}
- }));
+ }
}
static int check_cond_jmp_op(struct bpf_verifier_env *env,
@@ -14587,6 +14778,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
struct bpf_reg_state *regs = this_branch->frame[this_branch->curframe]->regs;
struct bpf_reg_state *dst_reg, *other_branch_regs, *src_reg = NULL;
struct bpf_reg_state *eq_branch_regs;
+ struct linked_regs linked_regs = {};
u8 opcode = BPF_OP(insn->code);
bool is_jmp32;
int pred = -1;
@@ -14704,6 +14896,21 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
return 0;
}
+ /* Push scalar registers sharing same ID to jump history,
+ * do this before creating 'other_branch', so that both
+ * 'this_branch' and 'other_branch' share this history
+ * if parent state is created.
+ */
+ if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id)
+ collect_linked_regs(this_branch, src_reg->id, &linked_regs);
+ if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
+ collect_linked_regs(this_branch, dst_reg->id, &linked_regs);
+ if (linked_regs.cnt > 1) {
+ err = push_jmp_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
+ if (err)
+ return err;
+ }
+
other_branch = push_stack(env, *insn_idx + insn->off + 1, *insn_idx,
false);
if (!other_branch)
@@ -14746,8 +14953,9 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
src_reg, dst_reg, opcode);
if (src_reg->id &&
!WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) {
- find_equal_scalars(this_branch, src_reg);
- find_equal_scalars(other_branch, &other_branch_regs[insn->src_reg]);
+ sync_linked_regs(this_branch, src_reg, &linked_regs);
+ sync_linked_regs(other_branch, &other_branch_regs[insn->src_reg],
+ &linked_regs);
}
}
@@ -14759,8 +14967,9 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
if (dst_reg->type == SCALAR_VALUE && dst_reg->id &&
!WARN_ON_ONCE(dst_reg->id != other_branch_regs[insn->dst_reg].id)) {
- find_equal_scalars(this_branch, dst_reg);
- find_equal_scalars(other_branch, &other_branch_regs[insn->dst_reg]);
+ sync_linked_regs(this_branch, dst_reg, &linked_regs);
+ sync_linked_regs(other_branch, &other_branch_regs[insn->dst_reg],
+ &linked_regs);
}
/* if one pointer register is compared to another pointer
@@ -16182,7 +16391,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
*
* First verification path is [1-6]:
* - at (4) same bpf_reg_state::id (b) would be assigned to r6 and r7;
- * - at (5) r6 would be marked <= X, find_equal_scalars() would also mark
+ * - at (5) r6 would be marked <= X, sync_linked_regs() would also mark
* r7 <= X, because r6 and r7 share same id.
* Next verification path is [1-4, 6].
*
@@ -16915,7 +17124,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
* the current state.
*/
if (is_jmp_point(env, env->insn_idx))
- err = err ? : push_jmp_history(env, cur, 0);
+ err = err ? : push_jmp_history(env, cur, 0, 0);
err = err ? : propagate_precision(env, &sl->state);
if (err)
return err;
@@ -17181,7 +17390,7 @@ static int do_check(struct bpf_verifier_env *env)
}
if (is_jmp_point(env, env->insn_idx)) {
- err = push_jmp_history(env, state, 0);
+ err = push_jmp_history(env, state, 0, 0);
if (err)
return err;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
index 4b8b0f45d17d..a188e26f04da 100644
--- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
@@ -141,7 +141,7 @@ __msg("mark_precise: frame0: last_idx 14 first_idx 9")
__msg("mark_precise: frame0: regs=r6 stack= before 13: (bf) r1 = r7")
__msg("mark_precise: frame0: regs=r6 stack= before 12: (27) r6 *= 4")
__msg("mark_precise: frame0: regs=r6 stack= before 11: (25) if r6 > 0x3 goto pc+4")
-__msg("mark_precise: frame0: regs=r6 stack= before 10: (bf) r6 = r0")
+__msg("mark_precise: frame0: regs=r0,r6 stack= before 10: (bf) r6 = r0")
__msg("mark_precise: frame0: regs=r0 stack= before 9: (85) call bpf_loop")
/* State entering callback body popped from states stack */
__msg("from 9 to 17: frame1:")
diff --git a/tools/testing/selftests/bpf/verifier/precise.c b/tools/testing/selftests/bpf/verifier/precise.c
index 8a2ff81d8350..b0b1bcc668ad 100644
--- a/tools/testing/selftests/bpf/verifier/precise.c
+++ b/tools/testing/selftests/bpf/verifier/precise.c
@@ -44,7 +44,7 @@
mark_precise: frame0: regs=r2 stack= before 23\
mark_precise: frame0: regs=r2 stack= before 22\
mark_precise: frame0: regs=r2 stack= before 20\
- mark_precise: frame0: parent state regs=r2 stack=:\
+ mark_precise: frame0: parent state regs=r2,r9 stack=:\
mark_precise: frame0: last_idx 19 first_idx 10\
mark_precise: frame0: regs=r2,r9 stack= before 19\
mark_precise: frame0: regs=r9 stack= before 18\
--
2.43.0
^ permalink raw reply related
* [PATCH stable 6.6.y v4 2/4] bpf: Remove mark_precise_scalar_ids()
From: Zhenzhong Wu @ 2026-06-21 17:27 UTC (permalink / raw)
To: bpf
Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
martin.lau, song, yonghong.song, kpsingh, haoluo, jolsa,
menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird
In-Reply-To: <20260621172735.409355-1-jt26wzz@gmail.com>
From: Eduard Zingerman <eddyz87@gmail.com>
[ Upstream commit 842edb5507a1038e009d27e69d13b94b6f085763 ]
Function mark_precise_scalar_ids() is superseded by
bt_sync_linked_regs() and equal scalars tracking in jump history.
mark_precise_scalar_ids() propagates precision over registers sharing
same ID on parent/child state boundaries, while jump history records
allow bt_sync_linked_regs() to propagate same information with
instruction level granularity, which is strictly more precise.
This commit removes mark_precise_scalar_ids() and updates test cases
in progs/verifier_scalar_ids to reflect new verifier behavior.
The tests are updated in the following manner:
- mark_precise_scalar_ids() propagated precision regardless of
presence of conditional jumps, while new jump history based logic
only kicks in when conditional jumps are present.
Hence test cases are augmented with conditional jumps to still
trigger precision propagation.
- As equal scalars tracking no longer relies on parent/child state
boundaries some test cases are no longer interesting,
such test cases are removed, namely:
- precision_same_state and precision_cross_state are superseded by
linked_regs_bpf_k;
- precision_same_state_broken_link and equal_scalars_broken_link
are superseded by linked_regs_broken_link.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20240718202357.1746514-3-eddyz87@gmail.com
[ zhenzhong: backport to 6.6.y after adapting the first linked-regs
history commit to the older scalar-id verifier layout. ]
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
kernel/bpf/verifier.c | 115 ------------
.../selftests/bpf/progs/verifier_scalar_ids.c | 171 ++++++------------
.../testing/selftests/bpf/verifier/precise.c | 2 +-
3 files changed, 56 insertions(+), 232 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2268f095203e..f638b2d3a42f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4265,96 +4265,6 @@ static void mark_all_scalars_imprecise(struct bpf_verifier_env *env, struct bpf_
}
}
-static bool idset_contains(struct bpf_idset *s, u32 id)
-{
- u32 i;
-
- for (i = 0; i < s->count; ++i)
- if (s->ids[i] == id)
- return true;
-
- return false;
-}
-
-static int idset_push(struct bpf_idset *s, u32 id)
-{
- if (WARN_ON_ONCE(s->count >= ARRAY_SIZE(s->ids)))
- return -EFAULT;
- s->ids[s->count++] = id;
- return 0;
-}
-
-static void idset_reset(struct bpf_idset *s)
-{
- s->count = 0;
-}
-
-/* Collect a set of IDs for all registers currently marked as precise in env->bt.
- * Mark all registers with these IDs as precise.
- */
-static int mark_precise_scalar_ids(struct bpf_verifier_env *env, struct bpf_verifier_state *st)
-{
- struct bpf_idset *precise_ids = &env->idset_scratch;
- struct backtrack_state *bt = &env->bt;
- struct bpf_func_state *func;
- struct bpf_reg_state *reg;
- DECLARE_BITMAP(mask, 64);
- int i, fr;
-
- idset_reset(precise_ids);
-
- for (fr = bt->frame; fr >= 0; fr--) {
- func = st->frame[fr];
-
- bitmap_from_u64(mask, bt_frame_reg_mask(bt, fr));
- for_each_set_bit(i, mask, 32) {
- reg = &func->regs[i];
- if (!reg->id || reg->type != SCALAR_VALUE)
- continue;
- if (idset_push(precise_ids, reg->id))
- return -EFAULT;
- }
-
- bitmap_from_u64(mask, bt_frame_stack_mask(bt, fr));
- for_each_set_bit(i, mask, 64) {
- if (i >= func->allocated_stack / BPF_REG_SIZE)
- break;
- if (!is_spilled_scalar_reg(&func->stack[i]))
- continue;
- reg = &func->stack[i].spilled_ptr;
- if (!reg->id)
- continue;
- if (idset_push(precise_ids, reg->id))
- return -EFAULT;
- }
- }
-
- for (fr = 0; fr <= st->curframe; ++fr) {
- func = st->frame[fr];
-
- for (i = BPF_REG_0; i < BPF_REG_10; ++i) {
- reg = &func->regs[i];
- if (!reg->id)
- continue;
- if (!idset_contains(precise_ids, reg->id))
- continue;
- bt_set_frame_reg(bt, fr, i);
- }
- for (i = 0; i < func->allocated_stack / BPF_REG_SIZE; ++i) {
- if (!is_spilled_scalar_reg(&func->stack[i]))
- continue;
- reg = &func->stack[i].spilled_ptr;
- if (!reg->id)
- continue;
- if (!idset_contains(precise_ids, reg->id))
- continue;
- bt_set_frame_slot(bt, fr, i);
- }
- }
-
- return 0;
-}
-
/*
* __mark_chain_precision() backtracks BPF program instruction sequence and
* chain of verifier states making sure that register *regno* (if regno >= 0)
@@ -4487,31 +4397,6 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
bt->frame, last_idx, first_idx, subseq_idx);
}
- /* If some register with scalar ID is marked as precise,
- * make sure that all registers sharing this ID are also precise.
- * This is needed to estimate effect of sync_linked_regs().
- * Do this at the last instruction of each state,
- * bpf_reg_state::id fields are valid for these instructions.
- *
- * Allows to track precision in situation like below:
- *
- * r2 = unknown value
- * ...
- * --- state #0 ---
- * ...
- * r1 = r2 // r1 and r2 now share the same ID
- * ...
- * --- state #1 {r1.id = A, r2.id = A} ---
- * ...
- * if (r2 > 10) goto exit; // sync_linked_regs() assigns range to r1
- * ...
- * --- state #2 {r1.id = A, r2.id = A} ---
- * r3 = r10
- * r3 += r1 // need to mark both r1 and r2
- */
- if (mark_precise_scalar_ids(env, st))
- return -EFAULT;
-
if (last_idx < 0) {
/* we are at the entry into subprog, which
* is expected for global funcs, but only if
diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
index 22a6cf6e8255..f70392bf696c 100644
--- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
+++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
@@ -5,54 +5,27 @@
#include "bpf_misc.h"
/* Check that precision marks propagate through scalar IDs.
- * Registers r{0,1,2} have the same scalar ID at the moment when r0 is
- * marked to be precise, this mark is immediately propagated to r{1,2}.
+ * Registers r{0,1,2} have the same scalar ID.
+ * Range information is propagated for scalars sharing same ID.
+ * Check that precision mark for r0 causes precision marks for r{1,2}
+ * when range information is propagated for 'if <reg> <op> <const>' insn.
*/
SEC("socket")
__success __log_level(2)
-__msg("frame0: regs=r0,r1,r2 stack= before 4: (bf) r3 = r10")
-__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0")
-__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0")
-__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
-__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns")
-__flag(BPF_F_TEST_STATE_FREQ)
-__naked void precision_same_state(void)
-{
- asm volatile (
- /* r0 = random number up to 0xff */
- "call %[bpf_ktime_get_ns];"
- "r0 &= 0xff;"
- /* tie r0.id == r1.id == r2.id */
- "r1 = r0;"
- "r2 = r0;"
- /* force r0 to be precise, this immediately marks r1 and r2 as
- * precise as well because of shared IDs
- */
- "r3 = r10;"
- "r3 += r0;"
- "r0 = 0;"
- "exit;"
- :
- : __imm(bpf_ktime_get_ns)
- : __clobber_all);
-}
-
-/* Same as precision_same_state, but mark propagates through state /
- * parent state boundary.
- */
-SEC("socket")
-__success __log_level(2)
-__msg("frame0: last_idx 6 first_idx 5 subseq_idx -1")
-__msg("frame0: regs=r0,r1,r2 stack= before 5: (bf) r3 = r10")
+/* first 'if' branch */
+__msg("6: (0f) r3 += r0")
+__msg("frame0: regs=r0 stack= before 4: (25) if r1 > 0x7 goto pc+0")
__msg("frame0: parent state regs=r0,r1,r2 stack=:")
-__msg("frame0: regs=r0,r1,r2 stack= before 4: (05) goto pc+0")
__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0")
-__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0")
-__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
-__msg("frame0: parent state regs=r0 stack=:")
-__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns")
+/* second 'if' branch */
+__msg("from 4 to 5: ")
+__msg("6: (0f) r3 += r0")
+__msg("frame0: regs=r0 stack= before 5: (bf) r3 = r10")
+__msg("frame0: regs=r0 stack= before 4: (25) if r1 > 0x7 goto pc+0")
+/* parent state already has r{0,1,2} as precise */
+__msg("frame0: parent state regs= stack=:")
__flag(BPF_F_TEST_STATE_FREQ)
-__naked void precision_cross_state(void)
+__naked void linked_regs_bpf_k(void)
{
asm volatile (
/* r0 = random number up to 0xff */
@@ -61,9 +34,8 @@ __naked void precision_cross_state(void)
/* tie r0.id == r1.id == r2.id */
"r1 = r0;"
"r2 = r0;"
- /* force checkpoint */
- "goto +0;"
- /* force r0 to be precise, this immediately marks r1 and r2 as
+ "if r1 > 7 goto +0;"
+ /* force r0 to be precise, this eventually marks r1 and r2 as
* precise as well because of shared IDs
*/
"r3 = r10;"
@@ -75,59 +47,18 @@ __naked void precision_cross_state(void)
: __clobber_all);
}
-/* Same as precision_same_state, but break one of the
+/* Same as linked_regs_bpf_k, but break one of the
* links, note that r1 is absent from regs=... in __msg below.
*/
SEC("socket")
__success __log_level(2)
-__msg("frame0: regs=r0,r2 stack= before 5: (bf) r3 = r10")
-__msg("frame0: regs=r0,r2 stack= before 4: (b7) r1 = 0")
-__msg("frame0: regs=r0,r2 stack= before 3: (bf) r2 = r0")
-__msg("frame0: regs=r0 stack= before 2: (bf) r1 = r0")
-__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
-__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns")
-__flag(BPF_F_TEST_STATE_FREQ)
-__naked void precision_same_state_broken_link(void)
-{
- asm volatile (
- /* r0 = random number up to 0xff */
- "call %[bpf_ktime_get_ns];"
- "r0 &= 0xff;"
- /* tie r0.id == r1.id == r2.id */
- "r1 = r0;"
- "r2 = r0;"
- /* break link for r1, this is the only line that differs
- * compared to the previous test
- */
- "r1 = 0;"
- /* force r0 to be precise, this immediately marks r1 and r2 as
- * precise as well because of shared IDs
- */
- "r3 = r10;"
- "r3 += r0;"
- "r0 = 0;"
- "exit;"
- :
- : __imm(bpf_ktime_get_ns)
- : __clobber_all);
-}
-
-/* Same as precision_same_state_broken_link, but with state /
- * parent state boundary.
- */
-SEC("socket")
-__success __log_level(2)
-__msg("frame0: regs=r0,r2 stack= before 6: (bf) r3 = r10")
-__msg("frame0: regs=r0,r2 stack= before 5: (b7) r1 = 0")
-__msg("frame0: parent state regs=r0,r2 stack=:")
-__msg("frame0: regs=r0,r1,r2 stack= before 4: (05) goto pc+0")
-__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0")
-__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0")
-__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
+__msg("7: (0f) r3 += r0")
+__msg("frame0: regs=r0 stack= before 6: (bf) r3 = r10")
__msg("frame0: parent state regs=r0 stack=:")
-__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns")
+__msg("frame0: regs=r0 stack= before 5: (25) if r0 > 0x7 goto pc+0")
+__msg("frame0: parent state regs=r0,r2 stack=:")
__flag(BPF_F_TEST_STATE_FREQ)
-__naked void precision_cross_state_broken_link(void)
+__naked void linked_regs_broken_link(void)
{
asm volatile (
/* r0 = random number up to 0xff */
@@ -136,18 +67,13 @@ __naked void precision_cross_state_broken_link(void)
/* tie r0.id == r1.id == r2.id */
"r1 = r0;"
"r2 = r0;"
- /* force checkpoint, although link between r1 and r{0,2} is
- * broken by the next statement current precision tracking
- * algorithm can't react to it and propagates mark for r1 to
- * the parent state.
- */
- "goto +0;"
/* break link for r1, this is the only line that differs
- * compared to precision_cross_state()
+ * compared to the previous test
*/
"r1 = 0;"
- /* force r0 to be precise, this immediately marks r1 and r2 as
- * precise as well because of shared IDs
+ "if r0 > 7 goto +0;"
+ /* force r0 to be precise,
+ * this eventually marks r2 as precise because of shared IDs
*/
"r3 = r10;"
"r3 += r0;"
@@ -164,10 +90,16 @@ __naked void precision_cross_state_broken_link(void)
*/
SEC("socket")
__success __log_level(2)
-__msg("11: (0f) r2 += r1")
+__msg("12: (0f) r2 += r1")
/* Current state */
-__msg("frame2: last_idx 11 first_idx 10 subseq_idx -1")
-__msg("frame2: regs=r1 stack= before 10: (bf) r2 = r10")
+__msg("frame2: last_idx 12 first_idx 11 subseq_idx -1 ")
+__msg("frame2: regs=r1 stack= before 11: (bf) r2 = r10")
+__msg("frame2: parent state regs=r1 stack=")
+__msg("frame1: parent state regs= stack=")
+__msg("frame0: parent state regs= stack=")
+/* Parent state */
+__msg("frame2: last_idx 10 first_idx 10 subseq_idx 11 ")
+__msg("frame2: regs=r1 stack= before 10: (25) if r1 > 0x7 goto pc+0")
__msg("frame2: parent state regs=r1 stack=")
/* frame1.r{6,7} are marked because mark_precise_scalar_ids()
* looks for all registers with frame2.r1.id in the current state
@@ -192,7 +124,7 @@ __msg("frame1: regs=r1 stack= before 4: (85) call pc+1")
__msg("frame0: parent state regs=r1,r6 stack=")
/* Parent state */
__msg("frame0: last_idx 3 first_idx 1 subseq_idx 4")
-__msg("frame0: regs=r0,r1,r6 stack= before 3: (bf) r6 = r0")
+__msg("frame0: regs=r1,r6 stack= before 3: (bf) r6 = r0")
__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0")
__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
__flag(BPF_F_TEST_STATE_FREQ)
@@ -230,7 +162,8 @@ static __naked __noinline __used
void precision_many_frames__bar(void)
{
asm volatile (
- /* force r1 to be precise, this immediately marks:
+ "if r1 > 7 goto +0;"
+ /* force r1 to be precise, this eventually marks:
* - bar frame r1
* - foo frame r{1,6,7}
* - main frame r{1,6}
@@ -247,14 +180,16 @@ void precision_many_frames__bar(void)
*/
SEC("socket")
__success __log_level(2)
+__msg("11: (0f) r2 += r1")
/* foo frame */
-__msg("frame1: regs=r1 stack=-8,-16 before 9: (bf) r2 = r10")
+__msg("frame1: regs=r1 stack= before 10: (bf) r2 = r10")
+__msg("frame1: regs=r1 stack= before 9: (25) if r1 > 0x7 goto pc+0")
__msg("frame1: regs=r1 stack=-8,-16 before 8: (7b) *(u64 *)(r10 -16) = r1")
__msg("frame1: regs=r1 stack=-8 before 7: (7b) *(u64 *)(r10 -8) = r1")
__msg("frame1: regs=r1 stack= before 4: (85) call pc+2")
/* main frame */
-__msg("frame0: regs=r0,r1 stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r1")
-__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0")
+__msg("frame0: regs=r1 stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r1")
+__msg("frame0: regs=r1 stack= before 2: (bf) r1 = r0")
__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255")
__flag(BPF_F_TEST_STATE_FREQ)
__naked void precision_stack(void)
@@ -283,7 +218,8 @@ void precision_stack__foo(void)
*/
"*(u64*)(r10 - 8) = r1;"
"*(u64*)(r10 - 16) = r1;"
- /* force r1 to be precise, this immediately marks:
+ "if r1 > 7 goto +0;"
+ /* force r1 to be precise, this eventually marks:
* - foo frame r1,fp{-8,-16}
* - main frame r1,fp{-8}
*/
@@ -299,15 +235,17 @@ void precision_stack__foo(void)
SEC("socket")
__success __log_level(2)
/* r{6,7} */
-__msg("11: (0f) r3 += r7")
-__msg("frame0: regs=r6,r7 stack= before 10: (bf) r3 = r10")
+__msg("12: (0f) r3 += r7")
+__msg("frame0: regs=r7 stack= before 11: (bf) r3 = r10")
+__msg("frame0: regs=r7 stack= before 9: (25) if r7 > 0x7 goto pc+0")
/* ... skip some insns ... */
__msg("frame0: regs=r6,r7 stack= before 3: (bf) r7 = r0")
__msg("frame0: regs=r0,r6 stack= before 2: (bf) r6 = r0")
/* r{8,9} */
-__msg("12: (0f) r3 += r9")
-__msg("frame0: regs=r8,r9 stack= before 11: (0f) r3 += r7")
+__msg("13: (0f) r3 += r9")
+__msg("frame0: regs=r9 stack= before 12: (0f) r3 += r7")
/* ... skip some insns ... */
+__msg("frame0: regs=r9 stack= before 10: (25) if r9 > 0x7 goto pc+0")
__msg("frame0: regs=r8,r9 stack= before 7: (bf) r9 = r0")
__msg("frame0: regs=r0,r8 stack= before 6: (bf) r8 = r0")
__flag(BPF_F_TEST_STATE_FREQ)
@@ -328,8 +266,9 @@ __naked void precision_two_ids(void)
"r9 = r0;"
/* clear r0 id */
"r0 = 0;"
- /* force checkpoint */
- "goto +0;"
+ /* propagate equal scalars precision */
+ "if r7 > 7 goto +0;"
+ "if r9 > 7 goto +0;"
"r3 = r10;"
/* force r7 to be precise, this also marks r6 */
"r3 += r7;"
diff --git a/tools/testing/selftests/bpf/verifier/precise.c b/tools/testing/selftests/bpf/verifier/precise.c
index b0b1bcc668ad..59a020c35647 100644
--- a/tools/testing/selftests/bpf/verifier/precise.c
+++ b/tools/testing/selftests/bpf/verifier/precise.c
@@ -106,7 +106,7 @@
mark_precise: frame0: regs=r2 stack= before 22\
mark_precise: frame0: parent state regs=r2 stack=:\
mark_precise: frame0: last_idx 20 first_idx 20\
- mark_precise: frame0: regs=r2,r9 stack= before 20\
+ mark_precise: frame0: regs=r2 stack= before 20\
mark_precise: frame0: parent state regs=r2,r9 stack=:\
mark_precise: frame0: last_idx 19 first_idx 17\
mark_precise: frame0: regs=r2,r9 stack= before 19\
--
2.43.0
^ permalink raw reply related
* [PATCH stable 6.6.y v4 3/4] selftests/bpf: Tests for per-insn sync_linked_regs() precision tracking
From: Zhenzhong Wu @ 2026-06-21 17:27 UTC (permalink / raw)
To: bpf
Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
martin.lau, song, yonghong.song, kpsingh, haoluo, jolsa,
menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird
In-Reply-To: <20260621172735.409355-1-jt26wzz@gmail.com>
From: Eduard Zingerman <eddyz87@gmail.com>
[ Upstream commit bebc17b1c03b224a0b4aec6a171815e39f8ba9bc ]
Add a few test cases to verify precision tracking for scalars gaining
range because of sync_linked_regs():
- check what happens when more than 6 registers might gain range in
sync_linked_regs();
- check if precision is propagated correctly when operand of
conditional jump gained range in sync_linked_regs() and one of
linked registers is marked precise;
- check if precision is propagated correctly when operand of
conditional jump gained range in sync_linked_regs() and a
other-linked operand of the conditional jump is marked precise;
- add a minimized reproducer for precision tracking bug reported in [0];
- Check that mark_chain_precision() for one of the conditional jump
operands does not trigger equal scalars precision propagation.
[0] https://lore.kernel.org/bpf/CAEf4BzZ0xidVCqB47XnkXcNhkPWF6_nTV7yt+_Lf0kcFEut2Mg@mail.gmail.com/
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20240718202357.1746514-4-eddyz87@gmail.com
[ zhenzhong: keep the linked_regs_broken_link_2 reject check, but
drop the mark_precise log expectations because 6.6.y does not derive
the scalar-vs-scalar range for that non-constant JMP_X comparison. ]
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
.../selftests/bpf/progs/verifier_scalar_ids.c | 162 ++++++++++++++++++
1 file changed, 162 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
index f70392bf696c..2eb85eb3a06c 100644
--- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
+++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
@@ -47,6 +47,72 @@ __naked void linked_regs_bpf_k(void)
: __clobber_all);
}
+/* Registers r{0,1,2} share same ID when 'if r1 > ...' insn is processed,
+ * check that verifier marks r{1,2} as precise while backtracking
+ * 'if r1 > ...' with r0 already marked.
+ */
+SEC("socket")
+__success __log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("frame0: regs=r0 stack= before 5: (2d) if r1 > r3 goto pc+0")
+__msg("frame0: parent state regs=r0,r1,r2,r3 stack=:")
+__msg("frame0: regs=r0,r1,r2,r3 stack= before 4: (b7) r3 = 7")
+__naked void linked_regs_bpf_x_src(void)
+{
+ asm volatile (
+ /* r0 = random number up to 0xff */
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ /* tie r0.id == r1.id == r2.id */
+ "r1 = r0;"
+ "r2 = r0;"
+ "r3 = 7;"
+ "if r1 > r3 goto +0;"
+ /* force r0 to be precise, this eventually marks r1 and r2 as
+ * precise as well because of shared IDs
+ */
+ "r4 = r10;"
+ "r4 += r0;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+/* Registers r{0,1,2} share same ID when 'if r1 > r3' insn is processed,
+ * check that verifier marks r{0,1,2} as precise while backtracking
+ * 'if r1 > r3' with r3 already marked.
+ */
+SEC("socket")
+__success __log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("frame0: regs=r3 stack= before 5: (2d) if r1 > r3 goto pc+0")
+__msg("frame0: parent state regs=r0,r1,r2,r3 stack=:")
+__msg("frame0: regs=r0,r1,r2,r3 stack= before 4: (b7) r3 = 7")
+__naked void linked_regs_bpf_x_dst(void)
+{
+ asm volatile (
+ /* r0 = random number up to 0xff */
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ /* tie r0.id == r1.id == r2.id */
+ "r1 = r0;"
+ "r2 = r0;"
+ "r3 = 7;"
+ "if r1 > r3 goto +0;"
+ /* force r0 to be precise, this eventually marks r1 and r2 as
+ * precise as well because of shared IDs
+ */
+ "r4 = r10;"
+ "r4 += r3;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
/* Same as linked_regs_bpf_k, but break one of the
* links, note that r1 is absent from regs=... in __msg below.
*/
@@ -280,6 +346,102 @@ __naked void precision_two_ids(void)
: __clobber_all);
}
+SEC("socket")
+__success __log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+/* check thar r0 and r6 have different IDs after 'if',
+ * collect_linked_regs() can't tie more than 6 registers for a single insn.
+ */
+__msg("8: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1")
+__msg("9: (bf) r6 = r6 ; R6_w=scalar(id=2")
+/* check that r{0-5} are marked precise after 'if' */
+__msg("frame0: regs=r0 stack= before 8: (25) if r0 > 0x7 goto pc+0")
+__msg("frame0: parent state regs=r0,r1,r2,r3,r4,r5 stack=:")
+__naked void linked_regs_too_many_regs(void)
+{
+ asm volatile (
+ /* r0 = random number up to 0xff */
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ /* tie r{0-6} IDs */
+ "r1 = r0;"
+ "r2 = r0;"
+ "r3 = r0;"
+ "r4 = r0;"
+ "r5 = r0;"
+ "r6 = r0;"
+ /* propagate range for r{0-6} */
+ "if r0 > 7 goto +0;"
+ /* make r6 appear in the log */
+ "r6 = r6;"
+ /* force r0 to be precise,
+ * this would cause r{0-4} to be precise because of shared IDs
+ */
+ "r7 = r10;"
+ "r7 += r0;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+SEC("socket")
+__failure __log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("div by zero")
+__naked void linked_regs_broken_link_2(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r7 = r0;"
+ "r8 = r0;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 > 1 goto +0;"
+ /* r7.id == r8.id,
+ * thus r7 precision implies r8 precision,
+ * which implies r0 precision because of the conditional below.
+ */
+ "if r8 >= r0 goto 1f;"
+ /* break id relation between r7 and r8 */
+ "r8 += r8;"
+ /* make r7 precise */
+ "if r7 == 0 goto 1f;"
+ "r0 /= 0;"
+"1:"
+ "r0 = 42;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Check that mark_chain_precision() for one of the conditional jump
+ * operands does not trigger equal scalars precision propagation.
+ */
+SEC("socket")
+__success __log_level(2)
+__msg("3: (25) if r1 > 0x100 goto pc+0")
+__msg("frame0: regs=r1 stack= before 2: (bf) r1 = r0")
+__naked void cjmp_no_linked_regs_trigger(void)
+{
+ asm volatile (
+ /* r0 = random number up to 0xff */
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ /* tie r0.id == r1.id */
+ "r1 = r0;"
+ /* the jump below would be predicted, thus r1 would be marked precise,
+ * this should not imply precision mark for r0
+ */
+ "if r1 > 256 goto +0;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
/* Verify that check_ids() is used by regsafe() for scalars.
*
* r9 = ... some pointer with range X ...
--
2.43.0
^ permalink raw reply related
* [PATCH stable 6.6.y v4 4/4] selftests/bpf: Update comments find_equal_scalars->sync_linked_regs
From: Zhenzhong Wu @ 2026-06-21 17:27 UTC (permalink / raw)
To: bpf
Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
martin.lau, song, yonghong.song, kpsingh, haoluo, jolsa,
menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird
In-Reply-To: <20260621172735.409355-1-jt26wzz@gmail.com>
From: Eduard Zingerman <eddyz87@gmail.com>
[ Upstream commit cfbf25481d6dec0089c99c9d33a2ea634fe8f008 ]
find_equal_scalars() is renamed to sync_linked_regs(),
this commit updates existing references in the selftests comments.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20240718202357.1746514-5-eddyz87@gmail.com
[ zhenzhong: only two pre-existing comments still needed updating in 6.6.y. ]
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
tools/testing/selftests/bpf/progs/verifier_spill_fill.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index 1f71f596d33f..07a2527a8f47 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
@@ -392,7 +392,7 @@ __naked void spill_32bit_of_64bit_fail(void)
*(u32*)(r10 - 8) = r1; \
/* 32-bit fill r2 from stack. */ \
r2 = *(u32*)(r10 - 8); \
- /* Compare r2 with another register to trigger find_equal_scalars.\
+ /* Compare r2 with another register to trigger sync_linked_regs.\
* Having one random bit is important here, otherwise the verifier cuts\
* the corners. If the ID was mistakenly preserved on spill, this would\
* cause the verifier to think that r1 is also equal to zero in one of\
@@ -431,7 +431,7 @@ __naked void spill_16bit_of_32bit_fail(void)
*(u16*)(r10 - 8) = r1; \
/* 16-bit fill r2 from stack. */ \
r2 = *(u16*)(r10 - 8); \
- /* Compare r2 with another register to trigger find_equal_scalars.\
+ /* Compare r2 with another register to trigger sync_linked_regs.\
* Having one random bit is important here, otherwise the verifier cuts\
* the corners. If the ID was mistakenly preserved on spill, this would\
* cause the verifier to think that r1 is also equal to zero in one of\
--
2.43.0
^ permalink raw reply related
* [PATCH iproute2] ip: return correct status from help command
From: Rose Wright @ 2026-06-21 18:03 UTC (permalink / raw)
To: netdev; +Cc: stephen, Rose Wright
Currently, "ip help" or "ip -help" always returns an error code because usage() is used as a fall through on "ip" and defaults to stderr with -1.
This is a minor bug that breaks "ip help | grep" and other scripts that rely on standard exit codes. The fix is to pass the status code as a parameter into usage() and change stderr to stdout when needed.
Signed-off-by: Rose Wright <rosesophiewright@gmail.com>
---
ip/ip.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/ip/ip.c b/ip/ip.c
index e4b71bde..ee4595e8 100644
--- a/ip/ip.c
+++ b/ip/ip.c
@@ -52,11 +52,12 @@ const char *get_ip_lib_dir(void)
return lib_dir;
}
-static void usage(void) __attribute__((noreturn));
+static void usage(int status) __attribute__((noreturn));
-static void usage(void)
+static void usage(int status)
{
- fprintf(stderr,
+ FILE *out = status == 0 ? stdout : stderr;
+ fprintf(out,
"Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n"
" ip [ -force ] -batch filename\n"
"where OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |\n"
@@ -72,12 +73,12 @@ static void usage(void)
" -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
" -rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |\n"
" -c[olor]}\n");
- exit(-1);
+ exit(status);
}
static int do_help(int argc, char **argv)
{
- usage();
+ usage(0);
return 0;
}
@@ -279,7 +280,7 @@ int main(int argc, char **argv)
rcvbuf = size;
} else if (matches_color(opt, &color)) {
} else if (matches(opt, "-help") == 0) {
- usage();
+ usage(0);
} else if (matches(opt, "-netns") == 0) {
NEXT_ARG();
if (netns_switch(argv[1]))
@@ -321,5 +322,5 @@ int main(int argc, char **argv)
return do_cmd(argv[1], argc-1, argv+1, true);
rtnl_close(&rth);
- usage();
+ usage(-1);
}
--
2.54.0
^ permalink raw reply related
* [PATCH nf-next v3] netfilter: TCPMSS: handle packets with unaligned MSS option
From: Kacper Kokot @ 2026-06-21 18:49 UTC (permalink / raw)
To: netfilter-devel
Cc: pablo, kadlec, fmancera, fw, david.laight.linux, Kacper Kokot,
Phil Sutter, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, coreteam, netdev, linux-kernel
In-Reply-To: <20260528223412.27311-1-kacper.kokot.44@gmail.com>
RFC 9293 permits TCP options to begin on any octet boundary. Padding
to a word boundary with NOPs is a sender convention, not a requirement,
and robust receivers must handle unaligned options (MUST-64).
The xt_TCPMSS target's incremental checksum update assumes the MSS
option is word-aligned. When it's not, the modified bytes straddle
two checksum words and the resulting checksum is incorrect. The mangled
packet may then fail checksum validation and be dropped downstream.
That said, all mainstream stacks emit a word-aligned MSS, this change is
motivated by spec conformance rather than a bug observed in the wild.
Extend the checksum update to handle unaligned MSS options. When the
changed word is unaligned, the modified bytes b' and c' straddle two
checksum words w1 and w2:
| w1 | w2 |
OLD | a b | c d |
NEW | a b' | c' d |
The two-step update C' = C - w1 + w1' - w2 + w2' reduces algebraically
to a single word incremental checksum update with byteswapped operands:
C' = C - w1 - w2 + w1' + w2'
= C - (a * 2^8 + b) - (c * 2^8 + d)
+ (a * 2^8 + b') + (c' * 2^8 + d)
= C + 2^8 * (a - a + c' - c) + (b' - b + d - d)
= C + 2^8 * (c' - c) + (b' - b)
= C - (2^8 * c + b) + (2^8 * c' + b')
So the unaligned case adds no extra checksum operations.
Signed-off-by: Kacper Kokot <kacper.kokot.44@gmail.com>
---
v3:
- Reframe as enhancement, not a fix (Pablo/Fernando)
- Rename subject to xt_TCPMSS, drop "fix" wording
- Reword commit message: packet may fail checksum validation and be
dropped downstream (Pablo)
- Target nf-next (Fernando)
- Use __be16 for csum_oldmss/csum_newmss (sparse warning from
kernel test robot)
- Reorder local variable declarations to reverse xmas tree (Fernando)
v2:
- Use get_unaligned_be16 (Fernando's suggestion)
- Fix alignment check expression (David)
- Mention it's a theoretical bug in the commit message
- Drop cc stable, the bug is only theoretical
diff --git a/net/netfilter/xt_TCPMSS.c b/net/netfilter/xt_TCPMSS.c
index 80e1634bc51f..037add799d41 100644
--- a/net/netfilter/xt_TCPMSS.c
+++ b/net/netfilter/xt_TCPMSS.c
@@ -116,9 +116,10 @@ tcpmss_mangle_packet(struct sk_buff *skb,
opt = (u_int8_t *)tcph;
for (i = sizeof(struct tcphdr); i <= tcp_hdrlen - TCPOLEN_MSS; i += optlen(opt, i)) {
if (opt[i] == TCPOPT_MSS && opt[i+1] == TCPOLEN_MSS) {
+ __be16 csum_oldmss, csum_newmss;
u_int16_t oldmss;
- oldmss = (opt[i+2] << 8) | opt[i+3];
+ oldmss = get_unaligned_be16(&opt[i + 2]);
/* Never increase MSS, even when setting it, as
* doing so results in problems for hosts that rely
@@ -130,8 +131,25 @@ tcpmss_mangle_packet(struct sk_buff *skb,
opt[i+2] = (newmss & 0xff00) >> 8;
opt[i+3] = newmss & 0x00ff;
+ csum_oldmss = htons(oldmss);
+ csum_newmss = htons(newmss);
+
+ if (((char *)&opt[i + 2] - (char *)tcph) & 0x1) {
+ /* MSS option is unaligned: the modified bytes
+ * straddle two checksum words. Byteswapping
+ * the operands lets a single incremental
+ * update produce the correct checksum delta
+ * (see commit message for the derivation).
+ */
+ csum_oldmss = htons(swab16(oldmss));
+ csum_newmss = htons(swab16(newmss));
+ } else {
+ csum_oldmss = htons(oldmss);
+ csum_newmss = htons(newmss);
+ }
+
inet_proto_csum_replace2(&tcph->check, skb,
- htons(oldmss), htons(newmss),
+ csum_oldmss, csum_newmss,
false);
return 0;
}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next RESEND v3 0/2] udp: fix FOU/GUE over multicast
From: Anton Danilov @ 2026-06-21 19:04 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, davem, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
This is a resend of the v3 series originally posted on 2026-05-05,
which did not receive review feedback during the previous net-next
window. No changes since v3; rebased cleanly on current net-next
(after the net-next-7.2 merge).
v3 archive:
https://lore.kernel.org/netdev/cover.1777934869.git.littlesmilingcloud@gmail.com/
UDP encapsulation (FOU, GUE) has never worked correctly with multicast
destination addresses. When a FOU-encapsulated packet arrives at a
multicast address, it enters __udp4_lib_mcast_deliver() which calls
consume_skb() on packets that need resubmission to the inner protocol
handler, silently dropping them instead.
The unicast delivery path handles this correctly by returning -ret,
but the multicast path was never updated to support UDP encapsulation
resubmit.
This causes silent packet loss for FOU/GRETAP tunnels configured with
multicast remote addresses. The loss ratio depends on the early demux
cache hit rate - packets that hit early demux bypass the multicast path
and work correctly, masking the issue.
Reproducing the issue:
ip netns add ns_a && ip netns add ns_b
ip link add veth0 type veth peer name veth1
ip link set veth0 netns ns_a && ip link set veth1 netns ns_b
ip -n ns_a addr add 10.0.0.1/24 dev veth0 && ip -n ns_a link set veth0 up
ip -n ns_b addr add 10.0.0.2/24 dev veth1 && ip -n ns_b link set veth1 up
# Multicast routes
ip -n ns_a route add 239.0.0.0/8 dev veth0
ip -n ns_b route add 239.0.0.0/8 dev veth1
# Disable early demux to expose the issue (otherwise it's partially masked)
ip netns exec ns_b sysctl -w net.ipv4.ip_early_demux=0
# Join multicast group on receiver
ip -n ns_b addr add 239.0.0.1/32 dev veth1 autojoin
# Sender: GRETAP with FOU encap
ip -n ns_a link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.1 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_a link set eoudp0 up
ip -n ns_a addr add 192.168.99.1/24 dev eoudp0
# Receiver: FOU listener + GRETAP
ip netns exec ns_b ip fou add port 4797 ipproto 47
ip -n ns_b link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.2 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_b link set eoudp0 up
ip -n ns_b addr add 192.168.99.2/24 dev eoudp0
# Static neigh: ARP replies can't traverse unidirectional mcast tunnel
recv_mac=$(ip -n ns_b link show eoudp0 | awk '/ether/{print $2}')
ip -n ns_a neigh add 192.168.99.2 lladdr $recv_mac dev eoudp0
# Test: ping through the FOU/GRETAP tunnel
ip netns exec ns_a ping -c 100 192.168.99.2
# -> without this patch: 0 packets received on eoudp0
# -> with this patch: all packets received on eoudp0
AI assistance (Claude, claude-opus-4-6) was used during root cause
analysis of the kernel source code (tracing the call chain from
udp_queue_rcv_skb through encap_rcv to ip_protocol_deliver_rcu,
comparing unicast/GSO/multicast paths) and during patch and selftest
authoring. The fix approach was identified by observing that the
unicast path (udp_unicast_rcv_skb) already handles encap resubmit
correctly via return -ret, while the multicast path did not.
Changes since v2:
- Use return -ret instead of calling ip_protocol_deliver_rcu()
directly, matching the unicast path and avoiding call stack
growth with nested encapsulations (Kuniyuki Iwashima)
- Only change the first-socket path; the clone loop is not
reachable for tunnel sockets (no SO_REUSEADDR/SO_REUSEPORT)
- Replace Python packet generator with ping through a properly
configured FOU/GRETAP tunnel in the selftest
- Add static neighbor entry in the test (ARP replies cannot
traverse the unidirectional multicast tunnel)
Changes since v1 (RFC):
- Moved inline Python packet generator into a separate helper
- Fixed author email typo in Signed-off-by
Anton Danilov (2):
udp: fix encapsulation packet resubmit in multicast deliver
selftests: net: add FOU multicast encapsulation resubmit test
net/ipv4/udp.c | 6 +-
net/ipv6/udp.c | 6 +-
tools/testing/selftests/net/Makefile | 1 +
.../testing/selftests/net/fou_mcast_encap.sh | 112 ++++++++++++++++++
4 files changed, 121 insertions(+), 4 deletions(-)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
--
2.47.3
^ permalink raw reply
* [PATCH net-next RESEND v3 1/2] udp: fix encapsulation packet resubmit in multicast deliver
From: Anton Danilov @ 2026-06-21 19:04 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, davem, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <cover.1782067871.git.littlesmilingcloud@gmail.com>
When a UDP encapsulation socket (e.g., FOU) receives a multicast
packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver()
call consume_skb() when udp_queue_rcv_skb() returns a positive value.
A positive return value from udp_queue_rcv_skb() indicates that the
encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header
and wants the packet to be resubmitted to the IP protocol handler
for further processing (e.g., as a GRE packet).
The unicast path in udp_unicast_rcv_skb() handles this correctly by
returning -ret, which propagates up to ip_protocol_deliver_rcu() for
resubmission. However, the multicast path destroys the packet via
consume_skb() instead of resubmitting it, causing silent packet loss.
This affects any UDP encapsulation (FOU, GUE) combined with multicast
destination addresses.
Fix this by returning -ret instead of calling consume_skb() when the
return value is positive, matching the behavior of the unicast path.
This avoids growing the call stack compared to calling
ip_protocol_deliver_rcu() directly.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
net/ipv4/udp.c | 6 ++++--
net/ipv6/udp.c | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 70f6cbd4ef73..b0910659391e 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2475,6 +2475,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -2519,8 +2520,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udp_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udp_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return -ret;
} else {
kfree_skb(skb);
__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 15e032194ecc..ff2e389e286b 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udpv6_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udpv6_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return -ret;
} else {
kfree_skb(skb);
__UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox