* [PATCH net v5] seg6: validate state in netfilter continuations
@ 2026-11-06 3:30 Ren Wei
2026-11-09 16:46 ` Andrea Mayer
2026-11-10 22:30 ` patchwork-bot+netdevbpf
0 siblings, 2 replies; 3+ messages in thread
From: Ren Wei @ 2026-11-06 3:30 UTC (permalink / raw)
To: netdev, andrea.mayer
Cc: davem, edumazet, kuba, pabeni, horms, contact, pablo, vega,
sashiko-bot, petalzu987, weir, liuhangbin
From: Zixuan Chai <petalzu987@gmail.com>
When nf_hooks_lwtunnel is enabled, End.DX4 sends decapsulated packets
through NF_INET_PRE_ROUTING. If DNAT changes the inner destination,
nf_nat_ipv4_pre_routing() drops skb_dst(), and the continuation then
dereferences the missing dst's lwtstate. This causes a KASAN NULL
pointer dereference and fatal exception panic.
End.DX6 has the same issue with IPv6 DNAT. In seg6_iptunnel,
POST_ROUTING SNAT with an outbound XFRM policy can replace the dst before
the continuation resumes.
Validate the current dst and expected lwtstate type in each NF_HOOK()
continuation before calling the existing handler. Drop packets with
missing or incompatible state while preserving direct no-hook paths.
The helpers return bool because callers only need a validity check.
Testing shows that the End.DX4 DNAT reproducer triggers a KASAN NULL
pointer dereference without this change and no longer does so with the
fix. The SNAT+ESP/XFRM reproducer also completes without the reported
fault. The in-tree End.DX4 netfilter forwarding selftest passes all four
bidirectional checks with nf_hooks_lwtunnel disabled and enabled.
Fixes: 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260720204430.1886091-1-xmei5@asu.edu
Signed-off-by: Zixuan Chai <petalzu987@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>
Acked-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
Changes in v5:
- Explain the End.DX4 DNAT trigger and KASAN fatal-exception impact in the
commit message.
- Return bool from both skb lwtstate helpers because callers only test
validity.
- Add current clean/patched runtime results and an independent forwarding
selftest result to the validation record.
- Retain the v4 wrapper-only checks and direct no-hook behavior.
- v4 Link:
https://lore.kernel.org/netdev/c9ec4da483c6c17dda703bcd6fc425ef2b53e320.1793207060.git.petalzu987@gmail.com/
Changes in v4:
- Keep seg6_input_core() and seg6_output_core() unchanged; validate state
only in their NF_HOOK() continuation wrappers, addressing the review
comment on the unnecessary core refactoring.
- Remove the Assisted-by: LLM trailer as requested.
- Preserve the original NF_HOOK() continuation indentation.
- Omit the cover letter.
- v3 Link:
https://lore.kernel.org/netdev/cover.1792660496.git.petalzu987@gmail.com/
Changes in v3:
- Move the five dst/lwtstate checks into NF_HOOK() okfn
wrappers; preserve direct no-hook calls.
- Fix the stale orig_dst reference in the seg6_input_core()
comment.
- Add clean XFRM and End.DX4 results plus independent
forwarding tests.
- Remove the private tracker Closes URL.
- v2 Link:
https://lore.kernel.org/netdev/cover.1792230570.git.petalzu987@gmail.com/
Changes in v2:
- Add unlikely() to the new checks.
- Validate the current dst/lwtstate type in both seg6local and
seg6_iptunnel continuations; drop invalid state.
- Do not walk through XFRM dsts or take an extra lwtstate
reference.
- Retain the seg6_input_core() loop-detection comment.
- Document both reproducer setups and clean/patched KASAN
results.
- v1 Link:
https://lore.kernel.org/all/cover.1790748418.git.petalzu987@gmail.com/
net/ipv6/seg6_iptunnel.c | 46 ++++++++++++++++++++++++++++++---
net/ipv6/seg6_local.c | 55 +++++++++++++++++++++++++++++++++++++---
2 files changed, 94 insertions(+), 7 deletions(-)
diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c
index 0d39a38a5dd2..3bc47c5614ff 100644
--- a/net/ipv6/seg6_iptunnel.c
+++ b/net/ipv6/seg6_iptunnel.c
@@ -18,6 +18,7 @@
#include <net/ip6_fib.h>
#include <net/route.h>
#include <net/seg6.h>
+#include <net/dst_metadata.h>
#include <linux/seg6.h>
#include <linux/seg6_iptunnel.h>
#include <net/addrconf.h>
@@ -60,6 +61,21 @@ static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt)
return (struct seg6_lwt *)lwt->data;
}
+static bool seg6_skb_has_lwtstate(struct sk_buff *skb)
+{
+ struct dst_entry *dst;
+
+ if (unlikely(!skb_valid_dst(skb)))
+ return false;
+
+ dst = skb_dst(skb);
+ if (unlikely(!dst->lwtstate ||
+ dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6))
+ return false;
+
+ return true;
+}
+
static inline struct seg6_iptunnel_encap *
seg6_encap_lwtunnel(struct lwtunnel_state *lwt)
{
@@ -622,6 +638,17 @@ static int seg6_input_core(struct net *net, struct sock *sk,
return err;
}
+static int seg6_input_core_nf(struct net *net, struct sock *sk,
+ struct sk_buff *skb)
+{
+ if (unlikely(!seg6_skb_has_lwtstate(skb))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return seg6_input_core(net, sk, skb);
+}
+
static int seg6_input_nf(struct sk_buff *skb)
{
struct net_device *dev = skb_dst_dev(skb);
@@ -630,10 +657,10 @@ static int seg6_input_nf(struct sk_buff *skb)
switch (skb->protocol) {
case htons(ETH_P_IP):
return NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, NULL,
- skb, NULL, dev, seg6_input_core);
+ skb, NULL, dev, seg6_input_core_nf);
case htons(ETH_P_IPV6):
return NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING, net, NULL,
- skb, NULL, dev, seg6_input_core);
+ skb, NULL, dev, seg6_input_core_nf);
}
return -EINVAL;
@@ -726,6 +753,17 @@ static int seg6_output_core(struct net *net, struct sock *sk,
return err;
}
+static int seg6_output_core_nf(struct net *net, struct sock *sk,
+ struct sk_buff *skb)
+{
+ if (unlikely(!seg6_skb_has_lwtstate(skb))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return seg6_output_core(net, sk, skb);
+}
+
static int seg6_output_nf(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct net_device *dev = skb_dst_dev(skb);
@@ -733,10 +771,10 @@ static int seg6_output_nf(struct net *net, struct sock *sk, struct sk_buff *skb)
switch (skb->protocol) {
case htons(ETH_P_IP):
return NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, sk, skb,
- NULL, dev, seg6_output_core);
+ NULL, dev, seg6_output_core_nf);
case htons(ETH_P_IPV6):
return NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING, net, sk, skb,
- NULL, dev, seg6_output_core);
+ NULL, dev, seg6_output_core_nf);
}
return -EINVAL;
diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 0c3d1be7f129..156df33e054d 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -24,6 +24,7 @@
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/dst_cache.h>
+#include <net/dst_metadata.h>
#include <net/ip_tunnels.h>
#ifdef CONFIG_IPV6_SEG6_HMAC
#include <net/seg6_hmac.h>
@@ -213,6 +214,21 @@ static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
return (struct seg6_local_lwt *)lwt->data;
}
+static bool seg6_local_skb_has_lwtstate(struct sk_buff *skb)
+{
+ struct dst_entry *dst;
+
+ if (unlikely(!skb_valid_dst(skb)))
+ return false;
+
+ dst = skb_dst(skb);
+ if (unlikely(!dst->lwtstate ||
+ dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6_LOCAL))
+ return false;
+
+ return true;
+}
+
static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
{
struct ipv6_sr_hdr *srh;
@@ -1028,6 +1044,17 @@ static int input_action_end_dx6_finish(struct net *net, struct sock *sk,
return dst_input(skb);
}
+static int input_action_end_dx6_finish_nf(struct net *net, struct sock *sk,
+ struct sk_buff *skb)
+{
+ if (unlikely(!seg6_local_skb_has_lwtstate(skb))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return input_action_end_dx6_finish(net, sk, skb);
+}
+
/* decapsulate and forward to specified nexthop */
static int input_action_end_dx6(struct sk_buff *skb,
struct seg6_local_lwt *slwt)
@@ -1048,7 +1075,7 @@ static int input_action_end_dx6(struct sk_buff *skb,
if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
return NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING,
dev_net(skb->dev), NULL, skb, skb->dev,
- NULL, input_action_end_dx6_finish);
+ NULL, input_action_end_dx6_finish_nf);
return input_action_end_dx6_finish(dev_net(skb->dev), NULL, skb);
drop:
@@ -1082,6 +1109,17 @@ static int input_action_end_dx4_finish(struct net *net, struct sock *sk,
return dst_input(skb);
}
+static int input_action_end_dx4_finish_nf(struct net *net, struct sock *sk,
+ struct sk_buff *skb)
+{
+ if (unlikely(!seg6_local_skb_has_lwtstate(skb))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return input_action_end_dx4_finish(net, sk, skb);
+}
+
static int input_action_end_dx4(struct sk_buff *skb,
struct seg6_local_lwt *slwt)
{
@@ -1098,7 +1136,7 @@ static int input_action_end_dx4(struct sk_buff *skb,
if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
dev_net(skb->dev), NULL, skb, skb->dev,
- NULL, input_action_end_dx4_finish);
+ NULL, input_action_end_dx4_finish_nf);
return input_action_end_dx4_finish(dev_net(skb->dev), NULL, skb);
drop:
@@ -1741,6 +1779,17 @@ static int seg6_local_input_core(struct net *net, struct sock *sk,
return rc;
}
+static int seg6_local_input_core_nf(struct net *net, struct sock *sk,
+ struct sk_buff *skb)
+{
+ if (unlikely(!seg6_local_skb_has_lwtstate(skb))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return seg6_local_input_core(net, sk, skb);
+}
+
static int seg6_local_input(struct sk_buff *skb)
{
if (skb->protocol != htons(ETH_P_IPV6)) {
@@ -1751,7 +1800,7 @@ static int seg6_local_input(struct sk_buff *skb)
if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_IN,
dev_net(skb->dev), NULL, skb, skb->dev, NULL,
- seg6_local_input_core);
+ seg6_local_input_core_nf);
return seg6_local_input_core(dev_net(skb->dev), NULL, skb);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH net v5] seg6: validate state in netfilter continuations
2026-11-06 3:30 [PATCH net v5] seg6: validate state in netfilter continuations Ren Wei
@ 2026-11-09 16:46 ` Andrea Mayer
2026-11-10 22:30 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: Andrea Mayer @ 2026-11-09 16:46 UTC (permalink / raw)
To: Ren Wei
Cc: netdev, davem, edumazet, kuba, pabeni, horms, contact, pablo,
vega, sashiko-bot, petalzu987, liuhangbin, Hangbin Liu,
Stefano Salsano, Andrea Mayer, Andrea Mayer
On Fri, 6 Nov 2026 11:30:19 +0800
Ren Wei <weir@nebusec.ai> wrote:
> From: Zixuan Chai <petalzu987@gmail.com>
>
> When nf_hooks_lwtunnel is enabled, End.DX4 sends decapsulated packets
> through NF_INET_PRE_ROUTING. If DNAT changes the inner destination,
> nf_nat_ipv4_pre_routing() drops skb_dst(), and the continuation then
> dereferences the missing dst's lwtstate. This causes a KASAN NULL
> pointer dereference and fatal exception panic.
>
> End.DX6 has the same issue with IPv6 DNAT. In seg6_iptunnel,
> POST_ROUTING SNAT with an outbound XFRM policy can replace the dst before
> the continuation resumes.
>
> Validate the current dst and expected lwtstate type in each NF_HOOK()
> continuation before calling the existing handler. Drop packets with
> missing or incompatible state while preserving direct no-hook paths.
> The helpers return bool because callers only need a validity check.
>
> Testing shows that the End.DX4 DNAT reproducer triggers a KASAN NULL
> pointer dereference without this change and no longer does so with the
> fix. The SNAT+ESP/XFRM reproducer also completes without the reported
> fault. The in-tree End.DX4 netfilter forwarding selftest passes all four
> bidirectional checks with nf_hooks_lwtunnel disabled and enabled.
>
> Fixes: 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://sashiko.dev/#/patchset/20260720204430.1886091-1-xmei5@asu.edu
> Signed-off-by: Zixuan Chai <petalzu987@gmail.com>
> Signed-off-by: Ren Wei <weir@nebusec.ai>
> Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>
> Acked-by: Pablo Neira Ayuso <pablo@netfilter.org>
> [snip]
Hi,
thanks for the v5.
Reviewed-by: Andrea Mayer <andrea.mayer@uniroma2.it>
Thanks,
Andrea
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net v5] seg6: validate state in netfilter continuations
2026-11-06 3:30 [PATCH net v5] seg6: validate state in netfilter continuations Ren Wei
2026-11-09 16:46 ` Andrea Mayer
@ 2026-11-10 22:30 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-11-10 22:30 UTC (permalink / raw)
To: Ren Wei
Cc: netdev, andrea.mayer, davem, edumazet, kuba, pabeni, horms,
contact, pablo, vega, sashiko-bot, petalzu987, liuhangbin
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Fri, 6 Nov 2026 11:30:19 +0800 you wrote:
> From: Zixuan Chai <petalzu987@gmail.com>
>
> When nf_hooks_lwtunnel is enabled, End.DX4 sends decapsulated packets
> through NF_INET_PRE_ROUTING. If DNAT changes the inner destination,
> nf_nat_ipv4_pre_routing() drops skb_dst(), and the continuation then
> dereferences the missing dst's lwtstate. This causes a KASAN NULL
> pointer dereference and fatal exception panic.
>
> [...]
Here is the summary with links:
- [net,v5] seg6: validate state in netfilter continuations
https://git.kernel.org/netdev/net/c/549484dd3e80
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-10 22:30 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-06 3:30 [PATCH net v5] seg6: validate state in netfilter continuations Ren Wei
2026-11-09 16:46 ` Andrea Mayer
2026-11-10 22:30 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox