[PATCH net v4 2/2] ipv4: reject partial checksums covering the IP header
From: Paulos Yibelo
Date: Sat Sep 19 2026 - 20:49:11 EST
ip_do_fragment() completes a CHECKSUM_PARTIAL skb before reading the IPv4
header length. A virtualization interface can supply a checksum start that
still points inside the IPv4 header after link-layer removal.
This does not require a virtual-machine guest. A TUN device with
virtio-net header support is sufficient to reach this path.
skb_checksum_help() can then change iph->ihl after the packet was parsed
and routed. Fragmentation trusts the changed IHL and can copy beyond the
skb's logical linear head into transmitted IPv4 options.
Read and validate IHL before checksum completion, reject a checksum start
inside that header, retain the validated length, and reacquire iph after
skb_checksum_help().
Fixes: dbd3393c56a8 ("ipv4: add defensive check for CHECKSUM_PARTIAL skbs in ip_fragment")
Reported-by: Paulos Yibelo <habte.yibelo@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Paulos Yibelo <habte.yibelo@xxxxxxxxx>
Acked-by: Michael S. Tsirkin <mst@xxxxxxxxxx>
---
Changes in v4:
- State explicitly that a TUN device is sufficient and no guest is required,
as noted by Michael S. Tsirkin. No code changes.
Changes in v3:
- No code changes.
Changes in v2:
- No code changes.
net/ipv4/ip_output.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index a24cc8e..ff902a2 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -770,17 +770,29 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
struct ip_frag_state state;
int err = 0;
- /* for offloaded checksums cleanup checksum before fragmentation */
- if (skb->ip_summed == CHECKSUM_PARTIAL &&
- (err = skb_checksum_help(skb)))
- goto fail;
-
/*
* Point into the IP datagram header.
*/
iph = ip_hdr(skb);
+ hlen = iph->ihl * 4;
+ if (unlikely(hlen < sizeof(*iph) || hlen > skb_headlen(skb))) {
+ err = -EINVAL;
+ goto fail;
+ }
+ /* Complete offloaded checksums only after the validated IP header. */
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ if (unlikely(skb_checksum_start_offset(skb) < hlen)) {
+ err = -EINVAL;
+ goto fail;
+ }
+ err = skb_checksum_help(skb);
+ if (err)
+ goto fail;
+ iph = ip_hdr(skb);
+ }
+
mtu = ip_skb_dst_mtu(sk, skb);
if (IPCB(skb)->frag_max_size && IPCB(skb)->frag_max_size < mtu)
mtu = IPCB(skb)->frag_max_size;
@@ -789,7 +801,6 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
* Setup starting values.
*/
- hlen = iph->ihl * 4;
if (mtu < hlen + 8) {
err = -EMSGSIZE;
goto fail;