Thread (3 messages) 3 messages, 2 authors, 8d ago

[PATCH net 1/1] seg6: validate state in netfilter continuations

flat view
COOLING8d

From: Ren Wei <hidden>
Date: 2026-09-30 18:09:07
Subsystem: networking [general], networking [srv6], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrea Mayer, Linus Torvalds

From: Zixuan Chai <redacted>

Netfilter hooks can drop or replace the dst while an SRv6 packet is
queued for continuation. The seg6local callbacks must not assume that
skb_dst() still carries the state for the route being processed.

Validate the destination and SEG6_LOCAL state before using it in the
End.DX4/End.DX6 continuations and seg6_local_input_core(). The
seg6_iptunnel continuations must also resolve the SEG6 state beneath
an XFRM dst and hold a reference while processing the SRH.

Fixes: 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
Cc: stable@vger.kernel.org
Reported-by: Vega <redacted>
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/20260720204430.1886091-1-xmei5@asu.edu/ (local)
Assisted-by: LLM
Signed-off-by: Zixuan Chai <redacted>
Signed-off-by: Ren Wei <redacted>
---
 net/ipv6/seg6_iptunnel.c | 62 +++++++++++++++++++++++++++++-----------
 net/ipv6/seg6_local.c    | 34 ++++++++++++++++++----
 2 files changed, 73 insertions(+), 23 deletions(-)
diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c
index 61c6a27bf202..e3d36fb0f290 100644
--- a/net/ipv6/seg6_iptunnel.c
+++ b/net/ipv6/seg6_iptunnel.c
@@ -18,6 +18,8 @@
 #include <net/ip6_fib.h>
 #include <net/route.h>
 #include <net/seg6.h>
+#include <net/dst_metadata.h>
+#include <net/xfrm.h>
 #include <linux/seg6.h>
 #include <linux/seg6_iptunnel.h>
 #include <net/addrconf.h>
@@ -60,6 +62,26 @@ static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt)
 	return (struct seg6_lwt *)lwt->data;
 }
 
+static struct lwtunnel_state *seg6_lwt_state(struct dst_entry *dst)
+{
+	dst = xfrm_dst_path(dst);
+	return dst->lwtstate;
+}
+
+static struct lwtunnel_state *seg6_lwt_state_get(struct sk_buff *skb)
+{
+	struct lwtunnel_state *lwtst;
+
+	if (!skb_valid_dst(skb))
+		return NULL;
+
+	lwtst = seg6_lwt_state(skb_dst(skb));
+	if (!lwtst || lwtst->type != LWTUNNEL_ENCAP_SEG6)
+		return NULL;
+
+	return lwtstate_get(lwtst);
+}
+
 static inline struct seg6_iptunnel_encap *
 seg6_encap_lwtunnel(struct lwtunnel_state *lwt)
 {
@@ -395,14 +417,12 @@ static int __seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh,
 	return 0;
 }
 
-static int seg6_do_srh(struct sk_buff *skb, struct dst_entry *cache_dst)
+static int seg6_do_srh(struct sk_buff *skb, struct dst_entry *cache_dst,
+		       struct seg6_lwt *slwt)
 {
-	struct dst_entry *dst = skb_dst(skb);
 	struct seg6_iptunnel_encap *tinfo;
-	struct seg6_lwt *slwt;
 	int proto, err = 0;
 
-	slwt = seg6_lwt_lwtunnel(dst->lwtstate);
 	tinfo = slwt->tuninfo;
 
 	switch (tinfo->mode) {
@@ -557,18 +577,16 @@ static int seg6_input_finish(struct net *net, struct sock *sk,
 static int seg6_input_core(struct net *net, struct sock *sk,
 			   struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
 	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	/* We cannot dereference "orig_dst" once ip6_route_input() or
-	 * skb_dst_drop() is called. However, in order to detect a dst loop, we
-	 * need the address of its lwtstate. So, save the address of lwtstate
-	 * now and use it later as a comparison.
-	 */
-	lwtst = orig_dst->lwtstate;
+	lwtst = seg6_lwt_state_get(skb);
+	if (!lwtst) {
+		err = -EINVAL;
+		goto drop;
+	}
 
 	slwt = seg6_lwt_lwtunnel(lwtst);
 
@@ -576,7 +594,7 @@ static int seg6_input_core(struct net *net, struct sock *sk,
 	dst = dst_cache_get(&slwt->cache_input);
 	local_bh_enable();
 
-	err = seg6_do_srh(skb, dst);
+	err = seg6_do_srh(skb, dst, slwt);
 	if (unlikely(err)) {
 		dst_release(dst);
 		goto drop;
@@ -590,7 +608,7 @@ static int seg6_input_core(struct net *net, struct sock *sk,
 		}
 
 		/* cache only if we don't create a dst reference loop */
-		if (!dst->error && lwtst != dst->lwtstate) {
+		if (!dst->error && lwtst != seg6_lwt_state(dst)) {
 			local_bh_disable();
 			dst_cache_set_ip6(&slwt->cache_input, dst,
 					  &ipv6_hdr(skb)->saddr);
@@ -605,6 +623,7 @@ static int seg6_input_core(struct net *net, struct sock *sk,
 		skb_dst_set(skb, dst);
 	}
 
+	lwtstate_put(lwtst);
 	if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
 		return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT,
 			       dev_net(skb->dev), NULL, skb, NULL,
@@ -613,6 +632,7 @@ static int seg6_input_core(struct net *net, struct sock *sk,
 	return seg6_input_finish(dev_net(skb->dev), NULL, skb);
 drop:
 	kfree_skb(skb);
+	lwtstate_put(lwtst);
 	return err;
 }
 
@@ -667,18 +687,24 @@ static struct dst_entry *seg6_output_dst_lookup(struct net *net,
 static int seg6_output_core(struct net *net, struct sock *sk,
 			    struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
+	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate);
+	lwtst = seg6_lwt_state_get(skb);
+	if (!lwtst) {
+		err = -EINVAL;
+		goto drop;
+	}
+
+	slwt = seg6_lwt_lwtunnel(lwtst);
 
 	local_bh_disable();
 	dst = dst_cache_get(&slwt->cache_output);
 	local_bh_enable();
 
-	err = seg6_do_srh(skb, dst);
+	err = seg6_do_srh(skb, dst, slwt);
 	if (unlikely(err))
 		goto drop;
 
@@ -695,7 +721,7 @@ static int seg6_output_core(struct net *net, struct sock *sk,
 		}
 
 		/* cache only if we don't create a dst reference loop */
-		if (orig_dst->lwtstate != dst->lwtstate) {
+		if (lwtst != seg6_lwt_state(dst)) {
 			local_bh_disable();
 			dst_cache_set_ip6(&slwt->cache_output, dst, &fl6.saddr);
 			local_bh_enable();
@@ -708,6 +734,7 @@ static int seg6_output_core(struct net *net, struct sock *sk,
 
 	skb_dst_drop(skb);
 	skb_dst_set(skb, dst);
+	lwtstate_put(lwtst);
 
 	if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
 		return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, net, sk, skb,
@@ -716,6 +743,7 @@ static int seg6_output_core(struct net *net, struct sock *sk,
 	return dst_output(net, sk, skb);
 drop:
 	dst_release(dst);
+	lwtstate_put(lwtst);
 	kfree_skb(skb);
 	return err;
 }
diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index d1070aec7b72..ac01e3032973 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,17 @@ static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
 	return (struct seg6_local_lwt *)lwt->data;
 }
 
+static struct seg6_local_lwt *seg6_local_lwt_from_skb(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb_dst(skb);
+
+	if (!skb_valid_dst(skb) || !dst->lwtstate ||
+	    dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6_LOCAL)
+		return NULL;
+
+	return seg6_local_lwtunnel(dst->lwtstate);
+}
+
 static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
 {
 	struct ipv6_sr_hdr *srh;
@@ -924,11 +936,14 @@ static int input_action_end_dx2(struct sk_buff *skb,
 static int input_action_end_dx6_finish(struct net *net, struct sock *sk,
 				       struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct in6_addr *nhaddr = NULL;
 	struct seg6_local_lwt *slwt;
 
-	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
+	slwt = seg6_local_lwt_from_skb(skb);
+	if (!slwt) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
 
 	/* The inner packet is not associated to any local interface,
 	 * so we do not call netif_rx().
@@ -975,13 +990,16 @@ static int input_action_end_dx6(struct sk_buff *skb,
 static int input_action_end_dx4_finish(struct net *net, struct sock *sk,
 				       struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	enum skb_drop_reason reason;
 	struct seg6_local_lwt *slwt;
 	struct iphdr *iph;
 	__be32 nhaddr;
 
-	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
+	slwt = seg6_local_lwt_from_skb(skb);
+	if (!slwt) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
 
 	iph = ip_hdr(skb);
 
@@ -1628,13 +1646,17 @@ static void seg6_local_update_counters(struct seg6_local_lwt *slwt,
 static int seg6_local_input_core(struct net *net, struct sock *sk,
 				 struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct seg6_action_desc *desc;
 	struct seg6_local_lwt *slwt;
 	unsigned int len = skb->len;
 	int rc;
 
-	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
+	slwt = seg6_local_lwt_from_skb(skb);
+	if (!slwt) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
 	desc = slwt->desc;
 
 	rc = desc->input(skb, slwt);
-- 
2.34.1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help