Re: [PATCH net] ipv6: rpl: expand skb head when recompressed SRH grows, not only on last segment

From: Kuniyuki Iwashima

Date: Tue Apr 21 2026 - 00:55:25 EST


From: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
Date: Mon, 20 Apr 2026 21:32:25 +0200
> ipv6_rpl_srh_rcv() processes a Routing Protocol for LLNs Source Routing
> Header by decompressing it, swapping the next segment address into
> ipv6_hdr->daddr, recompressing, and pushing the new header back. The
> recompressed header can be larger than the original when the
> address-elision opportunities are worse after the swap.
>
> The function pulls (hdr->hdrlen + 1) << 3 bytes (the old header) and
> pushes (chdr->hdrlen + 1) << 3 + sizeof(ipv6hdr) bytes (the new header
> plus the IPv6 header). pskb_expand_head() is called to guarantee
> headroom only when segments_left == 0.
>
> A crafted SRH that loops back to the local host (each segment is a local
> address, so ip6_route_input() delivers it back to ipv6_rpl_srh_rcv())
> with chdr growing on each pass exhausts headroom over several
> iterations.

How could this occur.. ? Did AI generate a repro or just
flagged the possibility ?

ipv6_rpl_sr_hdr.hdrlen >> 3 is the size of addresses in the
header and 1 >> 3 is the size of ipv6_rpl_sr_hdr itself, which
is pulled into skb_headroom in ipv6_rthdr_rcv().

In ipv6_rpl_srh_rcv(), the number of addresses is calculated
based on ipv6_rpl_sr_hdr.hdrlen, and when hdr->segments_left
is not zero in the "if" below, the new header has the exact same
size with the old header, so there should be no overflow.

Also, before the "if", ipv6_rpl_srh_rcv() calls

skb_pull(skb, ((hdr->hdrlen + 1) << 3));

and after that,

skb_push(skb, ((chdr->hdrlen + 1) << 3) + sizeof(struct ipv6hdr));

and if we jump to the looped_back label, it calls

skb_pull(skb, sizeof(struct ipv6hdr));

So, I think the same size are pulled and pushed for each iteration
(except for segments_left == 0 case) even with local addresses.


> When skb_push() lands skb->data exactly at skb->head,
> skb_reset_network_header() stores 0, and skb_mac_header_rebuild()'s
> skb_set_mac_header(skb, -skb->mac_len) computes 0 + (u16)(-14) = 65522.
> The subsequent memmove writes 14 bytes at skb->head + 65522.
>
> Expand the head whenever there is insufficient room for the push, not
> only on the final segment.
>
> Cc: "David S. Miller" <davem@xxxxxxxxxxxxx>
> Cc: David Ahern <dsahern@xxxxxxxxxx>
> Cc: Eric Dumazet <edumazet@xxxxxxxxxx>
> Cc: Jakub Kicinski <kuba@xxxxxxxxxx>
> Cc: Paolo Abeni <pabeni@xxxxxxxxxx>
> Cc: Simon Horman <horms@xxxxxxxxxx>
> Reported-by: Anthropic
> Cc: stable <stable@xxxxxxxxxx>
> Assisted-by: gkh_clanker_t1000
> Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> ---
> net/ipv6/exthdrs.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
> index 95558fd6f447..d866ab011e0a 100644
> --- a/net/ipv6/exthdrs.c
> +++ b/net/ipv6/exthdrs.c
> @@ -592,7 +592,9 @@ static int ipv6_rpl_srh_rcv(struct sk_buff *skb)
> skb_pull(skb, ((hdr->hdrlen + 1) << 3));
> skb_postpull_rcsum(skb, oldhdr,
> sizeof(struct ipv6hdr) + ((hdr->hdrlen + 1) << 3));
> - if (unlikely(!hdr->segments_left)) {
> + if (unlikely(!hdr->segments_left ||
> + skb_headroom(skb) < sizeof(struct ipv6hdr) +
> + ((chdr->hdrlen + 1) << 3))) {
> if (pskb_expand_head(skb, sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3), 0,
> GFP_ATOMIC)) {
> __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_OUTDISCARDS);
> --
> 2.53.0
>