]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ipv6: Cap TLV scan in ip6_tnl_parse_tlv_enc_lim
authorDaniel Borkmann <daniel@iogearbox.net>
Tue, 21 Apr 2026 20:24:06 +0000 (22:24 +0200)
committerJakub Kicinski <kuba@kernel.org>
Thu, 23 Apr 2026 18:52:07 +0000 (11:52 -0700)
Commit 47d3d7ac656a ("ipv6: Implement limits on Hop-by-Hop and
Destination options") added net.ipv6.max_{hbh,dst}_opts_{cnt,len}
and applied them in ip6_parse_tlv(), the generic TLV walker
invoked from ipv6_destopt_rcv() and ipv6_parse_hopopts().

ip6_tnl_parse_tlv_enc_lim() does not go through ip6_parse_tlv();
it has its own hand-rolled TLV scanner inside its NEXTHDR_DEST
branch which looks for IPV6_TLV_TNL_ENCAP_LIMIT. That inner
loop is bounded only by optlen, which can be up to 2048 bytes.
Stuffing the Destination Options header with 2046 Pad1 (type=0)
entries advances the scanner a single byte at a time, yielding
~2000 TLV iterations per extension header.

Reusing max_dst_opts_cnt to bound the TLV iterations, matching
the semantics from 47d3d7ac656a, would require duplicating
ip6_parse_tlv() to also validate Pad1/PadN payload. It would
also mandate enforcing max_dst_opts_len, since otherwise an
attacker shifts the axis to few options with a giant PadN and
recovers the original DoS. Allowing up to 8 options before the
tunnel encapsulation limit TLV is liberal enough; in practice
encap limit is the first TLV. Thus, go with a hard-coded limit
IP6_TUNNEL_MAX_DEST_TLVS (8).

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/ipv6/ip6_tunnel.c

index 46bc06506470b2d742705c6096110c91b8e8dbb7..c468c83af0f20e72b2f40315c3dcd6aef3de9e60 100644 (file)
@@ -62,6 +62,8 @@ MODULE_LICENSE("GPL");
 MODULE_ALIAS_RTNL_LINK("ip6tnl");
 MODULE_ALIAS_NETDEV("ip6tnl0");
 
+#define IP6_TUNNEL_MAX_DEST_TLVS    8
+
 #define IP6_TUNNEL_HASH_SIZE_SHIFT  5
 #define IP6_TUNNEL_HASH_SIZE (1 << IP6_TUNNEL_HASH_SIZE_SHIFT)
 
@@ -425,11 +427,15 @@ __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)
                                break;
                }
                if (nexthdr == NEXTHDR_DEST) {
+                       int tlv_cnt = 0;
                        u16 i = 2;
 
                        while (1) {
                                struct ipv6_tlv_tnl_enc_lim *tel;
 
+                               if (unlikely(tlv_cnt++ >= IP6_TUNNEL_MAX_DEST_TLVS))
+                                       break;
+
                                /* No more room for encapsulation limit */
                                if (i + sizeof(*tel) > optlen)
                                        break;