bpf_sk_assign() permits TC ingress programs to associate an IPv6 packet with an AF_INET socket. The receive path can then interpret IPv6 skb control data as IPv4 metadata. When IP_RETOPTS is enabled, this can cause __ip_options_echo() to copy beyond its stack buffer. Reject incompatible packet and socket families in bpf_sk_assign() and bpf_sk_assign_tcp_reqsk(). Continue to allow IPv4 packets to use dual-stack AF_INET6 sockets. Check request sockets against rsk_ops->family, since their sk_family is inherited from the listener and sk_ipv6only is not initialized. Fixes: cf7fbe660f2d ("bpf: Add socket assign support") Assisted-by: LLM Signed-off-by: Shihuang Liu --- Changes since v1: - Move family validation out of the IPv6 receive fast path and into bpf_sk_assign() and bpf_sk_assign_tcp_reqsk(). - Preserve IPv4 assignments to dual-stack AF_INET6 sockets. - Check request sockets using rsk_ops->family. - Split the fix into two patches and target the BPF fixes tree. v1: https://lore.kernel.org/netdev/20260823101809.26802-1-shlomojune6@gmail.com/ include/uapi/linux/bpf.h | 4 ++++ net/core/filter.c | 31 +++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 4 ++++ 3 files changed, 39 insertions(+) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 732b35cc08d1c..5d8f5e2c8db38 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -4568,6 +4568,10 @@ union bpf_attr { * **-EOPNOTSUPP** if the operation is not supported, for example * a call from outside of TC ingress. * + * **-EAFNOSUPPORT** if the socket family is not compatible with + * the network layer of the packet, for example an **AF_INET** + * socket and an IPv6 packet. + * * long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, u64 flags) * Description * Helper is overloaded depending on BPF program type. This diff --git a/net/core/filter.c b/net/core/filter.c index 61940e7535523..e9cc76b775c0c 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3491,6 +3491,32 @@ static int bpf_skb_proto_xlat(struct sk_buff *skb, __be16 to_proto) return -ENOTSUPP; } +static bool bpf_sk_assign_family_ok(const struct sk_buff *skb, + const struct sock *sk) +{ + unsigned short family; + + switch (skb->protocol) { + case htons(ETH_P_IP): + family = AF_INET; + break; + case htons(ETH_P_IPV6): + family = AF_INET6; + break; + default: + return true; + } + + /* Requests inherit the listener family, but have family-specific ops. */ + if (sk->sk_state == TCP_NEW_SYN_RECV) + return inet_reqsk(sk)->rsk_ops->family == family; + + return sk->sk_family == family || + (family == AF_INET && + sk->sk_family == AF_INET6 && + !ipv6_only_sock(sk)); +} + BPF_CALL_3(bpf_skb_change_proto, struct sk_buff *, skb, __be16, proto, u64, flags) { @@ -7989,6 +8015,8 @@ BPF_CALL_3(bpf_sk_assign, struct sk_buff *, skb, struct sock *, sk, u64, flags) return -ENETUNREACH; if (sk_unhashed(sk)) return -EOPNOTSUPP; + if (!bpf_sk_assign_family_ok(skb, sk)) + return -EAFNOSUPPORT; if (sk_is_refcounted(sk) && unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) return -ENOENT; @@ -12526,6 +12554,9 @@ __bpf_kfunc int bpf_sk_assign_tcp_reqsk(struct __sk_buff *s, struct sock *sk, if (net != sock_net(sk)) return -ENETUNREACH; + if (!bpf_sk_assign_family_ok(skb, sk)) + return -EAFNOSUPPORT; + switch (skb->protocol) { case htons(ETH_P_IP): ops = &tcp_request_sock_ops; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 732b35cc08d1c..5d8f5e2c8db38 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -4568,6 +4568,10 @@ union bpf_attr { * **-EOPNOTSUPP** if the operation is not supported, for example * a call from outside of TC ingress. * + * **-EAFNOSUPPORT** if the socket family is not compatible with + * the network layer of the packet, for example an **AF_INET** + * socket and an IPv6 packet. + * * long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, u64 flags) * Description * Helper is overloaded depending on BPF program type. This -- 2.43.0