]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ila: reload IPv6 header after pskb_may_pull in checksum adjust
authorMichael Bommarito <michael.bommarito@gmail.com>
Tue, 14 Jul 2026 11:49:03 +0000 (07:49 -0400)
committerJakub Kicinski <kuba@kernel.org>
Wed, 22 Jul 2026 21:00:41 +0000 (14:00 -0700)
ila_csum_adjust_transport() caches ip6h = ipv6_hdr(skb) before calling
pskb_may_pull(). On a non-linear skb whose transport header sits in a page
fragment, pskb_may_pull() can call __pskb_pull_tail() / pskb_expand_head()
and free the old skb head, leaving ip6h dangling; the following
get_csum_diff(ip6h, p) then reads freed memory. ila_update_ipv6_locator()
uses ip6h (and the iaddr derived from it) again after the csum-adjust
call and additionally writes the new locator through that pointer.

Impact: a remote IPv6 packet routed through a configured ILA
csum-adjust-transport route or receive-side mapping triggers a
slab-use-after-free in ila_update_ipv6_locator() (KASAN). The route or
mapping requires CAP_NET_ADMIN to configure, but trigger packets are
unauthenticated once it exists.

Reload ip6h after each pskb_may_pull() in ila_csum_adjust_transport()
before the csum-diff read. In ila_update_ipv6_locator() only the
ILA_CSUM_ADJUST_TRANSPORT case pulls the skb, so reload ip6h and iaddr in
that case alone before the destination-address write; the neutral-map
modes never pull and keep their cached pointers.

Fixes: 33f11d16142b ("ila: Create net/ipv6/ila directory")
Cc: stable@vger.kernel.org
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Reviewed-by: Antoine Tenart <atenart@kernel.org>
Link: https://patch.msgid.link/20260714114903.3763420-1-michael.bommarito@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/ipv6/ila/ila_common.c

index e71571455c8a0ab72402b099d72bd945f6b3a073..b78179bfc4c72f08c65601a719fa6f7295eac17b 100644 (file)
@@ -85,6 +85,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
                        struct tcphdr *th = (struct tcphdr *)
                                        (skb_network_header(skb) + nhoff);
 
+                       ip6h = ipv6_hdr(skb);
                        diff = get_csum_diff(ip6h, p);
                        inet_proto_csum_replace_by_diff(&th->check, skb,
                                                        diff, true, true);
@@ -96,6 +97,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
                                        (skb_network_header(skb) + nhoff);
 
                        if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
+                               ip6h = ipv6_hdr(skb);
                                diff = get_csum_diff(ip6h, p);
                                inet_proto_csum_replace_by_diff(&uh->check, skb,
                                                                diff, true, true);
@@ -110,6 +112,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
                        struct icmp6hdr *ih = (struct icmp6hdr *)
                                        (skb_network_header(skb) + nhoff);
 
+                       ip6h = ipv6_hdr(skb);
                        diff = get_csum_diff(ip6h, p);
                        inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
                                                        diff, true, true);
@@ -127,6 +130,15 @@ void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
        switch (p->csum_mode) {
        case ILA_CSUM_ADJUST_TRANSPORT:
                ila_csum_adjust_transport(skb, p);
+               /*
+                * ila_csum_adjust_transport() calls pskb_may_pull(), which can
+                * reallocate the skb head and leave ip6h (and the iaddr derived
+                * from it) dangling; reload both before the write below.  The
+                * other csum modes do not pull, so their cached pointers stay
+                * valid.
+                */
+               ip6h = ipv6_hdr(skb);
+               iaddr = ila_a2i(&ip6h->daddr);
                break;
        case ILA_CSUM_NEUTRAL_MAP:
                if (sir2ila) {