* [PATCH net v3] ipv6: Implement limits on extension header parsing
@ 2026-04-27 10:13 Daniel Borkmann
2026-04-27 13:14 ` David Laight
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Daniel Borkmann @ 2026-04-27 10:13 UTC (permalink / raw)
To: kuba
Cc: edumazet, dsahern, tom, willemdebruijn.kernel, idosch,
justin.iurman, pabeni, netdev
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 <daniel@iogearbox.net>
---
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)
Documentation/networking/ip-sysctl.rst | 7 +++++++
include/net/dropreason-core.h | 6 ++++++
include/net/ipv6.h | 2 ++
include/net/netns/ipv6.h | 1 +
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs_core.c | 11 +++++++++++
net/ipv6/ip6_input.c | 6 ++++++
net/ipv6/ip6_tunnel.c | 5 +++++
net/ipv6/sysctl_net_ipv6.c | 8 ++++++++
9 files changed, 47 insertions(+)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 2e3a746fcc6d..f7412f4049d1 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2537,6 +2537,13 @@ max_hbh_length - INTEGER
Default: INT_MAX (unlimited)
+max_ext_hdrs_number - INTEGER
+ Maximum number of IPv6 extension headers allowed in a packet.
+ Limits how many extension headers will be traversed. The value
+ is read from the initial netns.
+
+ Default: 8
+
skip_notify_on_dev_down - BOOLEAN
Controls whether an RTM_DELROUTE message is generated for routes
removed when a device is taken down or deleted. IPv4 does not
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index e0ca3904ff8e..1fd91e59b84e 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -99,6 +99,7 @@
FN(FRAG_TOO_FAR) \
FN(TCP_MINTTL) \
FN(IPV6_BAD_EXTHDR) \
+ FN(IPV6_TOO_MANY_EXTHDRS) \
FN(IPV6_NDISC_FRAG) \
FN(IPV6_NDISC_HOP_LIMIT) \
FN(IPV6_NDISC_BAD_CODE) \
@@ -494,6 +495,11 @@ enum skb_drop_reason {
SKB_DROP_REASON_TCP_MINTTL,
/** @SKB_DROP_REASON_IPV6_BAD_EXTHDR: Bad IPv6 extension header. */
SKB_DROP_REASON_IPV6_BAD_EXTHDR,
+ /**
+ * @SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS: Number of IPv6 extension
+ * headers in the packet exceeds net.ipv6.max_ext_hdrs_number.
+ */
+ SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS,
/** @SKB_DROP_REASON_IPV6_NDISC_FRAG: invalid frag (suppress_frag_ndisc). */
SKB_DROP_REASON_IPV6_NDISC_FRAG,
/** @SKB_DROP_REASON_IPV6_NDISC_HOP_LIMIT: invalid hop limit. */
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index d042afe7a245..c540b750726e 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -90,6 +90,8 @@ struct ip_tunnel_info;
#define IP6_DEFAULT_MAX_DST_OPTS_LEN INT_MAX /* No limit */
#define IP6_DEFAULT_MAX_HBH_OPTS_LEN INT_MAX /* No limit */
+#define IP6_DEFAULT_MAX_EXT_HDRS_CNT 8
+
/*
* Addr type
*
diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index 499e4288170f..2cea457bddb4 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -54,6 +54,7 @@ struct netns_sysctl_ipv6 {
int max_hbh_opts_cnt;
int max_dst_opts_len;
int max_hbh_opts_len;
+ int max_ext_hdrs_cnt;
int seg6_flowlabel;
u32 ioam6_id;
u64 ioam6_id_wide;
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 0a88b376141d..19424c3f2dfc 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -945,6 +945,7 @@ static int __net_init inet6_net_init(struct net *net)
net->ipv6.sysctl.flowlabel_state_ranges = 0;
net->ipv6.sysctl.max_dst_opts_cnt = IP6_DEFAULT_MAX_DST_OPTS_CNT;
net->ipv6.sysctl.max_hbh_opts_cnt = IP6_DEFAULT_MAX_HBH_OPTS_CNT;
+ net->ipv6.sysctl.max_ext_hdrs_cnt = IP6_DEFAULT_MAX_EXT_HDRS_CNT;
net->ipv6.sysctl.max_dst_opts_len = IP6_DEFAULT_MAX_DST_OPTS_LEN;
net->ipv6.sysctl.max_hbh_opts_len = IP6_DEFAULT_MAX_HBH_OPTS_LEN;
net->ipv6.sysctl.fib_notify_on_flag_change = 0;
diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
index 49e31e4ae7b7..9df892e7f7fb 100644
--- a/net/ipv6/exthdrs_core.c
+++ b/net/ipv6/exthdrs_core.c
@@ -4,6 +4,8 @@
* not configured or static.
*/
#include <linux/export.h>
+
+#include <net/net_namespace.h>
#include <net/ipv6.h>
/*
@@ -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;
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
if (!hp)
return -1;
@@ -188,8 +194,10 @@ EXPORT_SYMBOL_GPL(ipv6_find_tlv);
int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
int target, unsigned short *fragoff, int *flags)
{
+ int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+ int exthdr_cnt = 0;
bool found;
if (fragoff)
@@ -216,6 +224,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
return -ENOENT;
}
+ if (unlikely(exthdr_cnt++ >= exthdr_max))
+ return -EBADMSG;
+
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
if (!hp)
return -EBADMSG;
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 967b07aeb683..79fa33573e53 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -403,6 +403,8 @@ INDIRECT_CALLABLE_DECLARE(int tcp_v6_rcv(struct sk_buff *));
void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
bool have_final)
{
+ int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
+ int exthdr_cnt = IP6CB(skb)->flags & IP6SKB_HOPBYHOP ? 1 : 0;
const struct inet6_protocol *ipprot;
struct inet6_dev *idev;
unsigned int nhoff;
@@ -487,6 +489,10 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
nexthdr = ret;
goto resubmit_final;
} else {
+ if (unlikely(exthdr_cnt++ >= exthdr_max)) {
+ SKB_DR_SET(reason, IPV6_TOO_MANY_EXTHDRS);
+ goto discard;
+ }
goto resubmit;
}
} else if (ret == 0) {
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index c468c83af0f2..4546a60942ab 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -395,15 +395,20 @@ ip6_tnl_dev_uninit(struct net_device *dev)
__u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)
{
+ int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
const struct ipv6hdr *ipv6h = (const struct ipv6hdr *)raw;
unsigned int nhoff = raw - skb->data;
unsigned int off = nhoff + sizeof(*ipv6h);
u8 nexthdr = ipv6h->nexthdr;
+ int exthdr_cnt = 0;
while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
struct ipv6_opt_hdr *hdr;
u16 optlen;
+ if (unlikely(exthdr_cnt++ >= exthdr_max))
+ break;
+
if (!pskb_may_pull(skb, off + sizeof(*hdr)))
break;
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index d2cd33e2698d..93f865545a7c 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -135,6 +135,14 @@ static struct ctl_table ipv6_table_template[] = {
.extra1 = SYSCTL_ZERO,
.extra2 = &flowlabel_reflect_max,
},
+ {
+ .procname = "max_ext_hdrs_number",
+ .data = &init_net.ipv6.sysctl.max_ext_hdrs_cnt,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_ONE,
+ },
{
.procname = "max_dst_opts_number",
.data = &init_net.ipv6.sysctl.max_dst_opts_cnt,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net v3] ipv6: Implement limits on extension header parsing
2026-04-27 10:13 [PATCH net v3] ipv6: Implement limits on extension header parsing Daniel Borkmann
@ 2026-04-27 13:14 ` David Laight
2026-04-27 13:30 ` Daniel Borkmann
2026-04-27 20:14 ` Victor Nogueira
2026-04-28 6:02 ` kernel test robot
2 siblings, 1 reply; 5+ messages in thread
From: David Laight @ 2026-04-27 13:14 UTC (permalink / raw)
To: Daniel Borkmann
Cc: kuba, edumazet, dsahern, tom, willemdebruijn.kernel, idosch,
justin.iurman, pabeni, netdev
On Mon, 27 Apr 2026 12:13:18 +0200
Daniel Borkmann <daniel@iogearbox.net> 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 <daniel@iogearbox.net>
> ---
> 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
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v3] ipv6: Implement limits on extension header parsing
2026-04-27 13:14 ` David Laight
@ 2026-04-27 13:30 ` Daniel Borkmann
0 siblings, 0 replies; 5+ messages in thread
From: Daniel Borkmann @ 2026-04-27 13:30 UTC (permalink / raw)
To: David Laight
Cc: kuba, edumazet, dsahern, tom, willemdebruijn.kernel, idosch,
justin.iurman, pabeni, netdev
On 4/27/26 3:14 PM, David Laight wrote:
> On Mon, 27 Apr 2026 12:13:18 +0200
> Daniel Borkmann <daniel@iogearbox.net> wrote:
[...]
>> ---
>> 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;
Well, its in the same style as the other existing gating counters, I'd
rather leave as-is rather than mixing inc/decs.
Thanks,
Daniel
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v3] ipv6: Implement limits on extension header parsing
2026-04-27 10:13 [PATCH net v3] ipv6: Implement limits on extension header parsing Daniel Borkmann
2026-04-27 13:14 ` David Laight
@ 2026-04-27 20:14 ` Victor Nogueira
2026-04-28 6:02 ` kernel test robot
2 siblings, 0 replies; 5+ messages in thread
From: Victor Nogueira @ 2026-04-27 20:14 UTC (permalink / raw)
To: Daniel Borkmann, kuba
Cc: edumazet, dsahern, tom, willemdebruijn.kernel, idosch,
justin.iurman, pabeni, netdev
On 27/04/2026 07:13, Daniel Borkmann 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 <daniel@iogearbox.net>
> [...]
> diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
> index 49e31e4ae7b7..9df892e7f7fb 100644
> --- a/net/ipv6/exthdrs_core.c
> +++ b/net/ipv6/exthdrs_core.c
> [...]
> @@ -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);
This seems to be breaking the build when CONFIG_IPV6 is disabled:
net/ipv6/exthdrs_core.c: In function ‘ipv6_skip_exthdr’:
net/ipv6/exthdrs_core.c:77:45: error: ‘struct net’ has no member named
‘ipv6’; did you mean ‘ipv4’?
77 | int exthdr_max =
READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
> [...]
> @@ -188,8 +194,10 @@ EXPORT_SYMBOL_GPL(ipv6_find_tlv);
> int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
> int target, unsigned short *fragoff, int *flags)
> {
> + int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
Here as well:
net/ipv6/exthdrs_core.c: In function ‘ipv6_find_hdr’:
net/ipv6/exthdrs_core.c:197:45: error: ‘struct net’ has no member named
‘ipv6’; did you mean ‘ipv4’?
197 | int exthdr_max =
READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
cheers,
Victor
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v3] ipv6: Implement limits on extension header parsing
2026-04-27 10:13 [PATCH net v3] ipv6: Implement limits on extension header parsing Daniel Borkmann
2026-04-27 13:14 ` David Laight
2026-04-27 20:14 ` Victor Nogueira
@ 2026-04-28 6:02 ` kernel test robot
2 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2026-04-28 6:02 UTC (permalink / raw)
To: Daniel Borkmann, kuba
Cc: llvm, oe-kbuild-all, edumazet, dsahern, tom,
willemdebruijn.kernel, idosch, justin.iurman, pabeni, netdev
Hi Daniel,
kernel test robot noticed the following build errors:
[auto build test ERROR on net/main]
url: https://github.com/intel-lab-lkp/linux/commits/Daniel-Borkmann/ipv6-Implement-limits-on-extension-header-parsing/20260427-194303
base: net/main
patch link: https://lore.kernel.org/r/20260427101318.750730-1-daniel%40iogearbox.net
patch subject: [PATCH net v3] ipv6: Implement limits on extension header parsing
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260428/202604280700.QqKZKu4g-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260428/202604280700.QqKZKu4g-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604280700.QqKZKu4g-lkp@intel.com/
All errors (new ones prefixed by >>):
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:660:10: note: expanded from macro '__native_word'
660 | (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:660:39: note: expanded from macro '__native_word'
660 | (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:661:10: note: expanded from macro '__native_word'
661 | sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long))
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:661:38: note: expanded from macro '__native_word'
661 | sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long))
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:48: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:50:14: note: expanded from macro 'READ_ONCE'
50 | __READ_ONCE(x); \
| ^
include/asm-generic/rwonce.h:44:65: note: expanded from macro '__READ_ONCE'
44 | #define __READ_ONCE(x) (*(const volatile __unqual_scalar_typeof(x) *)&(x))
| ^
include/linux/compiler_types.h:635:53: note: expanded from macro '__unqual_scalar_typeof'
635 | #define __unqual_scalar_typeof(x) __typeof_unqual__(x)
| ^
>> net/ipv6/exthdrs_core.c:77:38: error: no member named 'ipv6' in 'struct net'
77 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:50:14: note: expanded from macro 'READ_ONCE'
50 | __READ_ONCE(x); \
| ^
include/asm-generic/rwonce.h:44:72: note: expanded from macro '__READ_ONCE'
44 | #define __READ_ONCE(x) (*(const volatile __unqual_scalar_typeof(x) *)&(x))
| ^
net/ipv6/exthdrs_core.c:197:38: error: no member named 'ipv6' in 'struct net'
197 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:660:10: note: expanded from macro '__native_word'
660 | (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
net/ipv6/exthdrs_core.c:197:38: error: no member named 'ipv6' in 'struct net'
197 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:660:39: note: expanded from macro '__native_word'
660 | (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
net/ipv6/exthdrs_core.c:197:38: error: no member named 'ipv6' in 'struct net'
197 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:661:10: note: expanded from macro '__native_word'
661 | sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long))
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
net/ipv6/exthdrs_core.c:197:38: error: no member named 'ipv6' in 'struct net'
197 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:35: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
| ^
include/linux/compiler_types.h:661:38: note: expanded from macro '__native_word'
661 | sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long))
| ^
include/linux/compiler_types.h:699:22: note: expanded from macro 'compiletime_assert'
699 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~
include/linux/compiler_types.h:687:23: note: expanded from macro '_compiletime_assert'
687 | __compiletime_assert(condition, msg, prefix, suffix)
| ^~~~~~~~~
include/linux/compiler_types.h:679:9: note: expanded from macro '__compiletime_assert'
679 | if (!(condition)) \
| ^~~~~~~~~
net/ipv6/exthdrs_core.c:197:38: error: no member named 'ipv6' in 'struct net'
197 | int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
| ~~~~~~~~ ^
include/asm-generic/rwonce.h:49:33: note: expanded from macro 'READ_ONCE'
49 | compiletime_assert_rwonce_type(x); \
| ^
include/asm-generic/rwonce.h:36:48: note: expanded from macro 'compiletime_assert_rwonce_type'
36 | compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long), \
vim +77 net/ipv6/exthdrs_core.c
28
29 /*
30 * Skip any extension headers. This is used by the ICMP module.
31 *
32 * Note that strictly speaking this conflicts with RFC 2460 4.0:
33 * ...The contents and semantics of each extension header determine whether
34 * or not to proceed to the next header. Therefore, extension headers must
35 * be processed strictly in the order they appear in the packet; a
36 * receiver must not, for example, scan through a packet looking for a
37 * particular kind of extension header and process that header prior to
38 * processing all preceding ones.
39 *
40 * We do exactly this. This is a protocol bug. We can't decide after a
41 * seeing an unknown discard-with-error flavour TLV option if it's a
42 * ICMP error message or not (errors should never be send in reply to
43 * ICMP error messages).
44 *
45 * But I see no other way to do this. This might need to be reexamined
46 * when Linux implements ESP (and maybe AUTH) headers.
47 * --AK
48 *
49 * This function parses (probably truncated) exthdr set "hdr".
50 * "nexthdrp" initially points to some place,
51 * where type of the first header can be found.
52 *
53 * It skips all well-known exthdrs, and returns pointer to the start
54 * of unparsable area i.e. the first header with unknown type.
55 * If it is not NULL *nexthdr is updated by type/protocol of this header.
56 *
57 * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
58 * - it may return pointer pointing beyond end of packet,
59 * if the last recognized header is truncated in the middle.
60 * - if packet is truncated, so that all parsed headers are skipped,
61 * it returns NULL.
62 * - First fragment header is skipped, not-first ones
63 * are considered as unparsable.
64 * - Reports the offset field of the final fragment header so it is
65 * possible to tell whether this is a first fragment, later fragment,
66 * or not fragmented.
67 * - ESP is unparsable for now and considered like
68 * normal payload protocol.
69 * - Note also special handling of AUTH header. Thanks to IPsec wizards.
70 *
71 * --ANK (980726)
72 */
73
74 int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
75 __be16 *frag_offp)
76 {
> 77 int exthdr_max = READ_ONCE(init_net.ipv6.sysctl.max_ext_hdrs_cnt);
78 u8 nexthdr = *nexthdrp;
79 int exthdr_cnt = 0;
80
81 *frag_offp = 0;
82
83 while (ipv6_ext_hdr(nexthdr)) {
84 struct ipv6_opt_hdr _hdr, *hp;
85 int hdrlen;
86
87 if (nexthdr == NEXTHDR_NONE)
88 return -1;
89 if (unlikely(exthdr_cnt++ >= exthdr_max))
90 return -1;
91 hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
92 if (!hp)
93 return -1;
94 if (nexthdr == NEXTHDR_FRAGMENT) {
95 __be16 _frag_off, *fp;
96 fp = skb_header_pointer(skb,
97 start+offsetof(struct frag_hdr,
98 frag_off),
99 sizeof(_frag_off),
100 &_frag_off);
101 if (!fp)
102 return -1;
103
104 *frag_offp = *fp;
105 if (ntohs(*frag_offp) & ~0x7)
106 break;
107 hdrlen = 8;
108 } else if (nexthdr == NEXTHDR_AUTH)
109 hdrlen = ipv6_authlen(hp);
110 else
111 hdrlen = ipv6_optlen(hp);
112
113 nexthdr = hp->nexthdr;
114 start += hdrlen;
115 }
116
117 *nexthdrp = nexthdr;
118 return start;
119 }
120 EXPORT_SYMBOL(ipv6_skip_exthdr);
121
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-04-28 6:02 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-27 10:13 [PATCH net v3] ipv6: Implement limits on extension header parsing Daniel Borkmann
2026-04-27 13:14 ` David Laight
2026-04-27 13:30 ` Daniel Borkmann
2026-04-27 20:14 ` Victor Nogueira
2026-04-28 6:02 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox