[PATCH net] net/sched: act_csum: Fix missing headroom COW and integer underflow in header rewriting

From: Muhammad Bilal

Date: Tue Aug 18 2026 - 18:16:23 EST


tcf_csum_skb_nextlayer() and tcf_csum_ipv4() access and rewrite IP and L4
headers based on ntkoff = skb_network_offset(skb).

When the network header resides in the headroom (ntkoff < 0):
1. hl + ntkoff or sizeof(*iph) + ntkoff can evaluate to a negative value
or underflow when passed to functions expecting unsigned lengths, such
as pskb_may_pull() and skb_try_make_writable().
2. skb_try_make_writable() only evaluates writability from skb->data
forwards and does not invoke skb_cow() on the headroom. When modifying
cloned SKBs (e.g. from packet sockets, tc mirred, or BPF redirects),
updating headers via ip_send_check() or L4 checksum replacements mutates
shared headroom data directly, leading to packet corruption and page
cache corruption.

Fix this by introducing a helper csum_ensure_writable() that validates
headroom using skb_cow(skb, -offset) when offset is negative before
ensuring writability across the modified header length.

Fixes: eb4d40654505 ("net/sched: add ACT_CSUM action to update packets checksums")
Signed-off-by: Muhammad Bilal <meatuni001@xxxxxxxxx>
---
net/sched/act_csum.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/net/sched/act_csum.c b/net/sched/act_csum.c
index a8e2b83ebae5..cd2a6e974e6f 100644
--- a/net/sched/act_csum.c
+++ b/net/sched/act_csum.c
@@ -124,6 +124,18 @@ static int tcf_csum_init(struct net *net, struct nlattr *nla,
return err;
}

+static int csum_ensure_writable(struct sk_buff *skb, int offset, size_t len)
+{
+ if (offset < 0) {
+ if (skb_cow(skb, -offset))
+ return -ENOMEM;
+ if (offset + (int)len > 0)
+ return skb_ensure_writable(skb, offset + len);
+ return 0;
+ }
+ return skb_ensure_writable(skb, offset + len);
+}
+
/**
* tcf_csum_skb_nextlayer - Get next layer pointer
* @skb: sk_buff to use
@@ -139,8 +151,7 @@ static void *tcf_csum_skb_nextlayer(struct sk_buff *skb,
int ntkoff = skb_network_offset(skb);
int hl = ihl + jhl;

- if (!pskb_may_pull(skb, ipl + ntkoff) || (ipl < hl) ||
- skb_try_make_writable(skb, hl + ntkoff))
+ if (ipl < hl || csum_ensure_writable(skb, ntkoff, max_t(unsigned int, ipl, hl)))
return NULL;
else
return (void *)(skb_network_header(skb) + ihl);
@@ -437,8 +448,8 @@ static int tcf_csum_ipv4(struct sk_buff *skb, u32 update_flags)
}

if (update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) {
- if (skb_try_make_writable(skb, sizeof(*iph) + ntkoff))
+ if (csum_ensure_writable(skb, ntkoff, sizeof(*iph)))
goto fail;

ip_send_check(ip_hdr(skb));
}
--
2.43.0