* [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities
@ 2026-01-21 21:49 Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert
IPv6 extension headers are defined to be quite open ended with few
limits. For instance, RFC8200 requires a receiver to process any
number of extension headers in a packet in any order. This flexiblity
comes at the cost of a potential Denial of Service attack. The only
thing that might mitigate the DoS attacks is the fact that packets
with extension headers experience high drop rates on the Internet so
that a DoS attack based on extension wouldn't be very effective at
Internet scale.
This patch set addresses some of the more egregious vulnerabilities
of extension headers to DoS attack.
- If sysctl.max_dst_opts_cnt or hbh_opts_cnt are set to 0 then that
disallows packets with Destination Options or Hop-by-Hop Options even
if the packet contain zero non-padding options
- Add a case for IPV6_TLV_TNL_ENCAP_LIMIT in the switch on TLV type
in ip6_parse_tlv function. This TLV is handled in tunnel processing,
however it needs to be detected in ip6_parse_tlv to properly account
for it as recognized non-padding option
- Move IPV6_TLV_TNL_ENCAP_LIMIT to uapi/linux/in6.h so that all the
TLV definitions are in one place
- Set the default limits of non-padding Hop-by-Hop and Destination
options to 2. This means that if a packet contains more then two
non-padding options then it will be dropped. The previous limit
was 8, but that was too liberal considering that the stack only
support two Destination Options and the most Hop-by-Hop options
likely to ever be in the same packet are IOAM and JUMBO. The limit
can be increased via sysctl for private use and experimentation
- Enforce RFC8200 recommended ordering of Extension Headers. This
also enforces that any Extension Header occurs at most once
in a packet (Destination Options before the Routing Header is
considered deprecated, so Destination Options may only appear once).
The enforce_ext_hdr_order sysctl controls enforcement. If it's set
to true then order is enforced, if it's set to false then neither
order nor number of occurrences are enforced.
The enforced ordering is:
IPv6 header
Hop-by-Hop Options header
Routing header
Fragment header
Authentication header
Encapsulating Security Payload header
Destination Options header
Upper-Layer header
V4: Switch order of patches to avoid transient build failure
Tom Herbert (7):
ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is
ipv6: Cleanup IPv6 TLV definitions
ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch
ipv6: Set HBH and DestOpt limits to 2
ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls
ipv6: Enforce Extension Header ordering
ipv6: Document enforce_ext_hdr_order sysctl
Documentation/networking/ip-sysctl.rst | 53 +++++++++++++++++++++-----
include/net/ipv6.h | 9 +++--
include/net/netns/ipv6.h | 1 +
include/net/protocol.h | 16 ++++++++
include/uapi/linux/in6.h | 21 ++++++----
include/uapi/linux/ip6_tunnel.h | 1 -
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs.c | 20 ++++++++--
net/ipv6/ip6_input.c | 14 +++++++
net/ipv6/reassembly.c | 1 +
net/ipv6/sysctl_net_ipv6.c | 7 ++++
net/ipv6/xfrm6_protocol.c | 2 +
12 files changed, 123 insertions(+), 23 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-23 21:16 ` Justin Iurman 2026-01-21 21:49 ` [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions Tom Herbert ` (5 subsequent siblings) 6 siblings, 1 reply; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert In IPv6 Destination options processing function check if net->ipv6.sysctl.max_dst_opts_cnt is zero up front. If it is zero then drop the packet since Destination Options processing is disabled. Similarly, in IPv6 hop-by-hop options processing function check if net->ipv6.sysctl.max_hbh_opts_cnt is zero up front. If it is zero then drop the packet since Hop-by-Hop Options processing is disabled. Signed-off-by: Tom Herbert <tom@herbertland.com> --- net/ipv6/exthdrs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 54088fa0c09d..45bbad76f5de 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -303,7 +303,8 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) struct net *net = dev_net(skb->dev); int extlen; - if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || + if (!net->ipv6.sysctl.max_dst_opts_cnt || + !pskb_may_pull(skb, skb_transport_offset(skb) + 8) || !pskb_may_pull(skb, (skb_transport_offset(skb) + ((skb_transport_header(skb)[1] + 1) << 3)))) { __IP6_INC_STATS(dev_net(dst_dev(dst)), idev, @@ -1041,7 +1042,8 @@ int ipv6_parse_hopopts(struct sk_buff *skb) * sizeof(struct ipv6hdr) by definition of * hop-by-hop options. */ - if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || + if (!net->ipv6.sysctl.max_hbh_opts_cnt || + !pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || !pskb_may_pull(skb, (sizeof(struct ipv6hdr) + ((skb_transport_header(skb)[1] + 1) << 3)))) { fail_and_free: -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is 2026-01-21 21:49 ` [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert @ 2026-01-23 21:16 ` Justin Iurman 0 siblings, 0 replies; 14+ messages in thread From: Justin Iurman @ 2026-01-23 21:16 UTC (permalink / raw) To: Tom Herbert, davem, kuba, netdev On 1/21/26 22:49, Tom Herbert wrote: > In IPv6 Destination options processing function check if > net->ipv6.sysctl.max_dst_opts_cnt is zero up front. If it is zero then > drop the packet since Destination Options processing is disabled. > > Similarly, in IPv6 hop-by-hop options processing function check if > net->ipv6.sysctl.max_hbh_opts_cnt is zero up front. If it is zero then > drop the packet since Hop-by-Hop Options processing is disabled. > > Signed-off-by: Tom Herbert <tom@herbertland.com> > --- > net/ipv6/exthdrs.c | 6 ++++-- > 1 file changed, 4 insertions(+), 2 deletions(-) > > diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c > index 54088fa0c09d..45bbad76f5de 100644 > --- a/net/ipv6/exthdrs.c > +++ b/net/ipv6/exthdrs.c > @@ -303,7 +303,8 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) > struct net *net = dev_net(skb->dev); > int extlen; > > - if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || > + if (!net->ipv6.sysctl.max_dst_opts_cnt || > + !pskb_may_pull(skb, skb_transport_offset(skb) + 8) || > !pskb_may_pull(skb, (skb_transport_offset(skb) + > ((skb_transport_header(skb)[1] + 1) << 3)))) { > __IP6_INC_STATS(dev_net(dst_dev(dst)), idev, Hi Tom, You should use READ_ONCE() and store it: int max_dst_opts_cnt = READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt); if (!max_dst_opts_cnt || ...) [...] if (ip6_parse_tlv(false, skb, max_dst_opts_cnt)) { > @@ -1041,7 +1042,8 @@ int ipv6_parse_hopopts(struct sk_buff *skb) > * sizeof(struct ipv6hdr) by definition of > * hop-by-hop options. > */ > - if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || > + if (!net->ipv6.sysctl.max_hbh_opts_cnt || > + !pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || > !pskb_may_pull(skb, (sizeof(struct ipv6hdr) + > ((skb_transport_header(skb)[1] + 1) << 3)))) { > fail_and_free: Same remark for max_hbh_opts_cnt. ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-23 21:19 ` Justin Iurman 2026-01-21 21:49 ` [PATCH net-next v4 3/7] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert ` (4 subsequent siblings) 6 siblings, 1 reply; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert Move IPV6_TLV_TNL_ENCAP_LIMIT to uapi/linux/in6.h to be with the rest of the TLV definitions. Label each of the TLV definitions as to whether they are a Hop-by-Hop option, Destination option, or both. Signed-off-by: Tom Herbert <tom@herbertland.com> --- include/uapi/linux/in6.h | 21 ++++++++++++++------- include/uapi/linux/ip6_tunnel.h | 1 - 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 5a47339ef7d7..c2f873c98d20 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -140,14 +140,21 @@ struct in6_flowlabel_req { /* * IPv6 TLV options. + * + * Hop-by-Hop and Destination options share the same number space. + * For each option below whether it is a Hop-by-Hop option or + * a Destination option is indicated by HBH or DestOpt. */ -#define IPV6_TLV_PAD1 0 -#define IPV6_TLV_PADN 1 -#define IPV6_TLV_ROUTERALERT 5 -#define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ -#define IPV6_TLV_IOAM 49 /* RFC 9486 */ -#define IPV6_TLV_JUMBO 194 -#define IPV6_TLV_HAO 201 /* home address option */ +#define IPV6_TLV_PAD1 0 /* HBH or DestOpt */ +#define IPV6_TLV_PADN 1 /* HBH or DestOpt */ +#define IPV6_TLV_ROUTERALERT 5 /* HBH */ +#define IPV6_TLV_TNL_ENCAP_LIMIT 4 /* RFC 2473, DestOpt */ +#define IPV6_TLV_CALIPSO 7 /* RFC 5570, HBH */ +#define IPV6_TLV_IOAM 49 /* RFC 9486, HBH or Destopt + * IOAM sent and rcvd as HBH + */ +#define IPV6_TLV_JUMBO 194 /* HBH */ +#define IPV6_TLV_HAO 201 /* home address option, DestOpt */ /* * IPV6 socket options diff --git a/include/uapi/linux/ip6_tunnel.h b/include/uapi/linux/ip6_tunnel.h index 85182a839d42..35af4d9c35fb 100644 --- a/include/uapi/linux/ip6_tunnel.h +++ b/include/uapi/linux/ip6_tunnel.h @@ -6,7 +6,6 @@ #include <linux/if.h> /* For IFNAMSIZ. */ #include <linux/in6.h> /* For struct in6_addr. */ -#define IPV6_TLV_TNL_ENCAP_LIMIT 4 #define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4 /* don't add encapsulation limit if one isn't present in inner packet */ -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions 2026-01-21 21:49 ` [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions Tom Herbert @ 2026-01-23 21:19 ` Justin Iurman 0 siblings, 0 replies; 14+ messages in thread From: Justin Iurman @ 2026-01-23 21:19 UTC (permalink / raw) To: Tom Herbert, davem, kuba, netdev On 1/21/26 22:49, Tom Herbert wrote: > Move IPV6_TLV_TNL_ENCAP_LIMIT to uapi/linux/in6.h to be with the rest > of the TLV definitions. Label each of the TLV definitions as to whether > they are a Hop-by-Hop option, Destination option, or both. > > Signed-off-by: Tom Herbert <tom@herbertland.com> > --- > include/uapi/linux/in6.h | 21 ++++++++++++++------- > include/uapi/linux/ip6_tunnel.h | 1 - > 2 files changed, 14 insertions(+), 8 deletions(-) > > diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h > index 5a47339ef7d7..c2f873c98d20 100644 > --- a/include/uapi/linux/in6.h > +++ b/include/uapi/linux/in6.h > @@ -140,14 +140,21 @@ struct in6_flowlabel_req { > > /* > * IPv6 TLV options. > + * > + * Hop-by-Hop and Destination options share the same number space. > + * For each option below whether it is a Hop-by-Hop option or > + * a Destination option is indicated by HBH or DestOpt. > */ > -#define IPV6_TLV_PAD1 0 > -#define IPV6_TLV_PADN 1 > -#define IPV6_TLV_ROUTERALERT 5 > -#define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ > -#define IPV6_TLV_IOAM 49 /* RFC 9486 */ > -#define IPV6_TLV_JUMBO 194 > -#define IPV6_TLV_HAO 201 /* home address option */ > +#define IPV6_TLV_PAD1 0 /* HBH or DestOpt */ > +#define IPV6_TLV_PADN 1 /* HBH or DestOpt */ > +#define IPV6_TLV_ROUTERALERT 5 /* HBH */ > +#define IPV6_TLV_TNL_ENCAP_LIMIT 4 /* RFC 2473, DestOpt */ Would make sense to have IPV6_TLV_TNL_ENCAP_LIMIT (4) before IPV6_TLV_ROUTERALERT (5). > +#define IPV6_TLV_CALIPSO 7 /* RFC 5570, HBH */ > +#define IPV6_TLV_IOAM 49 /* RFC 9486, HBH or Destopt > + * IOAM sent and rcvd as HBH > + */ > +#define IPV6_TLV_JUMBO 194 /* HBH */ > +#define IPV6_TLV_HAO 201 /* home address option, DestOpt */ > > /* > * IPV6 socket options > diff --git a/include/uapi/linux/ip6_tunnel.h b/include/uapi/linux/ip6_tunnel.h > index 85182a839d42..35af4d9c35fb 100644 > --- a/include/uapi/linux/ip6_tunnel.h > +++ b/include/uapi/linux/ip6_tunnel.h > @@ -6,7 +6,6 @@ > #include <linux/if.h> /* For IFNAMSIZ. */ > #include <linux/in6.h> /* For struct in6_addr. */ > > -#define IPV6_TLV_TNL_ENCAP_LIMIT 4 > #define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4 > > /* don't add encapsulation limit if one isn't present in inner packet */ ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v4 3/7] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 4/7] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert ` (3 subsequent siblings) 6 siblings, 0 replies; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert IPV6_TLV_TNL_ENCAP_LIMIT is a recognized Destination option that is processed in ip_tunnel.c. Add a case for it in the switch in ip6_parse_tlv so that it is recognized as a known option. Also remove the unlikely around the check for max_count < 0 since the default limits for HBH and Destination options can be less than zero. Signed-off-by: Tom Herbert <tom@herbertland.com> --- net/ipv6/exthdrs.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 45bbad76f5de..394e3397e4d4 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -122,7 +122,7 @@ static bool ip6_parse_tlv(bool hopbyhop, int tlv_count = 0; int padlen = 0; - if (unlikely(max_count < 0)) { + if (max_count < 0) { disallow_unknowns = true; max_count = -max_count; } @@ -202,6 +202,16 @@ static bool ip6_parse_tlv(bool hopbyhop, if (!ipv6_dest_hao(skb, off)) return false; break; +#endif +#if IS_ENABLED(CONFIG_IPV6_TUNNEL) + case IPV6_TLV_TNL_ENCAP_LIMIT: + /* The tunnel encapsulation option. + * This is handled in ip6_tunnel.c so + * we don't need to do anything here + * except to accept it as a recognized + * option + */ + break; #endif default: if (!ip6_tlvopt_unknown(skb, off, -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v4 4/7] ipv6: Set HBH and DestOpt limits to 2 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert ` (2 preceding siblings ...) 2026-01-21 21:49 ` [PATCH net-next v4 3/7] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 5/7] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert ` (2 subsequent siblings) 6 siblings, 0 replies; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert Set the default limits of non-padding Hop-by-Hop and Destination options to 2. This means that if a packet contains more then two non-padding options then it will be dropped. The previous limit was 8, but that was too liberal considering that the stack only support two Destination Options and the most Hop-by-Hop options likely to ever be in the same packet are IOAM and JUMBO. The limit can be increased via sysctl for private use and experimenation. Signed-off-by: Tom Herbert <tom@herbertland.com> --- include/net/ipv6.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index c7f597da01cd..31d270c8c2e4 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -86,9 +86,12 @@ struct ip_tunnel_info; * silently discarded. */ -/* Default limits for Hop-by-Hop and Destination options */ -#define IP6_DEFAULT_MAX_DST_OPTS_CNT 8 -#define IP6_DEFAULT_MAX_HBH_OPTS_CNT 8 +/* Default limits for Hop-by-Hop and Destination non-padding options. The + * default value for both is 2. This sets a limit at two non-padding options + * (see sysctl documention) + */ +#define IP6_DEFAULT_MAX_DST_OPTS_CNT 2 +#define IP6_DEFAULT_MAX_HBH_OPTS_CNT 2 #define IP6_DEFAULT_MAX_DST_OPTS_LEN INT_MAX /* No limit */ #define IP6_DEFAULT_MAX_HBH_OPTS_LEN INT_MAX /* No limit */ -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v4 5/7] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert ` (3 preceding siblings ...) 2026-01-21 21:49 ` [PATCH net-next v4 4/7] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering Tom Herbert 2026-01-21 21:49 ` [PATCH net-next v4 7/7] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert 6 siblings, 0 replies; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert In the descriptions of max_dst_opts_number and max_hbh_opts_number sysctls add text about how a zero setting means that a packet with any Destination or Hop-by-Hop options is dropped. Report the defaults for max_dst_opts_number and max_hbh_opts_number are 2 which means up to two options may be accepted. Signed-off-by: Tom Herbert <tom@herbertland.com> --- Documentation/networking/ip-sysctl.rst | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index bc9a01606daf..5051fe653c96 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -2474,20 +2474,26 @@ mld_qrv - INTEGER Minimum: 1 (as specified by RFC6636 4.5) max_dst_opts_number - INTEGER - Maximum number of non-padding TLVs allowed in a Destination - options extension header. If this value is less than zero - then unknown options are disallowed and the number of known - TLVs allowed is the absolute value of this number. + Maximum number of non-padding TLVs allowed in a Destination + options extension header. If this value is zero then receive + Destination Options processing is disabled in which case packets + with the Destination Options extension header are dropped. If + this value is less than zero then unknown options are disallowed + and the number of known TLVs allowed is the absolute value of + this number. - Default: 8 + Default: 2 max_hbh_opts_number - INTEGER Maximum number of non-padding TLVs allowed in a Hop-by-Hop - options extension header. If this value is less than zero - then unknown options are disallowed and the number of known - TLVs allowed is the absolute value of this number. - - Default: 8 + options extension header. If this value is zero then receive + Hop-by-Hop Options processing is disabled in which case packets + with the Hop-by-Hop Options extension header are dropped. + If this value is less than zero then unknown options are disallowed + and the number of known TLVs allowed is the absolute value of this + number. + + Default: 2 max_dst_opts_length - INTEGER Maximum length allowed for a Destination options extension -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert ` (4 preceding siblings ...) 2026-01-21 21:49 ` [PATCH net-next v4 5/7] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 2026-01-23 21:35 ` Justin Iurman 2026-01-21 21:49 ` [PATCH net-next v4 7/7] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert 6 siblings, 1 reply; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert RFC8200 highly recommends that different Extension Headers be send in a prescibed order and all Extension Header types occur at most once in a packet with the expection of Destination Options that may occur twice. This patch enforces the ordering be folowed in received packets. It also disregrards Destination Options before the Routing Header as those are unused in deployment asnd there is a proposal to deprecate them in draft-herbert-deprecate-destops-before-rh. The allowed order of Extension Headers is: IPv6 header Hop-by-Hop Options header Routing header Fragment header Authentication header Encapsulating Security Payload header Destination Options header Upper-Layer header Each Extension Header may be present only once in a packet. net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable enforcement of xtension Header order. If it is set to zero then Extension Header order and number of occurences is not checked in receive processeing (except for Hop-by-Hop Options that must be the first Extension Header and can only occur once in a packet. Signed-off-by: Tom Herbert <tom@herbertland.com> --- include/net/netns/ipv6.h | 1 + include/net/protocol.h | 16 ++++++++++++++++ net/ipv6/af_inet6.c | 1 + net/ipv6/exthdrs.c | 2 ++ net/ipv6/ip6_input.c | 14 ++++++++++++++ net/ipv6/reassembly.c | 1 + net/ipv6/sysctl_net_ipv6.c | 7 +++++++ net/ipv6/xfrm6_protocol.c | 2 ++ 8 files changed, 44 insertions(+) diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h index 34bdb1308e8f..2db56718ea60 100644 --- a/include/net/netns/ipv6.h +++ b/include/net/netns/ipv6.h @@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 { u8 fib_notify_on_flag_change; u8 icmpv6_error_anycast_as_unicast; u8 icmpv6_errors_extension_mask; + u8 enforce_ext_hdr_order; }; struct netns_ipv6 { diff --git a/include/net/protocol.h b/include/net/protocol.h index b2499f88f8f8..70cc2d0fdc0c 100644 --- a/include/net/protocol.h +++ b/include/net/protocol.h @@ -50,6 +50,21 @@ struct net_protocol { }; #if IS_ENABLED(CONFIG_IPV6) + +/* Order of extension headers as prescribed in RFC8200. The ordering and + * number of extension headers in a packet can be enforced in IPv6 receive + * processing. Destination Options before the Routing Header is not included + * in the list as they are unused in deployment and it has been proposed that + * they be deprecated. See: + * www.ietf.org/archive/id/draft-herbert-deprecate-destops-before-rh-01.txt + */ +#define IPV6_EXT_HDR_ORDER_HOP BIT(0) +#define IPV6_EXT_HDR_ORDER_ROUTING BIT(1) +#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(2) +#define IPV6_EXT_HDR_ORDER_AUTH BIT(3) +#define IPV6_EXT_HDR_ORDER_ESP BIT(4) +#define IPV6_EXT_HDR_ORDER_DEST BIT(5) + struct inet6_protocol { int (*handler)(struct sk_buff *skb); @@ -61,6 +76,7 @@ struct inet6_protocol { unsigned int flags; /* INET6_PROTO_xxx */ u32 secret; + u32 ext_hdr_order; }; #define INET6_PROTO_NOPOLICY 0x1 diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index bd29840659f3..43097360ce64 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -980,6 +980,7 @@ static int __net_init inet6_net_init(struct net *net) 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; + net->ipv6.sysctl.enforce_ext_hdr_order = 1; atomic_set(&net->ipv6.fib6_sernum, 1); net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 394e3397e4d4..2af16694db46 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) static const struct inet6_protocol rthdr_protocol = { .handler = ipv6_rthdr_rcv, .flags = INET6_PROTO_NOPOLICY, + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING, }; static const struct inet6_protocol destopt_protocol = { .handler = ipv6_destopt_rcv, .flags = INET6_PROTO_NOPOLICY, + .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST, }; static const struct inet6_protocol nodata_protocol = { diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c index 168ec07e31cc..5254ead5c226 100644 --- a/net/ipv6/ip6_input.c +++ b/net/ipv6/ip6_input.c @@ -366,6 +366,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, const struct inet6_protocol *ipprot; struct inet6_dev *idev; unsigned int nhoff; + u32 ext_hdrs = 0; SKB_DR(reason); bool raw; @@ -427,6 +428,19 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, goto discard; } } + + if (ipprot->ext_hdr_order && + READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) { + /* The protocol is an extension header and EH ordering + * is being enforced. Discard packet if we've already + * seen this EH or one that is lower in the order list + */ + if (ipprot->ext_hdr_order <= ext_hdrs) + goto discard; + + ext_hdrs |= ipprot->ext_hdr_order; + } + if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) { if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { SKB_DR_SET(reason, XFRM_POLICY); diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c index 25ec8001898d..91dba72c5a3c 100644 --- a/net/ipv6/reassembly.c +++ b/net/ipv6/reassembly.c @@ -414,6 +414,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) static const struct inet6_protocol frag_protocol = { .handler = ipv6_frag_rcv, .flags = INET6_PROTO_NOPOLICY, + .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT, }; #ifdef CONFIG_SYSCTL diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c index d2cd33e2698d..543b6acdb11d 100644 --- a/net/ipv6/sysctl_net_ipv6.c +++ b/net/ipv6/sysctl_net_ipv6.c @@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = { .proc_handler = proc_doulongvec_minmax, .extra2 = &ioam6_id_wide_max, }, + { + .procname = "enforce_ext_hdr_order", + .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order, + .maxlen = sizeof(u8), + .mode = 0644, + .proc_handler = proc_dou8vec_minmax, + }, }; static struct ctl_table ipv6_rotable[] = { diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c index ea2f805d3b01..5826edf67f64 100644 --- a/net/ipv6/xfrm6_protocol.c +++ b/net/ipv6/xfrm6_protocol.c @@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = { .handler = xfrm6_esp_rcv, .err_handler = xfrm6_esp_err, .flags = INET6_PROTO_NOPOLICY, + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP, }; static const struct inet6_protocol ah6_protocol = { .handler = xfrm6_ah_rcv, .err_handler = xfrm6_ah_err, .flags = INET6_PROTO_NOPOLICY, + .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH }; static const struct inet6_protocol ipcomp6_protocol = { -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering 2026-01-21 21:49 ` [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering Tom Herbert @ 2026-01-23 21:35 ` Justin Iurman 2026-01-26 16:56 ` Tom Herbert 0 siblings, 1 reply; 14+ messages in thread From: Justin Iurman @ 2026-01-23 21:35 UTC (permalink / raw) To: Tom Herbert, davem, kuba, netdev On 1/21/26 22:49, Tom Herbert wrote: > RFC8200 highly recommends that different Extension Headers be send in > a prescibed order and all Extension Header types occur at most once > in a packet with the expection of Destination Options that may > occur twice. This patch enforces the ordering be folowed in received > packets. It also disregrards Destination Options before the Routing > Header as those are unused in deployment asnd there is a proposal > to deprecate them in draft-herbert-deprecate-destops-before-rh. I'll have to pushback on this specifically, at least as long as draft-herbert-deprecate-destops-before-rh is not adopted and published (to be clear, I'm not against it, just that it's still an individual draft right now and there is no consensus). We're already at the edge of remaining compliant with RFC8200 with this series (which is fine considering its security gaps), so let's not rush by removing the DestOps before the Routing header. It can be done later, this series already mitigates DoS attacks quite well. FWIW, the rest of this patch makes sense. > The allowed order of Extension Headers is: > > IPv6 header > Hop-by-Hop Options header > Routing header > Fragment header > Authentication header > Encapsulating Security Payload header > Destination Options header > Upper-Layer header > > Each Extension Header may be present only once in a packet. > > net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable > enforcement of xtension Header order. If it is set to zero then > Extension Header order and number of occurences is not checked > in receive processeing (except for Hop-by-Hop Options that > must be the first Extension Header and can only occur once in > a packet. > > Signed-off-by: Tom Herbert <tom@herbertland.com> > --- > include/net/netns/ipv6.h | 1 + > include/net/protocol.h | 16 ++++++++++++++++ > net/ipv6/af_inet6.c | 1 + > net/ipv6/exthdrs.c | 2 ++ > net/ipv6/ip6_input.c | 14 ++++++++++++++ > net/ipv6/reassembly.c | 1 + > net/ipv6/sysctl_net_ipv6.c | 7 +++++++ > net/ipv6/xfrm6_protocol.c | 2 ++ > 8 files changed, 44 insertions(+) > > diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h > index 34bdb1308e8f..2db56718ea60 100644 > --- a/include/net/netns/ipv6.h > +++ b/include/net/netns/ipv6.h > @@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 { > u8 fib_notify_on_flag_change; > u8 icmpv6_error_anycast_as_unicast; > u8 icmpv6_errors_extension_mask; > + u8 enforce_ext_hdr_order; > }; > > struct netns_ipv6 { > diff --git a/include/net/protocol.h b/include/net/protocol.h > index b2499f88f8f8..70cc2d0fdc0c 100644 > --- a/include/net/protocol.h > +++ b/include/net/protocol.h > @@ -50,6 +50,21 @@ struct net_protocol { > }; > > #if IS_ENABLED(CONFIG_IPV6) > + > +/* Order of extension headers as prescribed in RFC8200. The ordering and > + * number of extension headers in a packet can be enforced in IPv6 receive > + * processing. Destination Options before the Routing Header is not included > + * in the list as they are unused in deployment and it has been proposed that > + * they be deprecated. See: > + * www.ietf.org/archive/id/draft-herbert-deprecate-destops-before-rh-01.txt > + */ > +#define IPV6_EXT_HDR_ORDER_HOP BIT(0) > +#define IPV6_EXT_HDR_ORDER_ROUTING BIT(1) > +#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(2) > +#define IPV6_EXT_HDR_ORDER_AUTH BIT(3) > +#define IPV6_EXT_HDR_ORDER_ESP BIT(4) > +#define IPV6_EXT_HDR_ORDER_DEST BIT(5) > + > struct inet6_protocol { > int (*handler)(struct sk_buff *skb); > > @@ -61,6 +76,7 @@ struct inet6_protocol { > > unsigned int flags; /* INET6_PROTO_xxx */ > u32 secret; > + u32 ext_hdr_order; > }; > > #define INET6_PROTO_NOPOLICY 0x1 > diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c > index bd29840659f3..43097360ce64 100644 > --- a/net/ipv6/af_inet6.c > +++ b/net/ipv6/af_inet6.c > @@ -980,6 +980,7 @@ static int __net_init inet6_net_init(struct net *net) > 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; > + net->ipv6.sysctl.enforce_ext_hdr_order = 1; > atomic_set(&net->ipv6.fib6_sernum, 1); > > net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; > diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c > index 394e3397e4d4..2af16694db46 100644 > --- a/net/ipv6/exthdrs.c > +++ b/net/ipv6/exthdrs.c > @@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) > static const struct inet6_protocol rthdr_protocol = { > .handler = ipv6_rthdr_rcv, > .flags = INET6_PROTO_NOPOLICY, > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING, > }; > > static const struct inet6_protocol destopt_protocol = { > .handler = ipv6_destopt_rcv, > .flags = INET6_PROTO_NOPOLICY, > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST, > }; > > static const struct inet6_protocol nodata_protocol = { > diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c > index 168ec07e31cc..5254ead5c226 100644 > --- a/net/ipv6/ip6_input.c > +++ b/net/ipv6/ip6_input.c > @@ -366,6 +366,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > const struct inet6_protocol *ipprot; > struct inet6_dev *idev; > unsigned int nhoff; > + u32 ext_hdrs = 0; > SKB_DR(reason); > bool raw; > > @@ -427,6 +428,19 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > goto discard; > } > } > + > + if (ipprot->ext_hdr_order && > + READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) { > + /* The protocol is an extension header and EH ordering > + * is being enforced. Discard packet if we've already > + * seen this EH or one that is lower in the order list > + */ > + if (ipprot->ext_hdr_order <= ext_hdrs) > + goto discard; > + > + ext_hdrs |= ipprot->ext_hdr_order; > + } > + > if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) { > if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { > SKB_DR_SET(reason, XFRM_POLICY); > diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c > index 25ec8001898d..91dba72c5a3c 100644 > --- a/net/ipv6/reassembly.c > +++ b/net/ipv6/reassembly.c > @@ -414,6 +414,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) > static const struct inet6_protocol frag_protocol = { > .handler = ipv6_frag_rcv, > .flags = INET6_PROTO_NOPOLICY, > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT, > }; > > #ifdef CONFIG_SYSCTL > diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c > index d2cd33e2698d..543b6acdb11d 100644 > --- a/net/ipv6/sysctl_net_ipv6.c > +++ b/net/ipv6/sysctl_net_ipv6.c > @@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = { > .proc_handler = proc_doulongvec_minmax, > .extra2 = &ioam6_id_wide_max, > }, > + { > + .procname = "enforce_ext_hdr_order", > + .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order, > + .maxlen = sizeof(u8), > + .mode = 0644, > + .proc_handler = proc_dou8vec_minmax, > + }, > }; > > static struct ctl_table ipv6_rotable[] = { > diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c > index ea2f805d3b01..5826edf67f64 100644 > --- a/net/ipv6/xfrm6_protocol.c > +++ b/net/ipv6/xfrm6_protocol.c > @@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = { > .handler = xfrm6_esp_rcv, > .err_handler = xfrm6_esp_err, > .flags = INET6_PROTO_NOPOLICY, > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP, > }; > > static const struct inet6_protocol ah6_protocol = { > .handler = xfrm6_ah_rcv, > .err_handler = xfrm6_ah_err, > .flags = INET6_PROTO_NOPOLICY, > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH > }; > > static const struct inet6_protocol ipcomp6_protocol = { ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering 2026-01-23 21:35 ` Justin Iurman @ 2026-01-26 16:56 ` Tom Herbert 2026-01-26 19:23 ` Justin Iurman 0 siblings, 1 reply; 14+ messages in thread From: Tom Herbert @ 2026-01-26 16:56 UTC (permalink / raw) To: Justin Iurman; +Cc: davem, kuba, netdev On Fri, Jan 23, 2026 at 1:35 PM Justin Iurman <justin.iurman@gmail.com> wrote: > > On 1/21/26 22:49, Tom Herbert wrote: > > RFC8200 highly recommends that different Extension Headers be send in > > a prescibed order and all Extension Header types occur at most once > > in a packet with the expection of Destination Options that may > > occur twice. This patch enforces the ordering be folowed in received > > packets. It also disregrards Destination Options before the Routing > > Header as those are unused in deployment asnd there is a proposal > > to deprecate them in draft-herbert-deprecate-destops-before-rh. > > I'll have to pushback on this specifically, at least as long as > draft-herbert-deprecate-destops-before-rh is not adopted and published > (to be clear, I'm not against it, just that it's still an individual > draft right now and there is no consensus). We're already at the edge of > remaining compliant with RFC8200 with this series (which is fine > considering its security gaps), so let's not rush by removing the > DestOps before the Routing header. It can be done later, this series > already mitigates DoS attacks quite well. Justin, If we need to wait for permission from IETF then pretty much most of this patch set isn't viable. For instance draft-iurman-6man-eh-occurrences is also not adopted and published either, and if we enforce the number of occurrences in implementation then we are non-conformant with RFC8200. But I think it is justified to protect users from an obvious DoS attack. As for Destination Options before the Routing header, _no_one_ is using this. There is no deployment of that. The only routing header being deployed is SRv6 and the header has its own options, no routers support Destination Options before the Routing Header. But, if by some miracle someone is using them in their network, then they can simply set the sysctl to not enforce EH ordering. IMO, this approach is better than adding additional complexity to the stack just to detect a case that nobody cares about and a code path that is never exercised. Tom > > FWIW, the rest of this patch makes sense. > > > The allowed order of Extension Headers is: > > > > IPv6 header > > Hop-by-Hop Options header > > Routing header > > Fragment header > > Authentication header > > Encapsulating Security Payload header > > Destination Options header > > Upper-Layer header > > > > Each Extension Header may be present only once in a packet. > > > > net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable > > enforcement of xtension Header order. If it is set to zero then > > Extension Header order and number of occurences is not checked > > in receive processeing (except for Hop-by-Hop Options that > > must be the first Extension Header and can only occur once in > > a packet. > > > > Signed-off-by: Tom Herbert <tom@herbertland.com> > > --- > > include/net/netns/ipv6.h | 1 + > > include/net/protocol.h | 16 ++++++++++++++++ > > net/ipv6/af_inet6.c | 1 + > > net/ipv6/exthdrs.c | 2 ++ > > net/ipv6/ip6_input.c | 14 ++++++++++++++ > > net/ipv6/reassembly.c | 1 + > > net/ipv6/sysctl_net_ipv6.c | 7 +++++++ > > net/ipv6/xfrm6_protocol.c | 2 ++ > > 8 files changed, 44 insertions(+) > > > > diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h > > index 34bdb1308e8f..2db56718ea60 100644 > > --- a/include/net/netns/ipv6.h > > +++ b/include/net/netns/ipv6.h > > @@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 { > > u8 fib_notify_on_flag_change; > > u8 icmpv6_error_anycast_as_unicast; > > u8 icmpv6_errors_extension_mask; > > + u8 enforce_ext_hdr_order; > > }; > > > > struct netns_ipv6 { > > diff --git a/include/net/protocol.h b/include/net/protocol.h > > index b2499f88f8f8..70cc2d0fdc0c 100644 > > --- a/include/net/protocol.h > > +++ b/include/net/protocol.h > > @@ -50,6 +50,21 @@ struct net_protocol { > > }; > > > > #if IS_ENABLED(CONFIG_IPV6) > > + > > +/* Order of extension headers as prescribed in RFC8200. The ordering and > > + * number of extension headers in a packet can be enforced in IPv6 receive > > + * processing. Destination Options before the Routing Header is not included > > + * in the list as they are unused in deployment and it has been proposed that > > + * they be deprecated. See: > > + * www.ietf.org/archive/id/draft-herbert-deprecate-destops-before-rh-01.txt > > + */ > > +#define IPV6_EXT_HDR_ORDER_HOP BIT(0) > > +#define IPV6_EXT_HDR_ORDER_ROUTING BIT(1) > > +#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(2) > > +#define IPV6_EXT_HDR_ORDER_AUTH BIT(3) > > +#define IPV6_EXT_HDR_ORDER_ESP BIT(4) > > +#define IPV6_EXT_HDR_ORDER_DEST BIT(5) > > + > > struct inet6_protocol { > > int (*handler)(struct sk_buff *skb); > > > > @@ -61,6 +76,7 @@ struct inet6_protocol { > > > > unsigned int flags; /* INET6_PROTO_xxx */ > > u32 secret; > > + u32 ext_hdr_order; > > }; > > > > #define INET6_PROTO_NOPOLICY 0x1 > > diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c > > index bd29840659f3..43097360ce64 100644 > > --- a/net/ipv6/af_inet6.c > > +++ b/net/ipv6/af_inet6.c > > @@ -980,6 +980,7 @@ static int __net_init inet6_net_init(struct net *net) > > 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; > > + net->ipv6.sysctl.enforce_ext_hdr_order = 1; > > atomic_set(&net->ipv6.fib6_sernum, 1); > > > > net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; > > diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c > > index 394e3397e4d4..2af16694db46 100644 > > --- a/net/ipv6/exthdrs.c > > +++ b/net/ipv6/exthdrs.c > > @@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) > > static const struct inet6_protocol rthdr_protocol = { > > .handler = ipv6_rthdr_rcv, > > .flags = INET6_PROTO_NOPOLICY, > > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING, > > }; > > > > static const struct inet6_protocol destopt_protocol = { > > .handler = ipv6_destopt_rcv, > > .flags = INET6_PROTO_NOPOLICY, > > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST, > > }; > > > > static const struct inet6_protocol nodata_protocol = { > > diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c > > index 168ec07e31cc..5254ead5c226 100644 > > --- a/net/ipv6/ip6_input.c > > +++ b/net/ipv6/ip6_input.c > > @@ -366,6 +366,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > > const struct inet6_protocol *ipprot; > > struct inet6_dev *idev; > > unsigned int nhoff; > > + u32 ext_hdrs = 0; > > SKB_DR(reason); > > bool raw; > > > > @@ -427,6 +428,19 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > > goto discard; > > } > > } > > + > > + if (ipprot->ext_hdr_order && > > + READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) { > > + /* The protocol is an extension header and EH ordering > > + * is being enforced. Discard packet if we've already > > + * seen this EH or one that is lower in the order list > > + */ > > + if (ipprot->ext_hdr_order <= ext_hdrs) > > + goto discard; > > + > > + ext_hdrs |= ipprot->ext_hdr_order; > > + } > > + > > if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) { > > if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { > > SKB_DR_SET(reason, XFRM_POLICY); > > diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c > > index 25ec8001898d..91dba72c5a3c 100644 > > --- a/net/ipv6/reassembly.c > > +++ b/net/ipv6/reassembly.c > > @@ -414,6 +414,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) > > static const struct inet6_protocol frag_protocol = { > > .handler = ipv6_frag_rcv, > > .flags = INET6_PROTO_NOPOLICY, > > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT, > > }; > > > > #ifdef CONFIG_SYSCTL > > diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c > > index d2cd33e2698d..543b6acdb11d 100644 > > --- a/net/ipv6/sysctl_net_ipv6.c > > +++ b/net/ipv6/sysctl_net_ipv6.c > > @@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = { > > .proc_handler = proc_doulongvec_minmax, > > .extra2 = &ioam6_id_wide_max, > > }, > > + { > > + .procname = "enforce_ext_hdr_order", > > + .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order, > > + .maxlen = sizeof(u8), > > + .mode = 0644, > > + .proc_handler = proc_dou8vec_minmax, > > + }, > > }; > > > > static struct ctl_table ipv6_rotable[] = { > > diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c > > index ea2f805d3b01..5826edf67f64 100644 > > --- a/net/ipv6/xfrm6_protocol.c > > +++ b/net/ipv6/xfrm6_protocol.c > > @@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = { > > .handler = xfrm6_esp_rcv, > > .err_handler = xfrm6_esp_err, > > .flags = INET6_PROTO_NOPOLICY, > > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP, > > }; > > > > static const struct inet6_protocol ah6_protocol = { > > .handler = xfrm6_ah_rcv, > > .err_handler = xfrm6_ah_err, > > .flags = INET6_PROTO_NOPOLICY, > > + .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH > > }; > > > > static const struct inet6_protocol ipcomp6_protocol = { > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering 2026-01-26 16:56 ` Tom Herbert @ 2026-01-26 19:23 ` Justin Iurman 2026-01-26 19:38 ` Tom Herbert 0 siblings, 1 reply; 14+ messages in thread From: Justin Iurman @ 2026-01-26 19:23 UTC (permalink / raw) To: Tom Herbert; +Cc: davem, kuba, netdev On 1/26/26 17:56, Tom Herbert wrote: > On Fri, Jan 23, 2026 at 1:35 PM Justin Iurman <justin.iurman@gmail.com> wrote: >> >> On 1/21/26 22:49, Tom Herbert wrote: >>> RFC8200 highly recommends that different Extension Headers be send in >>> a prescibed order and all Extension Header types occur at most once >>> in a packet with the expection of Destination Options that may >>> occur twice. This patch enforces the ordering be folowed in received >>> packets. It also disregrards Destination Options before the Routing >>> Header as those are unused in deployment asnd there is a proposal >>> to deprecate them in draft-herbert-deprecate-destops-before-rh. >> >> I'll have to pushback on this specifically, at least as long as >> draft-herbert-deprecate-destops-before-rh is not adopted and published >> (to be clear, I'm not against it, just that it's still an individual >> draft right now and there is no consensus). We're already at the edge of >> remaining compliant with RFC8200 with this series (which is fine >> considering its security gaps), so let's not rush by removing the >> DestOps before the Routing header. It can be done later, this series >> already mitigates DoS attacks quite well. > > Justin, > > If we need to wait for permission from IETF then pretty much most of > this patch set isn't viable. For instance > draft-iurman-6man-eh-occurrences is also not adopted and published > either, and if we enforce the number of occurrences in implementation > then we are non-conformant with RFC8200. But I think it is justified > to protect users from an obvious DoS attack. Hi Tom, This is why I mentioned that, with this series, we're on the edge regarding RFC8200 compliance. However, the text in Sec. 4.6 is somewhat ambiguous and does not use normative language. Therefore, this series does not actually violate RFC8200 compliance. On the other hand, removing the DestOpt before RH would completely violate RFC8200 and, IMO, is not necessary at the moment (because this series already mitigates quite well DoS attacks). > As for Destination Options before the Routing header, _no_one_ is > using this. There is no deployment of that. The only routing header > being deployed is SRv6 and the header has its own options, no routers > support Destination Options before the Routing Header. But, if by some > miracle someone is using them in their network, then they can simply > set the sysctl to not enforce EH ordering. IMO, this approach is > better than adding additional complexity to the stack just to detect a > case that nobody cares about and a code path that is never exercised. What if I want to enforce EH ordering in my network *AND* use the DestOpt before RH? I don't think we should make it an exclusive condition. And, as discussed offline, draft-bonica-6man-crh-helper-opt defines a DestOpt before RH. Therefore, we have at least one use case (still a draft, though) in limited domains. Let's wait and see if this draft progresses, or if draft-herbert-deprecate-destops-before-rh gets consensus. Justin > Tom > >> >> FWIW, the rest of this patch makes sense. >> >>> The allowed order of Extension Headers is: >>> >>> IPv6 header >>> Hop-by-Hop Options header >>> Routing header >>> Fragment header >>> Authentication header >>> Encapsulating Security Payload header >>> Destination Options header >>> Upper-Layer header >>> >>> Each Extension Header may be present only once in a packet. >>> >>> net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable >>> enforcement of xtension Header order. If it is set to zero then >>> Extension Header order and number of occurences is not checked >>> in receive processeing (except for Hop-by-Hop Options that >>> must be the first Extension Header and can only occur once in >>> a packet. >>> >>> Signed-off-by: Tom Herbert <tom@herbertland.com> >>> --- >>> include/net/netns/ipv6.h | 1 + >>> include/net/protocol.h | 16 ++++++++++++++++ >>> net/ipv6/af_inet6.c | 1 + >>> net/ipv6/exthdrs.c | 2 ++ >>> net/ipv6/ip6_input.c | 14 ++++++++++++++ >>> net/ipv6/reassembly.c | 1 + >>> net/ipv6/sysctl_net_ipv6.c | 7 +++++++ >>> net/ipv6/xfrm6_protocol.c | 2 ++ >>> 8 files changed, 44 insertions(+) >>> >>> diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h >>> index 34bdb1308e8f..2db56718ea60 100644 >>> --- a/include/net/netns/ipv6.h >>> +++ b/include/net/netns/ipv6.h >>> @@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 { >>> u8 fib_notify_on_flag_change; >>> u8 icmpv6_error_anycast_as_unicast; >>> u8 icmpv6_errors_extension_mask; >>> + u8 enforce_ext_hdr_order; >>> }; >>> >>> struct netns_ipv6 { >>> diff --git a/include/net/protocol.h b/include/net/protocol.h >>> index b2499f88f8f8..70cc2d0fdc0c 100644 >>> --- a/include/net/protocol.h >>> +++ b/include/net/protocol.h >>> @@ -50,6 +50,21 @@ struct net_protocol { >>> }; >>> >>> #if IS_ENABLED(CONFIG_IPV6) >>> + >>> +/* Order of extension headers as prescribed in RFC8200. The ordering and >>> + * number of extension headers in a packet can be enforced in IPv6 receive >>> + * processing. Destination Options before the Routing Header is not included >>> + * in the list as they are unused in deployment and it has been proposed that >>> + * they be deprecated. See: >>> + * www.ietf.org/archive/id/draft-herbert-deprecate-destops-before-rh-01.txt >>> + */ >>> +#define IPV6_EXT_HDR_ORDER_HOP BIT(0) >>> +#define IPV6_EXT_HDR_ORDER_ROUTING BIT(1) >>> +#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(2) >>> +#define IPV6_EXT_HDR_ORDER_AUTH BIT(3) >>> +#define IPV6_EXT_HDR_ORDER_ESP BIT(4) >>> +#define IPV6_EXT_HDR_ORDER_DEST BIT(5) >>> + >>> struct inet6_protocol { >>> int (*handler)(struct sk_buff *skb); >>> >>> @@ -61,6 +76,7 @@ struct inet6_protocol { >>> >>> unsigned int flags; /* INET6_PROTO_xxx */ >>> u32 secret; >>> + u32 ext_hdr_order; >>> }; >>> >>> #define INET6_PROTO_NOPOLICY 0x1 >>> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c >>> index bd29840659f3..43097360ce64 100644 >>> --- a/net/ipv6/af_inet6.c >>> +++ b/net/ipv6/af_inet6.c >>> @@ -980,6 +980,7 @@ static int __net_init inet6_net_init(struct net *net) >>> 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; >>> + net->ipv6.sysctl.enforce_ext_hdr_order = 1; >>> atomic_set(&net->ipv6.fib6_sernum, 1); >>> >>> net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; >>> diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c >>> index 394e3397e4d4..2af16694db46 100644 >>> --- a/net/ipv6/exthdrs.c >>> +++ b/net/ipv6/exthdrs.c >>> @@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) >>> static const struct inet6_protocol rthdr_protocol = { >>> .handler = ipv6_rthdr_rcv, >>> .flags = INET6_PROTO_NOPOLICY, >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING, >>> }; >>> >>> static const struct inet6_protocol destopt_protocol = { >>> .handler = ipv6_destopt_rcv, >>> .flags = INET6_PROTO_NOPOLICY, >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST, >>> }; >>> >>> static const struct inet6_protocol nodata_protocol = { >>> diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c >>> index 168ec07e31cc..5254ead5c226 100644 >>> --- a/net/ipv6/ip6_input.c >>> +++ b/net/ipv6/ip6_input.c >>> @@ -366,6 +366,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, >>> const struct inet6_protocol *ipprot; >>> struct inet6_dev *idev; >>> unsigned int nhoff; >>> + u32 ext_hdrs = 0; >>> SKB_DR(reason); >>> bool raw; >>> >>> @@ -427,6 +428,19 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, >>> goto discard; >>> } >>> } >>> + >>> + if (ipprot->ext_hdr_order && >>> + READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) { >>> + /* The protocol is an extension header and EH ordering >>> + * is being enforced. Discard packet if we've already >>> + * seen this EH or one that is lower in the order list >>> + */ >>> + if (ipprot->ext_hdr_order <= ext_hdrs) >>> + goto discard; >>> + >>> + ext_hdrs |= ipprot->ext_hdr_order; >>> + } >>> + >>> if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) { >>> if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { >>> SKB_DR_SET(reason, XFRM_POLICY); >>> diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c >>> index 25ec8001898d..91dba72c5a3c 100644 >>> --- a/net/ipv6/reassembly.c >>> +++ b/net/ipv6/reassembly.c >>> @@ -414,6 +414,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) >>> static const struct inet6_protocol frag_protocol = { >>> .handler = ipv6_frag_rcv, >>> .flags = INET6_PROTO_NOPOLICY, >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT, >>> }; >>> >>> #ifdef CONFIG_SYSCTL >>> diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c >>> index d2cd33e2698d..543b6acdb11d 100644 >>> --- a/net/ipv6/sysctl_net_ipv6.c >>> +++ b/net/ipv6/sysctl_net_ipv6.c >>> @@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = { >>> .proc_handler = proc_doulongvec_minmax, >>> .extra2 = &ioam6_id_wide_max, >>> }, >>> + { >>> + .procname = "enforce_ext_hdr_order", >>> + .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order, >>> + .maxlen = sizeof(u8), >>> + .mode = 0644, >>> + .proc_handler = proc_dou8vec_minmax, >>> + }, >>> }; >>> >>> static struct ctl_table ipv6_rotable[] = { >>> diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c >>> index ea2f805d3b01..5826edf67f64 100644 >>> --- a/net/ipv6/xfrm6_protocol.c >>> +++ b/net/ipv6/xfrm6_protocol.c >>> @@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = { >>> .handler = xfrm6_esp_rcv, >>> .err_handler = xfrm6_esp_err, >>> .flags = INET6_PROTO_NOPOLICY, >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP, >>> }; >>> >>> static const struct inet6_protocol ah6_protocol = { >>> .handler = xfrm6_ah_rcv, >>> .err_handler = xfrm6_ah_err, >>> .flags = INET6_PROTO_NOPOLICY, >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH >>> }; >>> >>> static const struct inet6_protocol ipcomp6_protocol = { >> ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering 2026-01-26 19:23 ` Justin Iurman @ 2026-01-26 19:38 ` Tom Herbert 0 siblings, 0 replies; 14+ messages in thread From: Tom Herbert @ 2026-01-26 19:38 UTC (permalink / raw) To: Justin Iurman; +Cc: davem, kuba, netdev On Mon, Jan 26, 2026 at 11:23 AM Justin Iurman <justin.iurman@gmail.com> wrote: > > On 1/26/26 17:56, Tom Herbert wrote: > > On Fri, Jan 23, 2026 at 1:35 PM Justin Iurman <justin.iurman@gmail.com> wrote: > >> > >> On 1/21/26 22:49, Tom Herbert wrote: > >>> RFC8200 highly recommends that different Extension Headers be send in > >>> a prescibed order and all Extension Header types occur at most once > >>> in a packet with the expection of Destination Options that may > >>> occur twice. This patch enforces the ordering be folowed in received > >>> packets. It also disregrards Destination Options before the Routing > >>> Header as those are unused in deployment asnd there is a proposal > >>> to deprecate them in draft-herbert-deprecate-destops-before-rh. > >> > >> I'll have to pushback on this specifically, at least as long as > >> draft-herbert-deprecate-destops-before-rh is not adopted and published > >> (to be clear, I'm not against it, just that it's still an individual > >> draft right now and there is no consensus). We're already at the edge of > >> remaining compliant with RFC8200 with this series (which is fine > >> considering its security gaps), so let's not rush by removing the > >> DestOps before the Routing header. It can be done later, this series > >> already mitigates DoS attacks quite well. > > > > Justin, > > > > If we need to wait for permission from IETF then pretty much most of > > this patch set isn't viable. For instance > > draft-iurman-6man-eh-occurrences is also not adopted and published > > either, and if we enforce the number of occurrences in implementation > > then we are non-conformant with RFC8200. But I think it is justified > > to protect users from an obvious DoS attack. > > Hi Tom, > > This is why I mentioned that, with this series, we're on the edge > regarding RFC8200 compliance. However, the text in Sec. 4.6 is somewhat > ambiguous and does not use normative language. Therefore, this series > does not actually violate RFC8200 compliance. > > On the other hand, removing the DestOpt before RH would completely > violate RFC8200 and, IMO, is not necessary at the moment (because this > series already mitigates quite well DoS attacks). > > > As for Destination Options before the Routing header, _no_one_ is > > using this. There is no deployment of that. The only routing header > > being deployed is SRv6 and the header has its own options, no routers > > support Destination Options before the Routing Header. But, if by some > > miracle someone is using them in their network, then they can simply > > set the sysctl to not enforce EH ordering. IMO, this approach is > > better than adding additional complexity to the stack just to detect a > > case that nobody cares about and a code path that is never exercised. > > What if I want to enforce EH ordering in my network *AND* use the > DestOpt before RH? I don't think we should make it an exclusive > condition. And, as discussed offline, draft-bonica-6man-crh-helper-opt > defines a DestOpt before RH. Therefore, we have at least one use case > (still a draft, though) in limited domains. Let's wait and see if this > draft progresses, or if draft-herbert-deprecate-destops-before-rh gets > consensus. Okay, I'll submit a new patch set shortly. Tom > > Justin > > > Tom > > > >> > >> FWIW, the rest of this patch makes sense. > >> > >>> The allowed order of Extension Headers is: > >>> > >>> IPv6 header > >>> Hop-by-Hop Options header > >>> Routing header > >>> Fragment header > >>> Authentication header > >>> Encapsulating Security Payload header > >>> Destination Options header > >>> Upper-Layer header > >>> > >>> Each Extension Header may be present only once in a packet. > >>> > >>> net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable > >>> enforcement of xtension Header order. If it is set to zero then > >>> Extension Header order and number of occurences is not checked > >>> in receive processeing (except for Hop-by-Hop Options that > >>> must be the first Extension Header and can only occur once in > >>> a packet. > >>> > >>> Signed-off-by: Tom Herbert <tom@herbertland.com> > >>> --- > >>> include/net/netns/ipv6.h | 1 + > >>> include/net/protocol.h | 16 ++++++++++++++++ > >>> net/ipv6/af_inet6.c | 1 + > >>> net/ipv6/exthdrs.c | 2 ++ > >>> net/ipv6/ip6_input.c | 14 ++++++++++++++ > >>> net/ipv6/reassembly.c | 1 + > >>> net/ipv6/sysctl_net_ipv6.c | 7 +++++++ > >>> net/ipv6/xfrm6_protocol.c | 2 ++ > >>> 8 files changed, 44 insertions(+) > >>> > >>> diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h > >>> index 34bdb1308e8f..2db56718ea60 100644 > >>> --- a/include/net/netns/ipv6.h > >>> +++ b/include/net/netns/ipv6.h > >>> @@ -61,6 +61,7 @@ struct netns_sysctl_ipv6 { > >>> u8 fib_notify_on_flag_change; > >>> u8 icmpv6_error_anycast_as_unicast; > >>> u8 icmpv6_errors_extension_mask; > >>> + u8 enforce_ext_hdr_order; > >>> }; > >>> > >>> struct netns_ipv6 { > >>> diff --git a/include/net/protocol.h b/include/net/protocol.h > >>> index b2499f88f8f8..70cc2d0fdc0c 100644 > >>> --- a/include/net/protocol.h > >>> +++ b/include/net/protocol.h > >>> @@ -50,6 +50,21 @@ struct net_protocol { > >>> }; > >>> > >>> #if IS_ENABLED(CONFIG_IPV6) > >>> + > >>> +/* Order of extension headers as prescribed in RFC8200. The ordering and > >>> + * number of extension headers in a packet can be enforced in IPv6 receive > >>> + * processing. Destination Options before the Routing Header is not included > >>> + * in the list as they are unused in deployment and it has been proposed that > >>> + * they be deprecated. See: > >>> + * www.ietf.org/archive/id/draft-herbert-deprecate-destops-before-rh-01.txt > >>> + */ > >>> +#define IPV6_EXT_HDR_ORDER_HOP BIT(0) > >>> +#define IPV6_EXT_HDR_ORDER_ROUTING BIT(1) > >>> +#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(2) > >>> +#define IPV6_EXT_HDR_ORDER_AUTH BIT(3) > >>> +#define IPV6_EXT_HDR_ORDER_ESP BIT(4) > >>> +#define IPV6_EXT_HDR_ORDER_DEST BIT(5) > >>> + > >>> struct inet6_protocol { > >>> int (*handler)(struct sk_buff *skb); > >>> > >>> @@ -61,6 +76,7 @@ struct inet6_protocol { > >>> > >>> unsigned int flags; /* INET6_PROTO_xxx */ > >>> u32 secret; > >>> + u32 ext_hdr_order; > >>> }; > >>> > >>> #define INET6_PROTO_NOPOLICY 0x1 > >>> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c > >>> index bd29840659f3..43097360ce64 100644 > >>> --- a/net/ipv6/af_inet6.c > >>> +++ b/net/ipv6/af_inet6.c > >>> @@ -980,6 +980,7 @@ static int __net_init inet6_net_init(struct net *net) > >>> 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; > >>> + net->ipv6.sysctl.enforce_ext_hdr_order = 1; > >>> atomic_set(&net->ipv6.fib6_sernum, 1); > >>> > >>> net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; > >>> diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c > >>> index 394e3397e4d4..2af16694db46 100644 > >>> --- a/net/ipv6/exthdrs.c > >>> +++ b/net/ipv6/exthdrs.c > >>> @@ -845,11 +845,13 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) > >>> static const struct inet6_protocol rthdr_protocol = { > >>> .handler = ipv6_rthdr_rcv, > >>> .flags = INET6_PROTO_NOPOLICY, > >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ROUTING, > >>> }; > >>> > >>> static const struct inet6_protocol destopt_protocol = { > >>> .handler = ipv6_destopt_rcv, > >>> .flags = INET6_PROTO_NOPOLICY, > >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_DEST, > >>> }; > >>> > >>> static const struct inet6_protocol nodata_protocol = { > >>> diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c > >>> index 168ec07e31cc..5254ead5c226 100644 > >>> --- a/net/ipv6/ip6_input.c > >>> +++ b/net/ipv6/ip6_input.c > >>> @@ -366,6 +366,7 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > >>> const struct inet6_protocol *ipprot; > >>> struct inet6_dev *idev; > >>> unsigned int nhoff; > >>> + u32 ext_hdrs = 0; > >>> SKB_DR(reason); > >>> bool raw; > >>> > >>> @@ -427,6 +428,19 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr, > >>> goto discard; > >>> } > >>> } > >>> + > >>> + if (ipprot->ext_hdr_order && > >>> + READ_ONCE(net->ipv6.sysctl.enforce_ext_hdr_order)) { > >>> + /* The protocol is an extension header and EH ordering > >>> + * is being enforced. Discard packet if we've already > >>> + * seen this EH or one that is lower in the order list > >>> + */ > >>> + if (ipprot->ext_hdr_order <= ext_hdrs) > >>> + goto discard; > >>> + > >>> + ext_hdrs |= ipprot->ext_hdr_order; > >>> + } > >>> + > >>> if (!(ipprot->flags & INET6_PROTO_NOPOLICY)) { > >>> if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { > >>> SKB_DR_SET(reason, XFRM_POLICY); > >>> diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c > >>> index 25ec8001898d..91dba72c5a3c 100644 > >>> --- a/net/ipv6/reassembly.c > >>> +++ b/net/ipv6/reassembly.c > >>> @@ -414,6 +414,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) > >>> static const struct inet6_protocol frag_protocol = { > >>> .handler = ipv6_frag_rcv, > >>> .flags = INET6_PROTO_NOPOLICY, > >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_FRAGMENT, > >>> }; > >>> > >>> #ifdef CONFIG_SYSCTL > >>> diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c > >>> index d2cd33e2698d..543b6acdb11d 100644 > >>> --- a/net/ipv6/sysctl_net_ipv6.c > >>> +++ b/net/ipv6/sysctl_net_ipv6.c > >>> @@ -213,6 +213,13 @@ static struct ctl_table ipv6_table_template[] = { > >>> .proc_handler = proc_doulongvec_minmax, > >>> .extra2 = &ioam6_id_wide_max, > >>> }, > >>> + { > >>> + .procname = "enforce_ext_hdr_order", > >>> + .data = &init_net.ipv6.sysctl.enforce_ext_hdr_order, > >>> + .maxlen = sizeof(u8), > >>> + .mode = 0644, > >>> + .proc_handler = proc_dou8vec_minmax, > >>> + }, > >>> }; > >>> > >>> static struct ctl_table ipv6_rotable[] = { > >>> diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c > >>> index ea2f805d3b01..5826edf67f64 100644 > >>> --- a/net/ipv6/xfrm6_protocol.c > >>> +++ b/net/ipv6/xfrm6_protocol.c > >>> @@ -197,12 +197,14 @@ static const struct inet6_protocol esp6_protocol = { > >>> .handler = xfrm6_esp_rcv, > >>> .err_handler = xfrm6_esp_err, > >>> .flags = INET6_PROTO_NOPOLICY, > >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_ESP, > >>> }; > >>> > >>> static const struct inet6_protocol ah6_protocol = { > >>> .handler = xfrm6_ah_rcv, > >>> .err_handler = xfrm6_ah_err, > >>> .flags = INET6_PROTO_NOPOLICY, > >>> + .ext_hdr_order = IPV6_EXT_HDR_ORDER_AUTH > >>> }; > >>> > >>> static const struct inet6_protocol ipcomp6_protocol = { > >> > ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v4 7/7] ipv6: Document enforce_ext_hdr_order sysctl 2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert ` (5 preceding siblings ...) 2026-01-21 21:49 ` [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering Tom Herbert @ 2026-01-21 21:49 ` Tom Herbert 6 siblings, 0 replies; 14+ messages in thread From: Tom Herbert @ 2026-01-21 21:49 UTC (permalink / raw) To: davem, kuba, netdev, justin.iurman; +Cc: Tom Herbert Document the enforce_ext_hdr_order sysctl that controls whether Extension Header order is enforced on receive. Signed-off-by: Tom Herbert <tom@herbertland.com> --- Documentation/networking/ip-sysctl.rst | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 5051fe653c96..f8fa9f67b2ed 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -2581,6 +2581,35 @@ ioam6_id_wide - LONG INTEGER Default: 0xFFFFFFFFFFFFFF +enforce_ext_hdr_order - BOOLEAN + Enforce recommended Extension Header ordering in RFC8200. + If the sysctl is set to 1 then the ordering the ordering is + enforced in received packets and each Extension Header + may be present at most once per packet. If the sysctl is + set to 0 then ordering is not enforced and Extension Headers + may be present in any order and have any number of + occurences per packet (except for Hop-by-Hop Options). Also, + if the sysctl is set then Destination Options before the + Routing header are disllowed. + + The Extension Header order is: + + IPv6 header + Hop-by-Hop Options header + Routing header + Fragment header + Authentication header + Encapsulating Security Payload header + Destination Options header + Upper-Layer header + + Possible values: + + - 0 (disabled) + - 1 (enabled) + + Default: 1 (enabled) + IPv6 Fragmentation: ip6frag_high_thresh - INTEGER -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-01-26 19:38 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-21 21:49 [PATCH net-next v4 0/7] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 1/7] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
2026-01-23 21:16 ` Justin Iurman
2026-01-21 21:49 ` [PATCH net-next v4 2/7] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
2026-01-23 21:19 ` Justin Iurman
2026-01-21 21:49 ` [PATCH net-next v4 3/7] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 4/7] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 5/7] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 6/7] ipv6: Enforce Extension Header ordering Tom Herbert
2026-01-23 21:35 ` Justin Iurman
2026-01-26 16:56 ` Tom Herbert
2026-01-26 19:23 ` Justin Iurman
2026-01-26 19:38 ` Tom Herbert
2026-01-21 21:49 ` [PATCH net-next v4 7/7] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox