Fix skb length accounting after XDP frag adjustment
Subsystems (4)
v2 cover Fix skb length accounting after XDP frag adjustment
Hi,
This series fixes skb length accounting after XDP fragment adjustment in generic XDP and veth.
v1:
https://lore.kernel.org/bpf/[email protected]/
Changes in v2:
- Move the veth fragment accounting before the linear tail adjustment, so __skb_put() observes a linear skb after a shrink removes all fragments.
- Fold the skb->len update into the xdp_buff_has_frags() branch, as suggested by Lorenzo Bianconi.
- Clarify why the old data_len contribution must be removed before adding the updated one, as requested by Maciej Fijalkowski.
Sun Jian (2):
net: fix skb length accounting after generic XDP frag adjustment
veth: fix skb length accounting after XDP frag adjustment
drivers/net/veth.c | 23 +++++++++++++++-------- net/core/dev.c | 10 +++++++--- 2 files changed, 22 insertions(+), 11 deletions(-)
Range-diff against v1:
1: b914fb40f348 ! 1: 2d716a1bd570 net: fix skb length accounting after generic XDP frag adjustment
@@ Commit message
Fixes: e6d5dbdd20aa ("xdp: add multi-buff support for xdp running in generic mode")
Cc: [email protected]
- Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/bpf/al9T9Eto%2FhRIzP5W@boxer/
Signed-off-by: Sun Jian <[email protected]>
@@ net/core/dev.c: u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buf
/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers
- * (e.g. bpf_xdp_adjust_tail), we need to update data_len here.
-+ * (e.g. bpf_xdp_adjust_tail), update skb length fields here.
++ * (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution
++ * from skb->len before updating data_len, then add the new one back.
*/
+- if (xdp_buff_has_frags(xdp))
+ skb->len -= skb->data_len;
- if (xdp_buff_has_frags(xdp))
++ if (xdp_buff_has_frags(xdp)) {
skb->data_len = skb_shinfo(skb)->xdp_frags_size;
- else
+- else
++ skb->len += skb->data_len;
++ } else {
skb->data_len = 0;
-+ skb->len += skb->data_len;
++ }
/* check if XDP changed eth hdr such SKB needs update */
eth = (struct ethhdr *)xdp->data;2: 59c79966c84a ! 2: e5b9383bc46e veth: fix skb length accounting after XDP frag adjustment
@@ Commit message
Subtract the old data_len before replacing it and add the new data_len afterwards, keeping skb->len and skb->data_len synchronized.
+ The fragment accounting must run before the linear tail adjustment:
+ when bpf_xdp_adjust_tail() shrinks the packet into the linear area it
+ releases all fragments, and __skb_put() requires skb->data_len == 0
+ by that point.
+
A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected
@@ Commit message
- Link: https://lore.kernel.org/r/[email protected]
## drivers/net/veth.c ##
@@ drivers/net/veth.c: static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
- __skb_put(skb, off); /* positive on grow, negative on shrink */
+ skb_reset_mac_header(skb);
+
+- /* check if bpf_xdp_adjust_tail was used */
+- off = xdp->data_end - orig_data_end;
+- if (off != 0)
+- __skb_put(skb, off); /* positive on grow, negative on shrink */
+-
/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers
- * (e.g. bpf_xdp_adjust_tail), we need to update data_len here.
-+ * (e.g. bpf_xdp_adjust_tail), update skb length fields here.
++ * (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution
++ * from skb->len before updating data_len, then add the new one back.
++ * This must precede the linear tail adjustment below: a changed
++ * data_end implies that no fragments remain, and __skb_put() requires
++ * a linear skb.
*/
+- if (xdp_buff_has_frags(xdp))
+ skb->len -= skb->data_len;
- if (xdp_buff_has_frags(xdp))
++ if (xdp_buff_has_frags(xdp)) {
skb->data_len = skb_shinfo(skb)->xdp_frags_size;
- else
+- else
++ skb->len += skb->data_len;
++ } else {
skb->data_len = 0;
-+ skb->len += skb->data_len;
++ }
++
++ /* check if bpf_xdp_adjust_tail was used */
++ off = xdp->data_end - orig_data_end;
++ if (off != 0)
++ __skb_put(skb, off); /* positive on grow, negative on shrink */
skb->protocol = eth_type_trans(skb, rq->dev);
base-commit: 97ac08560d236ca17f6606d9e671118e5eae5721
Signature · 1 line
2.43.0
PATCH v2 1/2 net: fix skb length accounting after generic XDP frag adjustment
Generic XDP exposes non-linear skb fragments through an xdp_buff. If an XDP program adjusts the fragment area, bpf_prog_run_generic_xdp() copies xdp_frags_size back to skb->data_len but leaves skb->len containing the old fragment contribution.
After a fragment shrink, this makes skb_headlen() larger than the actual linear area. In the reproduced UDP receive path, __skb_datagram_iter() copied 1024 bytes past the actual linear tail to userspace, starting at struct skb_shared_info. The copied bytes included the affected skb's nr_frags, xdp_frags_size and a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.
Subtract the old data_len before replacing it and add the new data_len afterwards, keeping skb->len and skb->data_len synchronized.
A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
--- net/core/dev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
net/core/dev.c +7 -3
@@ -5517,12 +5517,16 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, }/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + }/* check if XDP changed eth hdr such SKB needs update */ eth = (struct ethhdr *)xdp->data;
Signature · 1 line
2.43.0
Re: [PATCH net v2 1/2] net: fix skb length accounting after generic XDP frag adjustment
Quoted 43 lines · Sun Jian
Generic XDP exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,bpf_prog_run_generic_xdp()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Fixes e6d5dbdd20aa ("xdp: add multi-buff support for xdp running in generic mode")Signed-off-by Sun Jian <[email protected]>--- net/core/dev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)net/core/dev.c +3
-2@@ -5517,12 +5517,16 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, }/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len;
net/core/dev.c u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, · line 5532
Quoted 10 lines
net/core/dev.c +4
-1+if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; - else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + }/* check if XDP changed eth hdr such SKB needs update */ eth = (struct ethhdr *)xdp->data;
Re: [PATCH net v2 1/2] net: fix skb length accounting after generic XDP frag adjustment
In reply to Sun JianQuoted 21 lines
Generic XDP exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,bpf_prog_run_generic_xdp()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Quoted 4 lines
Fixes e6d5dbdd20aa ("xdp: add multi-buff support for xdp running in generic mode")Signed-off-by Sun Jian <[email protected]>
Quoted 31 lines
--- net/core/dev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)net/core/dev.c +7
-3@@ -5517,12 +5517,16 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, }/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + }/* check if XDP changed eth hdr such SKB needs update */ eth = (struct ethhdr *)xdp->data;Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH net v2 1/2] net: fix skb length accounting after generic XDP frag adjustment
In reply to Sun JianThank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The exact same metadata desynchronization bug that causes OOB reads in generic XDP also exists in drivers/net/veth.c, including an additional kernel panic vulnerability via BUG_ON.
- [Critical] bpf_xdp_frags_shrink_tail() incorrectly frees system_page_pool pages using page_frag_free() for Generic XDP.
- [High] Preexisting BUG_ON() panic in generic XDP when the head is pulled by more than the original linear length.
--
--- Patch [1]: [PATCH net v2 1/2] net: fix skb length accounting after generic XDP frag adjustment --- commit 98b02c083e22c12a3a47fab8bce5b83fb6fad2b7
Author: Sun Jian <[email protected]>
net: fix skb length accounting after generic XDP frag adjustment
This commit fixes an out-of-bounds read caused by generic XDP fragment adjustment. It subtracts the old data_len from skb->len before updating it to the new fragment size, keeping the lengths synchronized.
Quoted 5 lines
net/core/dev.c +0
-0
net/core/dev.c u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, · line 5523
Quoted 9 lines
net/core/dev.c +3
-2@@ -5517,12 +5517,16 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, }/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len;
[Severity: High]
This is a pre-existing issue, but does bpf_prog_run_generic_xdp() still panic if the head is pulled by more than the original linear length?
If an XDP program shrinks a packet's fragments to 0, it can subsequently grow the linear part and pull the head pointer (via bpf_xdp_adjust_head) by an amount greater than the original linear length.
In bpf_prog_run_generic_xdp(), the head adjustment is processed before the tail and data_len updates:
if (off > 0)
__skb_pull(skb, off);Since __skb_pull() asserts that skb->len >= skb->data_len and skb->data_len still holds its original value, wouldn't skb->len -= off fall below skb->data_len and trigger a kernel panic?
[Severity: Critical]
This is a pre-existing issue, but does veth_xdp_rcv_skb() in drivers/net/veth.c have the exact same metadata desynchronization bug?
If a fragmented packet is processed by a tail-adjusting XDP program in veth_xdp_rcv_skb(), skb->len is unmodified while skb->data_len changes:
/* check if bpf_xdp_adjust_tail was used */
off = xdp->data_end - orig_data_end;
if (off != 0)
__skb_put(skb, off); /* positive on grow, negative on shrink */If the packet is shrunk enough to remove all fragments and reduce the linear part, wouldn't calling __skb_put(skb, off) with a negative offset trigger a BUG_ON since skb->data_len is still non-zero at this point?
[Severity: Critical]
This is a pre-existing issue, but does bpf_xdp_shrink_data() incorrectly free system_page_pool pages using page_frag_free() for Generic XDP?
When generic XDP processes a non-linear SKB, fragments are allocated from system_page_pool. However, xdp_buff is initialized using the driver's rxqueue->xdp_rxq (often MEM_TYPE_PAGE_SHARED).
If an XDP program shrinks the packet tail, bpf_xdp_shrink_data() drops fragments and extracts the driver's mem_type:
enum xdp_mem_type mem_type = xdp->rxq->mem.type;
...
if (release) {
__xdp_return(netmem, mem_type, false, zc_frag);
}Because the memory type relies on the driver's default, wouldn't __xdp_return() incorrectly use page_frag_free() instead of returning the page to the page_pool?
case MEM_TYPE_PAGE_SHARED:
page_frag_free(__netmem_address(netmem));
break;Could this bypass the page_pool recycling logic and free a page_pool page directly to the buddy allocator, triggering a bad_page() BUG?
Quoted 10 lines
net/core/dev.c +4
-1+if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; - else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + }/* check if XDP changed eth hdr such SKB needs update */ eth = (struct ethhdr *)xdp->data;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v2 2/2 veth: fix skb length accounting after XDP frag adjustment
In reply to Sun Jianveth exposes non-linear skb fragments through an xdp_buff. If an XDP program adjusts the fragment area, veth_xdp_rcv_skb() copies xdp_frags_size back to skb->data_len but leaves skb->len containing the old fragment contribution.
After a fragment shrink, this makes skb_headlen() larger than the actual linear area. In the reproduced UDP receive path, __skb_datagram_iter() copied 1024 bytes past the actual linear tail to userspace, starting at struct skb_shared_info. The copied bytes included the affected skb's nr_frags, xdp_frags_size and a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.
Subtract the old data_len before replacing it and add the new data_len afterwards, keeping skb->len and skb->data_len synchronized.
The fragment accounting must run before the linear tail adjustment:
when bpf_xdp_adjust_tail() shrinks the packet into the linear area it releases all fragments, and __skb_put() requires skb->data_len == 0 by that point.
A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)
drivers/net/veth.c +15 -8
@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */ skb->protocol =eth_type_trans (skb, rq->dev);
Signature · 1 line
2.43.0
Re: [PATCH net v2 2/2] veth: fix skb length accounting after XDP frag adjustment
Quoted 26 lines
veth exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,veth_xdp_rcv_skb()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.The fragment accounting must run before the linear tail adjustment:
whenbpf_xdp_adjust_tail()shrinks the packet into the linear area it releases all fragments, and__skb_put()requiresskb->data_len== 0 by that point.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Quoted 4 lines
Fixes 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")Signed-off-by Sun Jian <[email protected]>
Quoted 45 lines
--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)drivers/net/veth.c +15
-8@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */ skb->protocol =eth_type_trans (skb, rq->dev);Signature · 1 line
2.43.0
Re: [PATCH net v2 2/2] veth: fix skb length accounting after XDP frag adjustment
In reply to Sun JianQuoted 62 lines · Sun Jian
veth exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,veth_xdp_rcv_skb()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.The fragment accounting must run before the linear tail adjustment:
whenbpf_xdp_adjust_tail()shrinks the packet into the linear area it releases all fragments, and__skb_put()requiresskb->data_len== 0 by that point.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Fixes 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")Signed-off-by Sun Jian <[email protected]>--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)drivers/net/veth.c +9
-8@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else {
drivers/net/veth.c static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq, · line 889
Quoted 10 lines
drivers/net/veth.c +6
-0skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */ skb->protocol =eth_type_trans (skb, rq->dev);
I am most likely missing something here but what happens if we have frags and we attempt to advance data_end while leaving some frags present (e.g., bpf_xdp_pull_data())? Looks like, in that case we would issue __skb_put(skb, off) with off > 0 and we would hit SKB_LINEAR_ASSERT() because skb is still non-linear?
Re: [PATCH net v2 2/2] veth: fix skb length accounting after XDP frag adjustment
Quoted 74 lines · Mohsin Bashir
Quoted 70 lines · Sun Jian
veth exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,veth_xdp_rcv_skb()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.The fragment accounting must run before the linear tail adjustment:
whenbpf_xdp_adjust_tail()shrinks the packet into the linear area it releases all fragments, and__skb_put()requiresskb->data_len== 0 by that point.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Fixes 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")Signed-off-by Sun Jian <[email protected]>--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)drivers/net/veth.c +15
-8@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */
Quoted 8 lines
drivers/net/veth.c static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq, · line 889
Quoted 2 lines
drivers/net/veth.c +0
-0skb->protocol =eth_type_trans (skb, rq->dev);I am most likely missing something here but what happens if we have frags and we attempt to advance
data_endwhile leaving some frags present (e.g.,bpf_xdp_pull_data())? Looks like, in that case we would issue__skb_put(skb, off)with off > 0 and we would hitSKB_LINEAR_ASSERT()because skb is still non-linear?
Hi Mohsin,
Thanks for catching this. You're right that bpf_xdp_pull_data() can advance data_end while leaving frags present. I'll investigate this path and follow up.
Thanks,
Sun Jian
Re: [PATCH net v2 2/2] veth: fix skb length accounting after XDP frag adjustment
Quoted 84 lines · Mohsin Bashir
Quoted 74 lines · Sun Jian
Quoted 70 lines
veth exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,veth_xdp_rcv_skb()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.The fragment accounting must run before the linear tail adjustment:
whenbpf_xdp_adjust_tail()shrinks the packet into the linear area it releases all fragments, and__skb_put()requiresskb->data_len== 0 by that point.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Fixes 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")Signed-off-by Sun Jian <[email protected]>--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)drivers/net/veth.c +15
-8@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else { skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */ Quoted 8 lines
drivers/net/veth.c static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq, · line 889
Quoted 2 lines
drivers/net/veth.c +0
-0skb->protocol =eth_type_trans (skb, rq->dev);I am most likely missing something here but what happens if we have frags and we attempt to advance
data_endwhile leaving some frags present (e.g.,bpf_xdp_pull_data())? Looks like, in that case we would issue__skb_put(skb, off)with off > 0 and we would hitSKB_LINEAR_ASSERT()because skb is still non-linear?
Quoted 7 lines
Hi Mohsin,
Thanks for catching this. You're right that
bpf_xdp_pull_data()can advancedata_endwhile leaving frags present. I'll investigate this path and follow up.Thanks,
Sun Jian
I guess we should implement the same approach used in bpf_prog_run_generic_xdp(), something like:
if (off) {
skb_set_tail_pointer();
skb->len += off;
}Regards,
Lorenzo
Re: [PATCH net v2 2/2] veth: fix skb length accounting after XDP frag adjustment
Quoted 85 lines · Lorenzo Bianconi
Quoted 85 lines · Mohsin Bashir
Quoted 82 lines · Sun Jian
Quoted 62 lines
veth exposes non-linear skb fragments through an
xdp_buff. If an XDP program adjusts the fragment area,veth_xdp_rcv_skb()copiesxdp_frags_sizeback toskb->data_lenbut leavesskb->lencontaining the old fragment contribution.After a fragment shrink, this makes
skb_headlen()larger than the actual linear area. In the reproduced UDP receive path,__skb_datagram_iter()copied 1024 bytes past the actual linear tail to userspace, starting at structskb_shared_info. The copied bytes included the affected skb'snr_frags,xdp_frags_sizeand a kernel pointer from skb_shinfo(skb)->frags[0]. Real packet data was displaced by the same amount and truncated at the end.Subtract the old
data_lenbefore replacing it and add the newdata_lenafterwards, keepingskb->lenandskb->data_lensynchronized.The fragment accounting must run before the linear tail adjustment:
whenbpf_xdp_adjust_tail()shrinks the packet into the linear area it releases all fragments, and__skb_put()requiresskb->data_len== 0 by that point.A 60000-byte UDP datagram on a veth pair with MTU 64000 was shortened by 1024 bytes from its fragment area. Before the fix, all 10 runs produced corrupted payloads. After the fix, all 10 runs matched the expected payload exactly.
Fixes 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")Signed-off-by Sun Jian <[email protected]>--- drivers/net/veth.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-)drivers/net/veth.c +9
-8@@ -865,18 +865,25 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,skb_reset_mac_header (skb); -/* check if bpf_xdp_adjust_tail was used */ - off = xdp->data_end - orig_data_end; -if (off !=0 ) -__skb_put (skb, off);/* positive on grow, negative on shrink */ -/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers -* (e.g. bpf_xdp_adjust_tail), we need to update data_len here. +* (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution +* from skb->len before updating data_len, then add the new one back. +* This must precede the linear tail adjustment below: a changed +* data_end implies that no fragments remain, and __skb_put() requires +* a linear skb. */ -if (xdp_buff_has_frags (xdp)) + skb->len -= skb->data_len; +if (xdp_buff_has_frags (xdp)) { skb->data_len =skb_shinfo (skb)->xdp_frags_size; -else + skb->len += skb->data_len; + }else {drivers/net/veth.c static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq, · line 889
Quoted 10 lines
drivers/net/veth.c +6
-0skb->data_len =0 ; + } + +/* check if bpf_xdp_adjust_tail was used */ + off = xdp->data_end - orig_data_end; +if (off !=0 ) +__skb_put (skb, off);/* positive on grow, negative on shrink */ skb->protocol =eth_type_trans (skb, rq->dev);I am most likely missing something here but what happens if we have frags and we attempt to advance
data_endwhile leaving some frags present (e.g.,bpf_xdp_pull_data())? Looks like, in that case we would issue__skb_put(skb, off)with off > 0 and we would hitSKB_LINEAR_ASSERT()because skb is still non-linear?
Quoted 8 lines
Quoted 7 lines
Hi Mohsin,
Thanks for catching this. You're right that
bpf_xdp_pull_data()can advancedata_endwhile leaving frags present. I'll investigate this path and follow up.Thanks,
Sun Jian
Hi Lorenzo,
Quoted 7 lines
I guess we should implement the same approach used in
bpf_prog_run_generic_xdp(), something like:if (off) { skb_set_tail_pointer(); skb->len += off; }
Yes, that makes sense. I confirmed Mohsin's case: bpf_xdp_pull_data() can advance data_end while keeping frags, so the current v2 can hit SKB_LINEAR_ASSERT() through __skb_put() on a non-linear skb.
I tested the skb_set_tail_pointer() + skb->len update approach locally with a forced-tailroom reproducer, and it fixes that path. I will fold this into v3.
Thanks,
Sun Jian
Quoted 3 lines
Regards,
Lorenzo