From: "Michael S. Tsirkin" <[email protected]> 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: [email protected] Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo <[email protected]> Closes: https://lore.kernel.org/netdev/[email protected]/ Signed-off-by: Michael S. Tsirkin <[email protected]> Reviewed-by: Willem de Bruijn <[email protected]> Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git....@redhat.com Signed-off-by: Jakub Kicinski <[email protected]> (cherry picked from commit 6785011f8b16abf40b1e9d8b2e332232b3e68822) Backport notes: - drop the net/bridge/netfilter/nf_conntrack_bridge.c hunk; - add nf_queue_checksum_help() to nfnetlink_queue_core.c with the IPv4 check only, and call it from nfqnl_build_packet_message(). nf_conntrack_bridge.c came with ms commit 3c171f496ef5 ("netfilter: bridge: add connection tracking system") in v5.3 and does not exist here. nf_queue_checksum_help() came with ms commit 26a77d02891a ("netfilter: nfnetlink_queue: unbreak SCTP traffic"), so here nfqnl_build_packet_message() still calls skb_checksum_help() directly. The helper is added without the SCTP branch of that commit. The four guarded sites are all skb_checksum_help() callers under net/ipv4, net/netfilter and net/xfrm in this tree. RHEL7 kernel-3.10.0-1160.169.1.el7 carries the same change. CVE-2026-99999 https://virtuozzo.atlassian.net/browse/VSTOR-154184 Signed-off-by: Konstantin Khorenko <[email protected]> --- CVE-2026-99999 (PwnEscape) fix for the vz7 kernel. https://virtuozzo.atlassian.net/browse/VSTOR-154184 Assisted-by: LLM --- include/net/ip.h | 7 +++++++ net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue_core.c | 13 ++++++++++++- net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index b321d2a5fc3df..138e1c2152a64 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 f7195e5f72e5d..7d66717027b4a 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -539,9 +539,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 ff2d4b31b353e..591c3f3b9bc21 100644 --- a/net/netfilter/nfnetlink_queue_core.c +++ b/net/netfilter/nfnetlink_queue_core.c @@ -29,6 +29,7 @@ #include <linux/netfilter/nfnetlink.h> #include <linux/netfilter/nfnetlink_queue.h> #include <linux/list.h> +#include <net/ip.h> #include <net/sock.h> #include <net/netfilter/nf_queue.h> #include <net/netns/generic.h> @@ -245,6 +246,16 @@ 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 (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + return skb_checksum_help(entskb); +} + static struct sk_buff * nfqnl_build_packet_message(struct nfqnl_instance *queue, struct nf_queue_entry *entry, @@ -297,7 +308,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); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index db286fc0bc32a..1d4efe9c59fcb 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -18,6 +18,7 @@ #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter_ipv6/ip6_tables.h> +#include <net/ip.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin <[email protected]>"); @@ -28,8 +29,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 && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + 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) --- base-commit: 78c10c447efc7633a8e4260fe7dc33e1cb4bb4d2 change-id: 20261109-b4-prep-khorenko-vstor-154184-ipv4-checksum_start-6f6b55915d5c Best regards, -- Konstantin Khorenko, Virtuozzo Linux Kernel Team _______________________________________________ Devel mailing list [email protected] https://lists.openvz.org/mailman/listinfo/devel
