From 6516ea705dd37afa03f9e550e820310889416502 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 19 Oct 2026 11:07:10 -0400 Subject: [PATCH 1/2] netfilter: nfnetlink_queue: unbreak SCTP traffic jira VULN-221091 cve-pre CVE-2026-99999 commit-author Antonio Ojea commit 26a77d02891ab62172085a4f94af9b3c90aed387 upstream-diff | context diff: this kernel predates the nfnetlink_queue refactor; the file is net/netfilter/nfnetlink_queue_core.c here. The commit depends on two inline helpers added to include/linux/skbuff.h by later upstream commits (skb_is_gso_sctp in d02f51cbcf12b0, skb_csum_is_sctp in fa82117010430); both are folded into this backport at upstream-adjacent placements, as the remaining hunks of those commits (BPF filter.c proto-xlat, documentation, ionic driver) do not apply to this kernel. skb_crc32c_csum_help() already exists in net/core/dev.c in this kernel (unexported); only its EXPORT_SYMBOL is added. In nfnetlink_queue_core.c the helper insertion conflicted against functions absent in this kernel (nfqnl_put_sk_uidgid &c.) and is placed directly above nfqnl_build_packet_message; the COPY_PACKET and enqueue-condition hunks apply at their original call sites. when packet is enqueued with nfqueue and GSO is enabled, checksum calculation has to take into account the protocol, as SCTP uses a 32 bits CRC checksum. Enter skb_gso_segment() path in case of SCTP GSO packets because skb_zerocopy() does not support for GSO_BY_FRAGS. Joint work with Pablo. Signed-off-by: Antonio Ojea Signed-off-by: Pablo Neira Ayuso (cherry picked from commit 26a77d02891ab62172085a4f94af9b3c90aed387) Signed-off-by: Jonathan Maple Signed-off-by: Brett Mastbergen --- include/linux/skbuff.h | 11 +++++++++++ net/core/dev.c | 1 + net/netfilter/nfnetlink_queue_core.c | 12 ++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 5a20c7f7b0431..e2d68c9ce29c6 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -3906,6 +3906,12 @@ static inline bool skb_is_gso_v6(const struct sk_buff *skb) return skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6; } +/* Note: Should be called only if skb_is_gso(skb) is true */ +static inline bool skb_is_gso_sctp(const struct sk_buff *skb) +{ + return skb_shinfo(skb)->gso_type & SKB_GSO_SCTP; +} + void __skb_warn_lro_forwarding(const struct sk_buff *skb); static inline bool skb_warn_if_lro(const struct sk_buff *skb) @@ -4023,5 +4029,10 @@ static inline __wsum lco_csum(struct sk_buff *skb) return csum_partial(l4_hdr, csum_start - l4_hdr, partial); } +static inline bool skb_csum_is_sctp(struct sk_buff *skb) +{ + return skb->csum_not_inet; +} + #endif /* __KERNEL__ */ #endif /* _LINUX_SKBUFF_H */ diff --git a/net/core/dev.c b/net/core/dev.c index b41fdcde32df5..5c351ebe25b42 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2825,6 +2825,7 @@ int skb_crc32c_csum_help(struct sk_buff *skb) out: return ret; } +EXPORT_SYMBOL(skb_crc32c_csum_help); __be16 skb_network_protocol(struct sk_buff *skb, int *depth) { diff --git a/net/netfilter/nfnetlink_queue_core.c b/net/netfilter/nfnetlink_queue_core.c index ff2d4b31b353e..2f042babd9c09 100644 --- a/net/netfilter/nfnetlink_queue_core.c +++ b/net/netfilter/nfnetlink_queue_core.c @@ -245,6 +245,14 @@ nfqnl_put_packet_info(struct sk_buff *nlskb, struct sk_buff *packet, return flags ? nla_put_be32(nlskb, NFQA_SKB_INFO, htonl(flags)) : 0; } +static int nf_queue_checksum_help(struct sk_buff *entskb) +{ + if (skb_csum_is_sctp(entskb)) + return skb_crc32c_csum_help(entskb); + + return skb_checksum_help(entskb); +} + static struct sk_buff * nfqnl_build_packet_message(struct nfqnl_instance *queue, struct nf_queue_entry *entry, @@ -297,7 +305,7 @@ nfqnl_build_packet_message(struct nfqnl_instance *queue, case NFQNL_COPY_PACKET: if (!(queue->flags & NFQA_CFG_F_GSO) && entskb->ip_summed == CHECKSUM_PARTIAL && - skb_checksum_help(entskb)) + nf_queue_checksum_help(entskb)) return NULL; data_len = ACCESS_ONCE(queue->copy_range); @@ -614,7 +622,7 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum) break; } - if ((queue->flags & NFQA_CFG_F_GSO) || !skb_is_gso(skb)) + if (!skb_is_gso(skb) || ((queue->flags & NFQA_CFG_F_GSO) && !skb_is_gso_sctp(skb))) return __nfqnl_enqueue_packet(net, queue, entry); nf_bridge_adjust_skb_data(skb); From 9d39c2c26cfe310eca5d615bfe5a9f1725d087b7 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 19 Oct 2026 11:08:16 -0400 Subject: [PATCH 2/2] ipv4: validate checksum_start before completing checksum jira VULN-221091 cve CVE-2026-99999 commit-author Michael S. Tsirkin commit 6785011f8b16abf40b1e9d8b2e332232b3e68822 commit-source-sha a464d950f31c89565cbaae038cc60db04fe01956 commit-source linux-5.10.y upstream-diff | context diff: net/bridge/netfilter/nf_conntrack_bridge.c does not exist in this kernel (added in 4.19) and is dropped; bridge fragmentation on this tree goes via net/bridge/br_netfilter_hooks.c -> ip_do_fragment() which is already covered by the central ip_do_fragment fix above, so no standalone bridge helper needs patching. nfnetlink_queue is nfnetlink_queue_core.c here; nf_queue_checksum_help() was added by the cve-pre 26a77d02891ab backport earlier in this branch, so the ip validation hunk lands at the top of that function exactly as upstream. xt_CHECKSUM keeps this kernel's existing condition (the skb_is_gso skip upstream gained is a separate commit, not part of this fix) and adds only the NFPROTO_IPV4 guard. xfrm_output keeps this kernel's 2-arg xfrm_output2(sk, skb) signature; the error path is a new error: label without upstream's out: label, and upstream's trailing xfrm4_tunnel_check_size context does not exist in this kernel. commit 6785011f8b16abf40b1e9d8b2e332232b3e68822 upstream. If a packet with bad checksum metadata gets into the ipv4 stack, skb_checksum_help can corrupt the network header and cause a bunch of mischief. This was discovered and reported by Paulos, and has been reporoduced by others independently since. We really shouldn't allow such packets in, but as a defence in depth measure, let's also check before we complete the checksum. A more complete validation at input is forthcoming, but needs more work. Cc: stable@vger.kernel.org Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo Closes: https://lore.kernel.org/netdev/20260922030310.8684-2-habte.yibelo@gmail.com/ Signed-off-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git.mst@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman (cherry picked from commit a464d950f31c89565cbaae038cc60db04fe01956) Signed-off-by: Jonathan Maple Signed-off-by: Brett Mastbergen --- include/net/ip.h | 7 +++++++ net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue_core.c | 7 +++++++ net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 9e6e1752a8715..02ee7aacc79f1 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -57,6 +57,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { __be32 addr; int oif; diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 48e1040b15dc9..bd87c1a0768c8 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -538,9 +538,13 @@ int ip_do_fragment(struct sock *sk, struct sk_buff *skb, dev = rt->dst.dev; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue_core.c b/net/netfilter/nfnetlink_queue_core.c index 2f042babd9c09..37a6be7bc213b 100644 --- a/net/netfilter/nfnetlink_queue_core.c +++ b/net/netfilter/nfnetlink_queue_core.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -247,6 +248,12 @@ nfqnl_put_packet_info(struct sk_buff *nlskb, struct sk_buff *packet, static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 0f642ef8cd266..8a1e2a1528618 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL) + if (skb->ip_summed == CHECKSUM_PARTIAL) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index 65a0e1a9ac985..a1d6c922f43e0 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -197,15 +197,21 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) return xfrm_output_gso(sk, skb); if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } return xfrm_output2(sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb)