114 lines
4.0 KiB
Diff
114 lines
4.0 KiB
Diff
From 485c82a86fb97fb86cac303348c85b6cf71fd787 Mon Sep 17 00:00:00 2001
|
|
From: Jakub Kicinski <kuba@kernel.org>
|
|
Date: Mon, 9 Jun 2025 17:12:44 -0700
|
|
Subject: net: clear the dst when changing skb protocol
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
A not-so-careful NAT46 BPF program can crash the kernel
|
|
if it indiscriminately flips ingress packets from v4 to v6:
|
|
|
|
BUG: kernel NULL pointer dereference, address: 0000000000000000
|
|
ip6_rcv_core (net/ipv6/ip6_input.c:190:20)
|
|
ipv6_rcv (net/ipv6/ip6_input.c:306:8)
|
|
process_backlog (net/core/dev.c:6186:4)
|
|
napi_poll (net/core/dev.c:6906:9)
|
|
net_rx_action (net/core/dev.c:7028:13)
|
|
do_softirq (kernel/softirq.c:462:3)
|
|
netif_rx (net/core/dev.c:5326:3)
|
|
dev_loopback_xmit (net/core/dev.c:4015:2)
|
|
ip_mc_finish_output (net/ipv4/ip_output.c:363:8)
|
|
NF_HOOK (./include/linux/netfilter.h:314:9)
|
|
ip_mc_output (net/ipv4/ip_output.c:400:5)
|
|
dst_output (./include/net/dst.h:459:9)
|
|
ip_local_out (net/ipv4/ip_output.c:130:9)
|
|
ip_send_skb (net/ipv4/ip_output.c:1496:8)
|
|
udp_send_skb (net/ipv4/udp.c:1040:8)
|
|
udp_sendmsg (net/ipv4/udp.c:1328:10)
|
|
|
|
The output interface has a 4->6 program attached at ingress.
|
|
We try to loop the multicast skb back to the sending socket.
|
|
Ingress BPF runs as part of netif_rx(), pushes a valid v6 hdr
|
|
and changes skb->protocol to v6. We enter ip6_rcv_core which
|
|
tries to use skb_dst(). But the dst is still an IPv4 one left
|
|
after IPv4 mcast output.
|
|
|
|
Clear the dst in all BPF helpers which change the protocol.
|
|
Try to preserve metadata dsts, those may carry non-routing
|
|
metadata.
|
|
|
|
Cc: stable@vger.kernel.org
|
|
Reviewed-by: Maciej Żenczykowski <maze@google.com>
|
|
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
|
|
Fixes: d219df60a70e ("bpf: Add ipip6 and ip6ip decap support for bpf_skb_adjust_room()")
|
|
Fixes: 1b00e0dfe7d0 ("bpf: update skb->protocol in bpf_skb_net_grow")
|
|
Fixes: 6578171a7ff0 ("bpf: add bpf_skb_change_proto helper")
|
|
Reviewed-by: Willem de Bruijn <willemb@google.com>
|
|
Link: https://patch.msgid.link/20250610001245.1981782-1-kuba@kernel.org
|
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
|
---
|
|
net/core/filter.c | 19 +++++++++++++------
|
|
1 file changed, 13 insertions(+), 6 deletions(-)
|
|
|
|
--- a/net/core/filter.c
|
|
+++ b/net/core/filter.c
|
|
@@ -3233,6 +3233,13 @@ static const struct bpf_func_proto bpf_s
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
};
|
|
|
|
+static void bpf_skb_change_protocol(struct sk_buff *skb, u16 proto)
|
|
+{
|
|
+ skb->protocol = htons(proto);
|
|
+ if (skb_valid_dst(skb))
|
|
+ skb_dst_drop(skb);
|
|
+}
|
|
+
|
|
static int bpf_skb_generic_push(struct sk_buff *skb, u32 off, u32 len)
|
|
{
|
|
/* Caller already did skb_cow() with len as headroom,
|
|
@@ -3329,7 +3336,7 @@ static int bpf_skb_proto_4_to_6(struct s
|
|
}
|
|
}
|
|
|
|
- skb->protocol = htons(ETH_P_IPV6);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IPV6);
|
|
skb_clear_hash(skb);
|
|
|
|
return 0;
|
|
@@ -3359,7 +3366,7 @@ static int bpf_skb_proto_6_to_4(struct s
|
|
}
|
|
}
|
|
|
|
- skb->protocol = htons(ETH_P_IP);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IP);
|
|
skb_clear_hash(skb);
|
|
|
|
return 0;
|
|
@@ -3550,10 +3557,10 @@ static int bpf_skb_net_grow(struct sk_bu
|
|
/* Match skb->protocol to new outer l3 protocol */
|
|
if (skb->protocol == htons(ETH_P_IP) &&
|
|
flags & BPF_F_ADJ_ROOM_ENCAP_L3_IPV6)
|
|
- skb->protocol = htons(ETH_P_IPV6);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IPV6);
|
|
else if (skb->protocol == htons(ETH_P_IPV6) &&
|
|
flags & BPF_F_ADJ_ROOM_ENCAP_L3_IPV4)
|
|
- skb->protocol = htons(ETH_P_IP);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IP);
|
|
}
|
|
|
|
if (skb_is_gso(skb)) {
|
|
@@ -3606,10 +3613,10 @@ static int bpf_skb_net_shrink(struct sk_
|
|
/* Match skb->protocol to new outer l3 protocol */
|
|
if (skb->protocol == htons(ETH_P_IP) &&
|
|
flags & BPF_F_ADJ_ROOM_DECAP_L3_IPV6)
|
|
- skb->protocol = htons(ETH_P_IPV6);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IPV6);
|
|
else if (skb->protocol == htons(ETH_P_IPV6) &&
|
|
flags & BPF_F_ADJ_ROOM_DECAP_L3_IPV4)
|
|
- skb->protocol = htons(ETH_P_IP);
|
|
+ bpf_skb_change_protocol(skb, ETH_P_IP);
|
|
|
|
if (skb_is_gso(skb)) {
|
|
struct skb_shared_info *shinfo = skb_shinfo(skb);
|