* [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities
@ 2026-02-04 22:51 Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
` (9 more replies)
0 siblings, 10 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
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 flexibility
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 except for Destination Options that may appear
twice. 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
Destination Options before the Routing header
Routing header
Fragment header
Authentication header
Encapsulating Security Payload header
Destination Options header
Upper-Layer header
--- Background
We selected the default value of eight in RFC8504 based on an
expectation that there might be new options defined and that the
Internet would be fixed to reliably support extension headers
including those with options. I do not believe either of those are
going to happen.
Hop-by-Hop Options are ostensibly the right way to do network to host
and host to network signaling.The only HBH options that might get any
substantial deployment are Router Alert option and IOAM. The Router
Alert option is being deprecated and IOAM is at best a "nice to
have". The best use case of Hop-by-Hop options is congestion
signaling, unfortunately the die was cast when CSIG authors decided to
place the information in VLANs at L2 and cajole the information to be
routable through a switch. IMO, the miss on CSIG pretty much is the
nail in the coffin for Hop-by-Hop options to ever be widely deployed
(https://www.ietf.org/archive/id/draft-ravi-ippm-csig-01.txt).
Destination Options have proven even less useful than Hop-by-Hop
Options. The only Destination Options supported by the stack is the
Tunnel Encap Limit option and Home Address Options. The Tunnel Encap
Option was buried in the v6 tunnel code which is why it wasn't obvious
it was supported in the first version of the patch set. I'll assume
that might be useful, so this patch set cleans up the code for it. I
don't believe there's any use of Home Address Option.
A major problem with DestOpts, HBH, Routing Header, and Fragment
header is that they have no inherent security. Their use presents a
security risk especially when sent over untrusted networks including
the Internet. Given that and that the high drop rates of extension
headers on the Internet, I am proposing that Extension header except
for ESP being deprecated on the Internet
(https://www.ietf.org/archive/id/draft-herbert-deprecate-eh-01.txt).
--- Testing
Add selftest eh_limits.sh. Tested by running:
$ sudo ./eh_limits.sh
TEST: EH-limits - default sysctls [ OK ]
TEST: EH-limits - modified sysctls [ OK ]
Tests passed: 2
Tests failed: 0
$ sudo ./eh_limits.sh -v
>>>>> Default
TEST: Two non-padding options in HBH and DestOpts: Received as expected
TEST: Big destination option: Received as expected
TEST: Almost Big HBH option: Received as expected
TEST: Big HBH option: Received as expected
TEST: Too much HBH padding: Didn't receive as expected
TEST: Too much DestOpt padding: Didn't receive as expected
TEST: Too much DestOpt padding #2: Didn't receive as expected
TEST: Too much DestOpt padding #3: Didn't receive as expected
TEST: Almost too much DestOpt padding #2: Received as expected
TEST: Two Dest Ops: Didn't receive as expected
TEST: OOO Routing header: Didn't receive as expected
TEST: Two Routing headers: Didn't receive as expected
TEST: Two Destination options okay: Received as expected
TEST: Two Destination options: Didn't receive as expected
TEST: Two Destination options after RH: Didn't receive as expected
TEST: Many EH OOO: Didn't receive as expected
TEST: Two fragment Headers: Didn't receive as expected
TEST: EH-limits - default sysctls [ OK ]
>>>>> No order enforce, 8 options, 66 length limit
TEST: Two non-padding options in HBH and DestOpts: Received as expected
TEST: Big destination option: Didn't receive as expected
TEST: Almost Big HBH option: Received as expected
TEST: Big HBH option: Didn't receive as expected
TEST: Too much HBH padding: Didn't receive as expected
TEST: Too much DestOpt padding: Didn't receive as expected
TEST: Too much DestOpt padding #2: Didn't receive as expected
TEST: Too much DestOpt padding #3: Didn't receive as expected
TEST: Almost too much DestOpt padding #2: Received as expected
TEST: Two Dest Ops: Received as expected
TEST: OOO Routing header: Received as expected
TEST: Two Routing headers: Received as expected
TEST: Two Destination options okay: Received as expected
TEST: Two Destination options: Received as expected
TEST: Two Destination options after RH: Received as expected
TEST: Many EH OOO: Received as expected
TEST: Two fragment Headers: Didn't receive as expected
TEST: EH-limits - modified sysctls [ OK ]
Tests passed: 2
Tests failed: 0
V4: Switch order of patches to avoid transient build failure
V5: Allow Destination Options before the Routing header, fixes
suggested by Justin Iurman
v6: Fix a bug and create a test for extension header limits
v7: Run pylint on new Python file, run shellcheck on new bash file,
Make fixes as needed
Tom Herbert (10):
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
test: Add proto_nums.py in networking selftests
test: Add ext_hdr.py in networking selftests
test: Add networking selftest for eh limits
Documentation/networking/ip-sysctl.rst | 56 +++-
include/net/ipv6.h | 9 +-
include/net/netns/ipv6.h | 1 +
include/net/protocol.h | 14 +
include/uapi/linux/in6.h | 21 +-
include/uapi/linux/ip6_tunnel.h | 1 -
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs.c | 32 +-
net/ipv6/ip6_input.c | 42 +++
net/ipv6/reassembly.c | 1 +
net/ipv6/sysctl_net_ipv6.c | 7 +
net/ipv6/xfrm6_protocol.c | 2 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/eh_limits.py | 349 ++++++++++++++++++++
tools/testing/selftests/net/eh_limits.sh | 205 ++++++++++++
tools/testing/selftests/net/ext_hdr.py | 385 ++++++++++++++++++++++
tools/testing/selftests/net/proto_nums.py | 231 +++++++++++++
17 files changed, 1328 insertions(+), 30 deletions(-)
create mode 100755 tools/testing/selftests/net/eh_limits.py
create mode 100755 tools/testing/selftests/net/eh_limits.sh
create mode 100755 tools/testing/selftests/net/ext_hdr.py
create mode 100644 tools/testing/selftests/net/proto_nums.py
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v7 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
` (8 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
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>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
---
net/ipv6/exthdrs.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 209fdf1b1aa9..6cc18f35216f 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -301,9 +301,11 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
#endif
struct dst_entry *dst = skb_dst(skb);
struct net *net = dev_net(skb->dev);
- int extlen;
+ int extlen, max_opts_cnt;
- if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) ||
+ max_opts_cnt = READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt);
+ if (!max_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,
@@ -322,8 +324,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
dstbuf = opt->dst1;
#endif
- if (ip6_parse_tlv(false, skb,
- READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt))) {
+ if (ip6_parse_tlv(false, skb, max_opts_cnt)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
#if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -1033,7 +1034,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
{
struct inet6_skb_parm *opt = IP6CB(skb);
struct net *net = dev_net(skb->dev);
- int extlen;
+ int extlen, max_opts_cnt;
/*
* skb_network_header(skb) is equal to skb->data, and
@@ -1041,7 +1042,9 @@ 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) ||
+ max_opts_cnt = READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt);
+ if (!max_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:
@@ -1054,8 +1057,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
goto fail_and_free;
opt->flags |= IP6SKB_HOPBYHOP;
- if (ip6_parse_tlv(true, skb,
- READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt))) {
+ if (ip6_parse_tlv(true, skb, max_opts_cnt)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
opt->nhoff = sizeof(struct ipv6hdr);
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v7 02/10] ipv6: Cleanup IPv6 TLV definitions
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
` (7 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
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>
Reviewed-by: Justin Iurman <justin.iurman@gmail.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..438283dc5fde 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_TNL_ENCAP_LIMIT 4 /* RFC 2473, DestOpt */
+#define IPV6_TLV_ROUTERALERT 5 /* HBH */
+#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] 12+ messages in thread
* [PATCH net-next v7 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
` (6 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman, Willem de Bruijn
IPV6_TLV_TNL_ENCAP_LIMIT is a recognized Destination option that is
processed in ip6_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>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.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 6cc18f35216f..bfce1ddab1c5 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] 12+ messages in thread
* [PATCH net-next v7 04/10] ipv6: Set HBH and DestOpt limits to 2
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (2 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
` (5 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
Set the default limits of non-padding Hop-by-Hop and Destination
options to 2. This means that if a packet contains more than 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.
Signed-off-by: Tom Herbert <tom@herbertland.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.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 c27b9d7aeb7c..29aecd8e9617 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 documentation)
+ */
+#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] 12+ messages in thread
* [PATCH net-next v7 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (3 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
` (4 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert, Justin Iurman
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>
Reviewed-by: Justin Iurman <justin.iurman@gmail.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 28c7e4f5ecf9..bafb2ab08d0e 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2476,20 +2476,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] 12+ messages in thread
* [PATCH net-next v7 06/10] ipv6: Enforce Extension Header ordering
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (4 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
` (3 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
RFC8200 highly recommends that different Extension Headers be sent in
a prescribed order and all Extension Header types occur at most once
in a packet with the exception of Destination Options that may
occur twice. This patch enforces the ordering be followed in received
packets.
The allowed order of Extension Headers is:
IPv6 header
Hop-by-Hop Options header
Destination Options before the Routing 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 expect
for Destination Options that may occur twice.
net.ipv6.enforce_ext_hdr_order is a sysctl to enable or disable
enforcement of extension Header order. If it is set to zero then
Extension Header order and number of occurrences is not checked
in receive processing (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 | 14 +++++++++++++
net/ipv6/af_inet6.c | 1 +
net/ipv6/exthdrs.c | 2 ++
net/ipv6/ip6_input.c | 42 ++++++++++++++++++++++++++++++++++++++
net/ipv6/reassembly.c | 1 +
net/ipv6/sysctl_net_ipv6.c | 7 +++++++
net/ipv6/xfrm6_protocol.c | 2 ++
8 files changed, 70 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..0f1676625570 100644
--- a/include/net/protocol.h
+++ b/include/net/protocol.h
@@ -50,6 +50,19 @@ 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.
+ */
+#define IPV6_EXT_HDR_ORDER_HOP BIT(0)
+#define IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH BIT(1)
+#define IPV6_EXT_HDR_ORDER_ROUTING BIT(2)
+#define IPV6_EXT_HDR_ORDER_FRAGMENT BIT(3)
+#define IPV6_EXT_HDR_ORDER_AUTH BIT(4)
+#define IPV6_EXT_HDR_ORDER_ESP BIT(5)
+#define IPV6_EXT_HDR_ORDER_DEST BIT(6)
+
struct inet6_protocol {
int (*handler)(struct sk_buff *skb);
@@ -61,6 +74,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 bfce1ddab1c5..ca63bccc76fe 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..1eb8e84d98b3 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -355,6 +355,27 @@ void ipv6_list_rcv(struct list_head *head, struct packet_type *pt,
ip6_sublist_rcv(&sublist, curr_dev, curr_net);
}
+static u32 check_dst_opts_before_rh(const struct inet6_protocol *ipprot,
+ u32 ext_hdrs)
+{
+ /* Check if Destination Options before the Routing Header are
+ * present.
+ */
+ if (ipprot->ext_hdr_order != IPV6_EXT_HDR_ORDER_ROUTING ||
+ !(ext_hdrs & IPV6_EXT_HDR_ORDER_DEST))
+ return ext_hdrs;
+
+ /* We have Destination Options before the Routing Header. Set
+ * the mask of received extension headers to reflect that. We promote
+ * the bit from indicating just Destination Options present to
+ * Destination Options before the Routing Header being present
+ */
+ ext_hdrs = (ext_hdrs & ~IPV6_EXT_HDR_ORDER_DEST) |
+ IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH;
+
+ return ext_hdrs;
+}
+
INDIRECT_CALLABLE_DECLARE(int tcp_v6_rcv(struct sk_buff *));
/*
@@ -366,6 +387,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 +449,26 @@ 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) {
+ /* Check if there's Destination Options
+ * before the Routing Header
+ */
+ ext_hdrs = check_dst_opts_before_rh(ipprot,
+ ext_hdrs);
+ 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] 12+ messages in thread
* [PATCH net-next v7 07/10] ipv6: Document enforce_ext_hdr_order sysctl
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (5 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
` (2 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
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 | 34 ++++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index bafb2ab08d0e..ce783551b687 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2487,8 +2487,8 @@ max_dst_opts_number - INTEGER
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 zero then receive
+ Maximum number of non-padding TLVs allowed in a Hop-by-Hop
+ 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
@@ -2583,6 +2583,36 @@ 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 is enforced in
+ received packets and each Extension Header may be present
+ at most once per packet (except for Destination Options that
+ may occur twice). 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 occurrences per packet (except
+ for Hop-by-Hop Options that must always be the first Extension
+ Header and occur at most once in a packet)).
+
+ The Extension Header order is:
+
+ IPv6 header
+ Hop-by-Hop Options header
+ Destination Options before the Routing 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] 12+ messages in thread
* [PATCH net-next v7 08/10] test: Add proto_nums.py in networking selftests
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (6 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 09/10] test: Add ext_hdr.py " Tom Herbert
2026-02-06 2:45 ` [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Add proto_nums.py that contains various python definitions of
common protocol constants
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
tools/testing/selftests/net/proto_nums.py | 231 ++++++++++++++++++++++
1 file changed, 231 insertions(+)
create mode 100644 tools/testing/selftests/net/proto_nums.py
diff --git a/tools/testing/selftests/net/proto_nums.py b/tools/testing/selftests/net/proto_nums.py
new file mode 100644
index 000000000000..dc775164dacc
--- /dev/null
+++ b/tools/testing/selftests/net/proto_nums.py
@@ -0,0 +1,231 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Various protocol constant definitions
+
+from enum import Enum
+
+# IP protocol numbers
+class IP_Proto(Enum):
+ IP_PROTO_HOPOPT = 0
+ IP_PROTO_ICMP = 1
+ IP_PROTO_IGMP = 2
+ IP_PROTO_GGP = 3
+ IP_PROTO_IPv4 = 4
+ IP_PROTO_ST = 5
+ IP_PROTO_TCP = 6
+ IP_PROTO_CBT = 7
+ IP_PROTO_EGP = 8
+ IP_PROTO_IGP = 9
+ IP_PROTO_BBN_RCC_MON = 10
+ IP_PROTO_NVP_II = 11
+ IP_PROTO_PUP = 12
+ IP_PROTO_ARGUS = 13
+ IP_PROTO_EMCON = 14
+ IP_PROTO_XNET = 15
+ IP_PROTO_CHAOS = 16
+ IP_PROTO_UDP = 17
+ IP_PROTO_MUX = 18
+ IP_PROTO_DCN_MEAS = 19
+ IP_PROTO_HMP = 20
+ IP_PROTO_PRM = 21
+ IP_PROTO_XNS_IDP = 22
+ IP_PROTO_TRUNK_1 = 23
+ IP_PROTO_TRUNK_2 = 24
+ IP_PROTO_LEAF_1 = 25
+ IP_PROTO_LEAF_2 = 26
+ IP_PROTO_RDP = 27
+ IP_PROTO_IRTP = 28
+ IP_PROTO_ISO_TP4 = 29
+ IP_PROTO_NETBLT = 30
+ IP_PROTO_MFE_NSP = 31
+ IP_PROTO_MERIT_INP = 32
+ IP_PROTO_DCCP = 33
+ IP_PROTO_3PC = 34
+ IP_PROTO_IDPR = 35
+ IP_PROTO_XTP = 36
+ IP_PROTO_DDP = 37
+ IP_PROTO_IDPR_CMTP = 38
+ IP_PROTO_TP_PLUS_PLUS = 39
+ IP_PROTO_IL = 40
+ IP_PROTO_IPv6 = 41
+ IP_PROTO_SDRP = 42
+ IP_PROTO_IPv6_Route = 43
+ IP_PROTO_IPv6_Frag = 44
+ IP_PROTO_IDRP = 45
+ IP_PROTO_RSVP = 46
+ IP_PROTO_GRE = 47
+ IP_PROTO_DSR = 48
+ IP_PROTO_BNA = 49
+ IP_PROTO_ESP = 50
+ IP_PROTO_AH = 51
+ IP_PROTO_I_NLSP = 52
+ IP_PROTO_SWIPE = 53
+ IP_PROTO_NARP = 54
+ IP_PROTO_Min_IPv4 = 55
+ IP_PROTO_TLSP = 56
+ IP_PROTO_SKIP = 57
+ IP_PROTO_IPv6_ICMP = 58
+ IP_PROTO_IPv6_NoNxt = 59
+ IP_PROTO_IPv6_Opts = 60
+ IP_PROTO_any_1 = 61
+ IP_PROTO_CFTP = 62
+ IP_PROTO_any_2 = 63
+ IP_PROTO_SAT_EXPAK = 64
+ IP_PROTO_KRYPTOLAN = 65
+ IP_PROTO_RVD = 66
+ IP_PROTO_IPPC = 67
+ IP_PROTO_any_3 = 68
+ IP_PROTO_SAT_MON = 69
+ IP_PROTO_VISA = 70
+ IP_PROTO_IPCV = 71
+ IP_PROTO_CPNX = 72
+ IP_PROTO_CPHB = 73
+ IP_PROTO_WSN = 74
+ IP_PROTO_PVP = 75
+ IP_PROTO_BR_SAT_MON = 76
+ IP_PROTO_SUN_ND = 77
+ IP_PROTO_WB_MON = 78
+ IP_PROTO_WB_EXPAK = 79
+ IP_PROTO_ISO_IP = 80
+ IP_PROTO_VMTP = 81
+ IP_PROTO_SECURE_VMTP = 82
+ IP_PROTO_VINES = 83
+ IP_PROTO_IPTM = 84
+ IP_PROTO_NSFNET_IGP = 85
+ IP_PROTO_DGP = 86
+ IP_PROTO_TCF = 87
+ IP_PROTO_EIGRP = 88
+ IP_PROTO_OSPFIGP = 89
+ IP_PROTO_Sprite_RPC = 90
+ IP_PROTO_LARP = 91
+ IP_PROTO_MTP = 92
+ IP_PROTO_AX_25 = 93
+ IP_PROTO_IPIP = 94
+ IP_PROTO_MICP = 95
+ IP_PROTO_SCC_SP = 96
+ IP_PROTO_ETHERIP = 97
+ IP_PROTO_ENCAP = 98
+ IP_PROTO_any_4 = 99
+ IP_PROTO_GMTP = 100
+ IP_PROTO_IFMP = 101
+ IP_PROTO_PNNI = 102
+ IP_PROTO_PIM = 103
+ IP_PROTO_ARIS = 104
+ IP_PROTO_SCPS = 105
+ IP_PROTO_QNX = 106
+ IP_PROTO_A_N = 107
+ IP_PROTO_IPComp = 108
+ IP_PROTO_SNP = 109
+ IP_PROTO_Compaq_Peer = 110
+ IP_PROTO_IPX_in_IP = 111
+ IP_PROTO_VRRP = 112
+ IP_PROTO_PGM = 113
+ IP_PROTO_any_5 = 114
+ IP_PROTO_L2TP = 115
+ IP_PROTO_DDX = 116
+ IP_PROTO_IATP = 117
+ IP_PROTO_STP = 118
+ IP_PROTO_SRP = 119
+ IP_PROTO_UTI = 120
+ IP_PROTO_SMP = 121
+ IP_PROTO_SM = 122
+ IP_PROTO_PTP = 123
+ IP_PROTO_ISIS = 124
+ IP_PROTO_FIRE = 125
+ IP_PROTO_CRTP = 126
+ IP_PROTO_CRUDP = 127
+ IP_PROTO_SSCOPMCE = 128
+ IP_PROTO_IPLT = 129
+ IP_PROTO_SPS = 130
+ IP_PROTO_PIPE = 131
+ IP_PROTO_SCTP = 132
+ IP_PROTO_FC = 133
+ IP_PROTO_RSVP_E2E_IGNORE = 134
+ IP_PROTO_Mobility = 135
+ IP_PROTO_UDPLite = 136
+ IP_PROTO_MPLS_in_IP = 137
+ IP_PROTO_manet = 138
+ IP_PROTO_HIP = 139
+ IP_PROTO_Shim6 = 140
+ IP_PROTO_WESP = 141
+ IP_PROTO_ROHC = 142
+ IP_PROTO_Ethernet = 143
+ IP_PROTO_AGGFRAG = 144
+ IP_PROTO_NSH = 145
+ IP_PROTO_Homa = 146
+ IP_PROTO_BIT_EMU = 147
+
+# Hop-by-Hop and Destination Options numbers
+class HBHDst_Types(Enum):
+ HBHDST_TYPE_PAD1 = 0x0
+ HBHDST_TYPE_PADN = 0x1
+ HBHDST_TYPE_JUMBO = 0xc2
+ HBHDST_TYPE_RPL = 0x23
+ HBHDST_TYPE_RPL_DEPRECATED = 0x63
+ HBHDST_TYPE_TNL_ENCAP_LIMIT = 0x4
+ HBHDST_TYPE_ROUTER_ALERT = 0x5
+ HBHDST_TYPE_QUICK_START = 0x26
+ HBHDST_TYPE_CALIPSO = 0x7
+ HBHDST_TYPE_SMF_DPD = 0x8
+ HBHDST_TYPE_HAO = 0xc9
+ HBHDST_TYPE_ENDPOINT_ID = 0x8a
+ HBHDST_TYPE_ILNP_NONCE = 0x8b
+ HBHDST_TYPE_LINE_ID = 0x8c
+ HBHDST_TYPE_DEPRECATED = 0x4d
+ HBHDST_TYPE_MPL = 0x6d
+ HBHDST_TYPE_DFF = 0xee
+ HBHDST_TYPE_PDM = 0x0f
+ HBHDST_TYPE_MIN_PATH_MTU = 0x30
+ HBHDST_TYPE_IOAM_NO_CHNG = 0x11
+ HBHDST_TYPE_IOAM_CHNG = 0x31
+ HBHDST_TYPE_ALT_MARK = 0x12
+ HBHDST_TYPE_RFC3692_1 = 0x1e
+ HBHDST_TYPE_RFC3692_2 = 0x3e
+ HBHDST_TYPE_RFC3692_3 = 0x5e
+ HBHDST_TYPE_RFC3692_4 = 0x7e
+ HBHDST_TYPE_RFC3692_5 = 0x9e
+ HBHDST_TYPE_RFC3692_6 = 0xbe
+ HBHDST_TYPE_RFC3692_7 = 0xde
+
+# Routing header types
+class RoutingTypes(Enum):
+ ROUTING_TYPE_SRC_RT = 0
+ ROUTING_TYPE_NIMROD = 1
+ ROUTING_TYPE_2 = 2
+ ROUTING_TYPE_RPL = 3
+ ROUTING_TYPE_SRH = 4
+ ROUTING_TYPE_CRH16 = 5
+ ROUTING_TYPE_CRH32 = 6
+ ROUTING_TYPE_RFC3692_1 = 253
+ ROUTING_TYPE_RFC3692_2 = 254
+
+# Canonical extension header order
+class EH_Order(Enum):
+ IPV6_EXT_HDR_ORDER_HOP = 1 << 0
+ IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH = 1 << 1
+ IPV6_EXT_HDR_ORDER_ROUTING = 1 << 2
+ IPV6_EXT_HDR_ORDER_FRAGMENT = 1 << 3
+ IPV6_EXT_HDR_ORDER_AUTH = 1 << 4
+ IPV6_EXT_HDR_ORDER_ESP = 1 << 5
+ IPV6_EXT_HDR_ORDER_DEST = 1 << 6
+
+# ICMPv6 types
+class ICMP6_Type(Enum):
+ ICMPV6_DEST_UNREACH = 1
+ ICMPV6_PKT_TOOBIG = 2
+ ICMPV6_TIME_EXCEED = 3
+ ICMPV6_PARAMPROB = 4
+ ICMPV6_ECHO_REQUEST = 128
+ ICMPV6_ECHO_REPLY = 129
+ ICMPV6_MGM_QUERY = 130
+ ICMPV6_MGM_REPORT = 131
+ ICMPV6_MGM_REDUCTION = 132
+ ICMPV6_NI_QUERY = 139
+ ICMPV6_NI_REPLY = 140
+ ICMPV6_MLD2_REPORT = 143
+ ICMPV6_DHAAD_REQUEST = 144
+ ICMPV6_DHAAD_REPLY = 145
+ ICMPV6_MOBILE_PREFIX_SOL = 146
+ ICMPV6_MOBILE_PREFIX_ADV = 147
+ ICMPV6_MRDISC_ADV = 151
+ ICMPV6_MRDISC_SOL = 152
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v7 09/10] test: Add ext_hdr.py in networking selftests
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (7 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
@ 2026-02-04 22:51 ` Tom Herbert
2026-02-06 2:45 ` [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
9 siblings, 0 replies; 12+ messages in thread
From: Tom Herbert @ 2026-02-04 22:51 UTC (permalink / raw)
To: davem, kuba, netdev, justin.iurman, willemdebruijn.kernel, pabeni
Cc: Tom Herbert
Add ext_hdr.py that contains various Extension Header format definitions
and related helper functions.
This includes the Make_EH_Chain function that creates an Extension
Header chain based on an input list. The input list has the format:
[(<type>, <args>), (<type>, <args>), ... (<type>, <args>)]
where <type> is "H" for Hop-by-Hop Options, "D" for Destination
Options, "R" for Routing Header, "F" for Fragment header, "A" for
Authentication Header, and "E" for ESP header.
<args> is specific to the type of extension header. For Hop-by-Hop
and Destination Options <args> is a list of options in the format:
[(<opt_type>, <opt_length>), (<opt_type>, <opt_length>), ...
(<opt_type>, <opt_length>)]
For the Routing Header, <args> is a list of SIDs in the format:
[IPv6_address, IPv6_address, ... IPv6_address]
For the Fragment Header, <args> is the identifier number
Authentication and ESP are not currently supported by Make_EH_Chain
Signed-off-by: Tom Herbert <tom@herbertland.com>
---
tools/testing/selftests/net/ext_hdr.py | 385 +++++++++++++++++++++++++
1 file changed, 385 insertions(+)
create mode 100755 tools/testing/selftests/net/ext_hdr.py
diff --git a/tools/testing/selftests/net/ext_hdr.py b/tools/testing/selftests/net/ext_hdr.py
new file mode 100755
index 000000000000..bfb7da4a7c88
--- /dev/null
+++ b/tools/testing/selftests/net/ext_hdr.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+# Helper functions for creating extension headers using scapy
+
+import ctypes
+import shlex
+import socket
+import sys
+import subprocess
+import scapy
+import proto_nums
+
+
+# Read a sysctl
+def sysctl_read(name):
+ try:
+ # shlex.split helps handle arguments correctly
+ command = shlex.split(f"sysctl -n {name}")
+ # Use check=True to raise an exception if the command fails
+ result = subprocess.run(command, check=True,
+ capture_output=True, text=True)
+ value = result.stdout.strip()
+ except subprocess.CalledProcessError as ex:
+ print(f"Error reading sysctl: {ex.stderr}")
+ except FileNotFoundError:
+ print("The 'sysctl' command was not found. "
+ "Check your system's PATH.")
+
+ return int(value)
+
+# Common definitions for Destination and Hop-by-Hop options
+
+# Common Destination and Hop-by-Hop Options header
+class HbhDstOptions(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("hdr_ext_len", ctypes.c_uint8)
+ ]
+
+ def __init__(self, next_hdr, length):
+ self.next_hdr = next_hdr
+ self.hdr_ext_len = length
+
+# Common single Destination and Hop-by-Hop Option header
+class HbhDstOption(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("opt_type", ctypes.c_uint8),
+ ("opt_data_len", ctypes.c_uint8),
+ ]
+
+ def __init__(self, opt_type, length):
+ self.opt_type = opt_type
+ self.opt_data_len = length
+
+# Make PAD1 option
+def make_hbh_dst_option_pad1():
+ opt_bytes = bytearray(1)
+ opt_bytes[0] = proto_nums.HBHDst_Types.HBHDST_TYPE_PAD1.value
+ return (scapy.all.Raw(opt_bytes), 1)
+
+# Make a full DestOpt or HBH Option with some length
+def make_hbh_dst_option_with_data(opt_type, opt_len):
+ hdr = scapy.all.Raw(load=HbhDstOption(opt_type, opt_len))
+ opt_bytes = scapy.all.Raw(bytearray(opt_len))
+ allhdr = hdr/opt_bytes
+ return (scapy.all.Raw(allhdr), 2 + opt_len)
+
+# Make PADN option
+def make_hbh_dst_option_pad_n(opt_len):
+ return make_hbh_dst_option_with_data(
+ proto_nums.HBHDst_Types.HBHDST_TYPE_PADN.value, opt_len)
+
+# Make a Destination or Hop-by-Hop Options list. Input is list of pairs as
+# (type, length). Option data is set to zeroes.
+#
+# Return value is (hdr, len, outcome) where hdr is the raw bytes and length
+# is the length of the header including two bytes for the common extension
+# header (the returned header does not include the two byte common header).
+# outcome is True or False depending on whether the options are expected to
+# exceed a sysctl limit and would be dropped
+def make_hbh_dst_options_list(opt_list, max_cnt, max_len):
+ hdr = scapy.all.Raw()
+ eh_len = 0
+
+ num_non_padding_opts = 0
+ max_consect_pad_len = 0
+
+ consect_padlen = 0
+
+ # Create the set of options
+ for opt_type, jlen in opt_list:
+ if opt_type == proto_nums.HBHDst_Types.HBHDST_TYPE_PAD1.value:
+ # PAD1 is a special case
+ pair = make_hbh_dst_option_pad1()
+ consect_padlen += pair[1]
+ else:
+ pair = make_hbh_dst_option_with_data(opt_type, jlen)
+
+ if opt_type == proto_nums.HBHDst_Types.HBHDST_TYPE_PADN.value:
+ consect_padlen += pair[1]
+ else:
+ if consect_padlen > max_consect_pad_len:
+ max_consect_pad_len = consect_padlen
+ consect_padlen = 0
+ num_non_padding_opts += 1
+
+ # Append the option, add to cumulative length
+ hdr = hdr/pair[0]
+ eh_len += pair[1]
+
+ # Add two to length to account for two byte extension header
+ eh_len += 2
+
+ if eh_len % 8 != 0:
+ # The extension header length must be a multiple of eight bytes.
+ # If we're short add a padding option
+ plen = 8 - (eh_len % 8)
+ if plen == 1:
+ pair = make_hbh_dst_option_pad1()
+ else:
+ pair = make_hbh_dst_option_pad_n(plen - 2)
+
+ consect_padlen += pair[1]
+ hdr = hdr/pair[0]
+ eh_len += plen
+
+ if consect_padlen > max_consect_pad_len:
+ max_consect_pad_len = consect_padlen
+
+ outcome = True
+ if num_non_padding_opts > max_cnt:
+ # The number of options we created is greater then the sysctl
+ # limit, so we expect the packet to be dropped
+ outcome = False
+ if eh_len > max_len:
+ # The length of the extension is greater then the sysctl limit,
+ # so we expect the packet to be dropped
+ outcome = False
+ if max_consect_pad_len > 7:
+ # The maximum consecutive number of bytes of padding is
+ # greater than seven, so we expect the packet to be dropped
+ outcome = False
+
+ return (hdr, eh_len - 2, outcome)
+
+# Make a full Hop-by-Hop or Destination Options header
+def make_full_hbh_dst_options_list(next_hdr, opt_list, max_cnt, max_len):
+ pair = make_hbh_dst_options_list(opt_list, max_cnt, max_len)
+ opt_len = pair[1] + 2
+
+ opts = HbhDstOptions(next_hdr, (opt_len - 1) // 8)
+ hdr = scapy.all.Raw(load=opts)/pair[0]
+
+ return (hdr, opt_len, pair[2])
+
+# Routing header definitions
+
+# Base Routing Header
+class RoutingHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("hdr_ext_len", ctypes.c_uint8),
+ ("routing_type", ctypes.c_uint8),
+ ("segments_left", ctypes.c_uint8)
+ ]
+
+# SRv6 Routing Header
+class Srv6RoutingHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("rh", RoutingHdr),
+ ("last_entry", ctypes.c_uint8),
+ ("flags", ctypes.c_uint8),
+ ("tags", ctypes.c_uint16),
+ # Variable list
+ # TLV options
+ ]
+
+ def __init__(self, next_hdr, hdr_ext_len, segments_left, last_entry):
+ self.rh.next_hdr = next_hdr
+ self.rh.hdr_ext_len = hdr_ext_len
+ self.rh.routing_type = proto_nums.RoutingTypes.ROUTING_TYPE_SRH.value
+ self.rh.segments_left = segments_left
+
+ self.last_entry = last_entry
+
+# Make an SRv6 Routing Header (with no segments left)
+def make_srv6_routing_hdr(next_hdr, sids):
+
+ bhdr = scapy.all.Raw()
+ num_sids = 0
+
+ # Set up each SID in the list
+ for sid in sids:
+ sid_bytes = socket.inet_pton(socket.AF_INET6, sid)
+ bhdr = bhdr/scapy.all.Raw(load=sid_bytes)
+ num_sids += 1
+
+ eh_len = num_sids * 16
+
+ hdr = Srv6RoutingHdr(next_hdr, eh_len // 8, 0, num_sids - 1)
+
+ bhdr = scapy.all.Raw(load=hdr)/bhdr
+
+ return (bhdr, eh_len + 8, True)
+
+# Fragment header
+
+# Basic Fragment Header
+class FragmentHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("rsvd", ctypes.c_uint8),
+ ("fragment_offset", ctypes.c_uint16, 13),
+ ("rsvd2", ctypes.c_uint16, 2),
+ ("more", ctypes.c_uint16, 1),
+ ("identfication", ctypes.c_uint32),
+ ]
+
+ def __init__(self, next_hdr, fragment_offset, more, ident):
+ self.next_hdr = next_hdr
+ self.fragment_offset = fragment_offset
+ self.more = more
+ self.identfication = ident
+
+# Make a raw fragment header
+def make_fragment_hdr(next_hdr, fragment_offset, more, ident):
+ hdr = FragmentHdr(next_hdr, fragment_offset, more, ident)
+
+ return (scapy.all.Raw(load=hdr), 8, True)
+
+# Authentication Header
+
+# Base Authentication Header
+class AuthHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("next_hdr", ctypes.c_uint8),
+ ("payload_len", ctypes.c_uint8),
+ ("spi", ctypes.c_uint32)
+ # ICV is variable length
+ ]
+
+ def __init__(self, next_hdr, payload_len, spi):
+ self.next_hdr = next_hdr
+ self.payload_len = payload_len
+ self.spi = spi
+
+# ESP
+
+# Base ESP header
+class EspHdr(ctypes.BigEndianStructure):
+ _pack_ = 1
+ _fields_ = [
+ ("spi", ctypes.c_uint32),
+ ("seqno", ctypes.c_uint32)
+ # Payload data + padding
+ # ICV is variable length
+ ]
+
+ def __init__(self, spi, seqno):
+ self.spi = spi
+ self.seqno = seqno
+
+# Check if EH list is out of order
+def check_eh_order(eh_list):
+ # OOO is okay if sysctl is not enforcing in order
+ do_check = sysctl_read("net.ipv6.enforce_ext_hdr_order")
+
+ seen = 0
+ for eh_type, _args in eh_list:
+ if eh_type == "H":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_HOP.value
+ elif eh_type == "D":
+ if (seen &
+ proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ROUTING.value):
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_DEST.value
+ else:
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_DEST_BEFORE_RH.value
+ elif eh_type == "R":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ROUTING.value
+ elif eh_type == "F":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_FRAGMENT.value
+ if seen & order != 0:
+ # Linux stack doesn't allow more than one
+ # Fragment Header in a packet
+ return False
+ elif eh_type == "A":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_AUTH.value
+ elif eh_type == "E":
+ order = proto_nums.EH_Order.IPV6_EXT_HDR_ORDER_ESP.value
+
+ if (do_check and seen >= order):
+ return False
+ seen |= order
+
+ return True
+
+# Compute the next headers for an EH chain. Returns a new list of EHs
+# with the next header attached to each element
+def compute_next_hdrs(next_hdr, eh_list):
+ nlist = []
+
+ # Run through the list in reverse and set the next header up for each
+ # enty
+ for eh_type, args in reversed(eh_list):
+ entry = (eh_type, args, next_hdr)
+ nlist.insert(0, entry)
+ if eh_type == "H":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_HOPOPT.value
+ elif eh_type == "D":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Opts.value
+ elif eh_type == "R":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Route.value
+ elif eh_type == "F":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_IPv6_Frag.value
+ elif eh_type == "A":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_AH.value
+ elif eh_type == "E":
+ next_hdr = proto_nums.IP_Proto.IP_PROTO_ESP.value
+
+ return nlist, next_hdr
+
+# Make an extension header chain from a list
+# The list contains a set of pairs in the form (<eh_type>, <args>)
+# <eh_type> is:
+# "H"-- Hop-by-Hop Options
+# "D"-- Destination Options
+# "R"-- Routing Header
+# "F"-- Fragment Header
+# "A"-- Authentication Header
+# "E"-- ESP
+#
+# <args> is specific to EH type
+def make_eh_chain(next_hdr, eh_list):
+ nlist = []
+
+ # Run through the list in reverse and set the next header up for each
+ # enty
+ nlist, next_hdr = compute_next_hdrs(next_hdr, eh_list)
+
+ outcome = check_eh_order(eh_list)
+
+ hdr = scapy.all.Raw()
+ eh_len = 0
+
+ for eh_type, args, nnext_hdr in reversed(nlist):
+ if eh_type == "H":
+ # args is a list of (<opt_type>, <opt_len>) pairs
+ pair = make_full_hbh_dst_options_list(nnext_hdr, args,
+ sysctl_read("net.ipv6.max_hbh_opts_number"),
+ sysctl_read("net.ipv6.max_hbh_length"))
+ elif eh_type == "D":
+ # args is a list of (<opt_type>, <opt_len>) pairs
+ pair = make_full_hbh_dst_options_list(nnext_hdr, args,
+ sysctl_read("net.ipv6.max_dst_opts_number"),
+ sysctl_read("net.ipv6.max_dst_opts_length"))
+ elif eh_type == "R":
+ # args is a list of IPv6 address string
+ pair = make_srv6_routing_hdr(nnext_hdr, args)
+ elif eh_type == "F":
+ # Arg is (<identifier>)
+ pair = make_fragment_hdr(nnext_hdr, 0, False, args)
+ elif eh_type == "A":
+ print("Auth type not supported for test")
+ sys.exit(1)
+ elif eh_type == "E":
+ print("ESP type not supported for test")
+ sys.exit(1)
+ else:
+ print("Unknown EH type character")
+ sys.exit(1)
+
+ hdr = pair[0]/hdr
+ eh_len += pair[1]
+
+ if pair[2] is False:
+ outcome = False
+
+ return (hdr, eh_len, next_hdr, outcome)
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
` (8 preceding siblings ...)
2026-02-04 22:51 ` [PATCH net-next v7 09/10] test: Add ext_hdr.py " Tom Herbert
@ 2026-02-06 2:45 ` Jakub Kicinski
[not found] ` <CALx6S3576DLyd18BnFRkaNMhxb-Y3bsy8YJXy3-Q43EQVppEDQ@mail.gmail.com>
9 siblings, 1 reply; 12+ messages in thread
From: Jakub Kicinski @ 2026-02-06 2:45 UTC (permalink / raw)
To: Tom Herbert; +Cc: davem, netdev, justin.iurman, willemdebruijn.kernel, pabeni
On Wed, 4 Feb 2026 14:51:44 -0800 Tom Herbert wrote:
> 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 flexibility
> 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.
Patch 10 never arrived..
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities
[not found] ` <CALx6S3576DLyd18BnFRkaNMhxb-Y3bsy8YJXy3-Q43EQVppEDQ@mail.gmail.com>
@ 2026-02-11 19:16 ` Jakub Kicinski
0 siblings, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2026-02-11 19:16 UTC (permalink / raw)
To: Tom Herbert; +Cc: davem, netdev, justin.iurman, willemdebruijn.kernel, pabeni
On Tue, 10 Feb 2026 13:46:27 -0800 Tom Herbert wrote:
> I resent it. If you would like me to resend the series let me know.
Looks like patchwork did not merge it with the previous posting of
the 9 patches. I'm afraid you'll have to wait until Feb 23rd and
do a full repost (net-next is closed now).
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-02-11 19:16 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-04 22:51 [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 01/10] ipv6: Check of max HBH or DestOp sysctl is zero and drop if it is Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 02/10] ipv6: Cleanup IPv6 TLV definitions Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 03/10] ipv6: Add case for IPV6_TLV_TNL_ENCAP_LIMIT in EH TLV switch Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 04/10] ipv6: Set HBH and DestOpt limits to 2 Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 05/10] ipv6: Document defaults for max_{dst|hbh}_opts_number sysctls Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 06/10] ipv6: Enforce Extension Header ordering Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 07/10] ipv6: Document enforce_ext_hdr_order sysctl Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 08/10] test: Add proto_nums.py in networking selftests Tom Herbert
2026-02-04 22:51 ` [PATCH net-next v7 09/10] test: Add ext_hdr.py " Tom Herbert
2026-02-06 2:45 ` [PATCH net-next v7 00/10] ipv6: Address ext hdr DoS vulnerabilities Jakub Kicinski
[not found] ` <CALx6S3576DLyd18BnFRkaNMhxb-Y3bsy8YJXy3-Q43EQVppEDQ@mail.gmail.com>
2026-02-11 19:16 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox