From ac6d285d0cb40cc16e459d31c865d9e4162b5274 Mon Sep 17 00:00:00 2001 From: Yan Zhai Date: Mon, 25 Nov 2024 09:54:07 +0800 Subject: [PATCH 1/4] gso: fix dodgy bit handling for GSO_UDP_L4 mainline inclusion from mainline-v6.5-rc3 commit 9840036786d90cea11a90d1f30b6dc003b34ee67 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAKQ33 CVE: CVE-2024-43817 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9840036786d90cea11a90d1f30b6dc003b34ee67 -------------------------------- Commit 1fd54773c267 ("udp: allow header check for dodgy GSO_UDP_L4 packets.") checks DODGY bit for UDP, but for packets that can be fed directly to the device after gso_segs reset, it actually falls through to fragmentation: https://lore.kernel.org/all/CAJPywTKDdjtwkLVUW6LRA2FU912qcDmQOQGt2WaDo28KzYDg+A@mail.gmail.com/ This change restores the expected behavior of GSO_UDP_L4 packets. Fixes: 1fd54773c267 ("udp: allow header check for dodgy GSO_UDP_L4 packets.") Suggested-by: Willem de Bruijn Signed-off-by: Yan Zhai Reviewed-by: Willem de Bruijn Acked-by: Jason Wang Signed-off-by: David S. Miller Conflicts: net/ipv4/udp_offload.c net/ipv6/udp_offload.c [conflict with 5957b013e3c3 ("[Backport] gso: fix udp gso fraglist segmentation after pull from frag_list"), 1fd54773c267 ("udp: allow header check for dodgy GSO_UDP_L4 packets.") not merged] Signed-off-by: Zhang Changzhong --- net/ipv4/udp_offload.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index d6ab7afc0802..6120df4f9265 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -270,6 +270,17 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, __sum16 check; __be16 newlen; + mss = skb_shinfo(gso_skb)->gso_size; + if (gso_skb->len <= sizeof(*uh) + mss) + return ERR_PTR(-EINVAL); + + if (skb_gso_ok(gso_skb, features | NETIF_F_GSO_ROBUST)) { + /* Packet is from an untrusted source, reset gso_segs. */ + skb_shinfo(gso_skb)->gso_segs = DIV_ROUND_UP(gso_skb->len - sizeof(*uh), + mss); + return NULL; + } + if (skb_shinfo(gso_skb)->gso_type & SKB_GSO_FRAGLIST) { /* Detect modified geometry and pass those to skb_segment. */ if (skb_pagelen(gso_skb) - sizeof(*uh) == skb_shinfo(gso_skb)->gso_size) @@ -291,10 +302,6 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, ip_hdr(gso_skb)->daddr, 0); } - mss = skb_shinfo(gso_skb)->gso_size; - if (gso_skb->len <= sizeof(*uh) + mss) - return ERR_PTR(-EINVAL); - skb_pull(gso_skb, sizeof(*uh)); /* clear destructor to avoid skb_segment assigning it to tail */ -- Gitee From a50c1e59c0c492c8fa99fe99e57911c03aea668c Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 25 Nov 2024 09:54:08 +0800 Subject: [PATCH 2/4] net: drop bad gso csum_start and offset in virtio_net_hdr mainline inclusion from mainline-v6.11-rc2 commit 89add40066f9ed9abe5f7f886fe5789ff7e0c50e category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAKQ33 CVE: CVE-2024-43817 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=89add40066f9ed9abe5f7f886fe5789ff7e0c50e -------------------------------- Tighten csum_start and csum_offset checks in virtio_net_hdr_to_skb for GSO packets. The function already checks that a checksum requested with VIRTIO_NET_HDR_F_NEEDS_CSUM is in skb linear. But for GSO packets this might not hold for segs after segmentation. Syzkaller demonstrated to reach this warning in skb_checksum_help offset = skb_checksum_start_offset(skb); ret = -EINVAL; if (WARN_ON_ONCE(offset >= skb_headlen(skb))) By injecting a TSO packet: WARNING: CPU: 1 PID: 3539 at net/core/dev.c:3284 skb_checksum_help+0x3d0/0x5b0 ip_do_fragment+0x209/0x1b20 net/ipv4/ip_output.c:774 ip_finish_output_gso net/ipv4/ip_output.c:279 [inline] __ip_finish_output+0x2bd/0x4b0 net/ipv4/ip_output.c:301 iptunnel_xmit+0x50c/0x930 net/ipv4/ip_tunnel_core.c:82 ip_tunnel_xmit+0x2296/0x2c70 net/ipv4/ip_tunnel.c:813 __gre_xmit net/ipv4/ip_gre.c:469 [inline] ipgre_xmit+0x759/0xa60 net/ipv4/ip_gre.c:661 __netdev_start_xmit include/linux/netdevice.h:4850 [inline] netdev_start_xmit include/linux/netdevice.h:4864 [inline] xmit_one net/core/dev.c:3595 [inline] dev_hard_start_xmit+0x261/0x8c0 net/core/dev.c:3611 __dev_queue_xmit+0x1b97/0x3c90 net/core/dev.c:4261 packet_snd net/packet/af_packet.c:3073 [inline] The geometry of the bad input packet at tcp_gso_segment: [ 52.003050][ T8403] skb len=12202 headroom=244 headlen=12093 tailroom=0 [ 52.003050][ T8403] mac=(168,24) mac_len=24 net=(192,52) trans=244 [ 52.003050][ T8403] shinfo(txflags=0 nr_frags=1 gso(size=1552 type=3 segs=0)) [ 52.003050][ T8403] csum(0x60000c7 start=199 offset=1536 ip_summed=3 complete_sw=0 valid=0 level=0) Mitigate with stricter input validation. csum_offset: for GSO packets, deduce the correct value from gso_type. This is already done for USO. Extend it to TSO. Let UFO be: udp[46]_ufo_fragment ignores these fields and always computes the checksum in software. csum_start: finding the real offset requires parsing to the transport header. Do not add a parser, use existing segmentation parsing. Thanks to SKB_GSO_DODGY, that also catches bad packets that are hw offloaded. Again test both TSO and USO. Do not test UFO for the above reason, and do not test UDP tunnel offload. GSO packet are almost always CHECKSUM_PARTIAL. USO packets may be CHECKSUM_NONE since commit 10154dbded6d6 ("udp: Allow GSO transmit from devices with no checksum offload"), but then still these fields are initialized correctly in udp4_hwcsum/udp6_hwcsum_outgoing. So no need to test for ip_summed == CHECKSUM_PARTIAL first. This revises an existing fix mentioned in the Fixes tag, which broke small packets with GSO offload, as detected by kselftests. Link: https://syzkaller.appspot.com/bug?extid=e1db31216c789f552871 Link: https://lore.kernel.org/netdev/20240723223109.2196886-1-kuba@kernel.org Fixes: e269d79c7d35 ("net: missing check virtio") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20240729201108.1615114-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski Conflicts: include/linux/virtio_net.h [commit e269d79c7d35 ("net: missing check virtio") and fc8b2a619469 ("net: more strict VIRTIO_NET_HDR_GSO_UDP_L4 validation") not merged] Signed-off-by: Zhang Changzhong --- include/linux/virtio_net.h | 12 ++++++++++-- net/ipv4/tcp_offload.c | 3 +++ net/ipv4/udp_offload.c | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 6047058d6703..c72e781099b7 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -144,9 +144,17 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, unsigned int nh_off = p_off; struct skb_shared_info *shinfo = skb_shinfo(skb); - /* UFO may not include transport header in gso_size. */ - if (gso_type & SKB_GSO_UDP) + switch (gso_type & ~SKB_GSO_TCP_ECN) { + case SKB_GSO_UDP: + /* UFO may not include transport header in gso_size. */ nh_off -= thlen; + break; + case SKB_GSO_TCPV4: + case SKB_GSO_TCPV6: + if (skb->csum_offset != offsetof(struct tcphdr, check)) + return -EINVAL; + break; + } /* Kernel has a special handling for GSO_BY_FRAGS. */ if (gso_size == GSO_BY_FRAGS) diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c index fc61cd3fea65..357d3be04f84 100644 --- a/net/ipv4/tcp_offload.c +++ b/net/ipv4/tcp_offload.c @@ -71,6 +71,9 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb, if (thlen < sizeof(*th)) goto out; + if (unlikely(skb_checksum_start(skb) != skb_transport_header(skb))) + goto out; + if (!pskb_may_pull(skb, thlen)) goto out; diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 6120df4f9265..884453d75fb6 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -274,6 +274,10 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, if (gso_skb->len <= sizeof(*uh) + mss) return ERR_PTR(-EINVAL); + if (unlikely(skb_checksum_start(gso_skb) != + skb_transport_header(gso_skb))) + return ERR_PTR(-EINVAL); + if (skb_gso_ok(gso_skb, features | NETIF_F_GSO_ROBUST)) { /* Packet is from an untrusted source, reset gso_segs. */ skb_shinfo(gso_skb)->gso_segs = DIV_ROUND_UP(gso_skb->len - sizeof(*uh), -- Gitee From d923b977b60682cc0467a5116e0ab8657c10eeec Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 25 Nov 2024 09:54:09 +0800 Subject: [PATCH 3/4] udp: fix receiving fraglist GSO packets mainline inclusion from mainline-v6.11-rc5 commit b128ed5ab27330deeeaf51ea8bb69f1442a96f7f category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAKQ33 CVE: CVE-2024-43817 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b128ed5ab27330deeeaf51ea8bb69f1442a96f7f -------------------------------- When assembling fraglist GSO packets, udp4_gro_complete does not set skb->csum_start, which makes the extra validation in __udp_gso_segment fail. Fixes: 89add40066f9 ("net: drop bad gso csum_start and offset in virtio_net_hdr") Signed-off-by: Felix Fietkau Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20240819150621.59833-1-nbd@nbd.name Signed-off-by: Jakub Kicinski Conflicts: net/ipv4/udp_offload.c [commit 30b03f2a0592 ("udp: Fall back to software USO if IPv6 extension headers are present")] Signed-off-by: Zhang Changzhong --- net/ipv4/udp_offload.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 884453d75fb6..d6d45c5c47c6 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -275,7 +275,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, return ERR_PTR(-EINVAL); if (unlikely(skb_checksum_start(gso_skb) != - skb_transport_header(gso_skb))) + skb_transport_header(gso_skb) && + !(skb_shinfo(gso_skb)->gso_type & SKB_GSO_FRAGLIST))) return ERR_PTR(-EINVAL); if (skb_gso_ok(gso_skb, features | NETIF_F_GSO_ROBUST)) { -- Gitee From 906c902f06e77fbdeb7871a0419369901c945c86 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 25 Nov 2024 09:54:10 +0800 Subject: [PATCH 4/4] net: tighten bad gso csum offset check in virtio_net_hdr mainline inclusion from mainline-v6.11 commit 6513eb3d3191574b58859ef2d6dc26c0277c6f81 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAKQ33 CVE: CVE-2024-43817 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6513eb3d3191574b58859ef2d6dc26c0277c6f81 -------------------------------- The referenced commit drops bad input, but has false positives. Tighten the check to avoid these. The check detects illegal checksum offload requests, which produce csum_start/csum_off beyond end of packet after segmentation. But it is based on two incorrect assumptions: 1. virtio_net_hdr_to_skb with VIRTIO_NET_HDR_GSO_TCP[46] implies GSO. True in callers that inject into the tx path, such as tap. But false in callers that inject into rx, like virtio-net. Here, the flags indicate GRO, and CHECKSUM_UNNECESSARY or CHECKSUM_NONE without VIRTIO_NET_HDR_F_NEEDS_CSUM is normal. 2. TSO requires checksum offload, i.e., ip_summed == CHECKSUM_PARTIAL. False, as tcp[46]_gso_segment will fix up csum_start and offset for all other ip_summed by calling __tcp_v4_send_check. Because of 2, we can limit the scope of the fix to virtio_net_hdr that do try to set these fields, with a bogus value. Link: https://lore.kernel.org/netdev/20240909094527.GA3048202@port70.net/ Fixes: 89add40066f9 ("net: drop bad gso csum_start and offset in virtio_net_hdr") Signed-off-by: Willem de Bruijn Acked-by: Jason Wang Acked-by: Michael S. Tsirkin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20240910213553.839926-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Zhang Changzhong --- include/linux/virtio_net.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c72e781099b7..8bb04b4aaddb 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -151,7 +151,8 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, break; case SKB_GSO_TCPV4: case SKB_GSO_TCPV6: - if (skb->csum_offset != offsetof(struct tcphdr, check)) + if (skb->ip_summed == CHECKSUM_PARTIAL && + skb->csum_offset != offsetof(struct tcphdr, check)) return -EINVAL; break; } -- Gitee