From: Zixuan Chai Netfilter hooks can replace or drop skb_dst() before an SRv6 continuation resumes. The continuation callbacks then assume that the current dst still contains the expected lwtstate, which can lead to invalid state access. Validate the current dst and lwtstate in each NF_HOOK() okfn wrapper. Drop the skb when the state is missing or has the wrong type. Keep the direct core calls unchanged when nf_hooks_lwtunnel is off. Fixes: 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane") Cc: stable@vger.kernel.org Reported-by: Vega Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260720204430.1886091-1-xmei5@asu.edu Assisted-by: LLM Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Reviewed-by: Hangbin Liu Acked-by: Pablo Neira Ayuso --- 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 | 62 +++++++++++++++++++++++++++++++--------- net/ipv6/seg6_local.c | 55 +++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 7cbe0685f8bb..19df8a772fdb 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,21 @@ 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_from_skb(struct sk_buff *skb) +{ + struct dst_entry *dst; + + if (unlikely(!skb_valid_dst(skb))) + return NULL; + + dst = skb_dst(skb); + if (unlikely(!dst->lwtstate || + dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6)) + return NULL; + + return dst->lwtstate; +} + static inline struct seg6_iptunnel_encap * seg6_encap_lwtunnel(struct lwtunnel_state *lwt) { @@ -557,18 +573,15 @@ 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. + /* Save the lwtstate address before replacing the dst to detect a + * dst reference loop. */ - lwtst = orig_dst->lwtstate; + lwtst = skb_dst(skb)->lwtstate; slwt = seg6_lwt_lwtunnel(lwtst); @@ -616,6 +629,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_lwt_state_from_skb(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); @@ -624,10 +648,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; @@ -667,12 +691,13 @@ 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 = skb_dst(skb)->lwtstate; + slwt = seg6_lwt_lwtunnel(lwtst); local_bh_disable(); dst = dst_cache_get(&slwt->cache_output); @@ -695,7 +720,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 != dst->lwtstate) { local_bh_disable(); dst_cache_set_ip6(&slwt->cache_output, dst, &fl6.saddr); local_bh_enable(); @@ -720,6 +745,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_lwt_state_from_skb(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); @@ -727,10 +763,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 53f7b2452575..ae0a5191652e 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #ifdef CONFIG_IPV6_SEG6_HMAC #include @@ -213,6 +214,21 @@ 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; + + if (unlikely(!skb_valid_dst(skb))) + return NULL; + + dst = skb_dst(skb); + if (unlikely(!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; @@ -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_lwt_from_skb(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_lwt_from_skb(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_lwt_from_skb(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