Subject: Re: [PATCH net v3] ipv6: Implement limits on extension header parsing

From: David Laight <[email-masked]>
Date: Mon, 27 Apr 2026 14:14:49 +0100

On Mon, 27 Apr 2026 12:13:18 +0200
Daniel Borkmann <[email-masked]> wrote:

> ipv6_{skip_exthdr,find_hdr}() and ip6_{tnl_parse_tlv_enc_lim,
> protocol_deliver_rcu}() iterate over IPv6 extension headers until they
> find a non-extension-header protocol or run out of packet data. The
> loops have no iteration counter, relying solely on the packet length
> to bound them. For a crafted packet with 8-byte extension headers
> filling a 64KB jumbogram, this means a worst case of up to ~8k
> iterations with a skb_header_pointer call each. ipv6_skip_exthdr(),
> for example, is used where it parses the inner quoted packet inside
> an incoming ICMPv6 error:
> 
>   - icmpv6_rcv
>     - checksum validation
>     - case ICMPV6_DEST_UNREACH
>       - icmpv6_notify
>         - pskb_may_pull()       <- pull inner IPv6 header
>         - ipv6_skip_exthdr()    <- iterates here
>         - pskb_may_pull()
>         - ipprot->err_handler() <- sk lookup
> 
> The per-iteration cost of ipv6_skip_exthdr itself is generally
> light, but skb_header_pointer becomes more costly on reassembled
> packets: the first ~1232 bytes of the inner packet are in the skb's
> linear area, but the remaining ~63KB are in the frag_list where
> skb_copy_bits is needed to read data.
> 
> Add a configurable limit via a new sysctl net.ipv6.max_ext_hdrs_number
> (default 8, minimum 1). All four extension header walking functions
> are bound by this limit. The sysctl is in line with commit 47d3d7ac656a
> ("ipv6: Implement limits on Hop-by-Hop and Destination options").
> As documented, init_net is used to derive max_ext_hdrs_number to
> be consistent given a net cannot always reliably be retrieved.
> 
> Note that the check in ip6_protocol_deliver_rcu() happens right
> before the goto resubmit, such that we don't have to have a test
> for ipv6_ext_hdr() in the fast-path.
> 
> There's an ongoing IETF draft-iurman-6man-eh-occurrences to enforce
> IPv6 extension headers ordering and occurrence. The latter also
> discusses security implications. As per RFC8200 section 4.1, the
> occurrence rules for extension headers provide a practical upper
> bound, thus 8 was used as the default.
> 
> Signed-off-by: Daniel Borkmann <[email-masked]>
> ---
>  v2->v3:
>    - Adding IP6SKB_HOPBYHOP coverage (Justin)
>    - I left the limit at 8 w/ sysctl, still feels the better
>      option to me if we can keep the worst-case more tightened
>  v1->v2:
>    - Set the default to 8 (Justin)
>    - Update IETF references (Justin)
>    - Add core path coverage as well (Justin)
...
> @@ -72,7 +74,9 @@ EXPORT_SYMBOL(ipv6_ext_hdr);
>  int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
>  		     __be16 *frag_offp)
>  {
> +	int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
>  	u8 nexthdr = *nexthdrp;
> +	int exthdr_cnt = 0;
>  
>  	*frag_offp = 0;
>  
> @@ -82,6 +86,8 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
>  
>  		if (nexthdr == NEXTHDR_NONE)
>  			return -1;
> +		if (unlikely(exthdr_cnt++ >= exthdr_max))
> +			return -1;

It would be better to decrement the count and error at zero.
		if (unlikely(--exthdr_max < 0))
			return -1;

	David