With the seg6 inline mode, a packet can carry more than one SRH. The SRHs with Segments Left 0 are considered consumed SRHs, and they can come before the SRH to process (Segments Left > 0), as in this packet:
+------+----------------------+----------------------+---------+ | IPv6 | SRH, Segments Left 0 | SRH, Segments Left 1 | payload | +------+----------------------+----------------------+---------+ End without flavors skips the consumed SRH and processes the next one [1]. With the PSP flavor, End should do the same. When the SRH to process arrives with Segments Left 1, as in the diagram, End with PSP should then also remove it [2]. However, End with PSP alone currently looks only at the first routing header of the packet, finds Segments Left 0 there, and drops the packet. Make End with PSP alone skip the consumed SRHs, and any other routing header with Segments Left 0, and process the SRH that follows them. If there is no SRH to process, the packet is dropped as before. When the SRH to process arrives with Segments Left 1, End with PSP now removes it even if consumed SRHs come before it. Found by manual code inspection and reproduced in a VM without kernel changes or error injection. A following commit adds tests for this case to srv6_end_flavors_test.sh. Without this change, the new tests with a consumed SRH fail for End with PSP alone. With this series, all tests pass and KASAN reports no error. [1] - https://datatracker.ietf.org/doc/html/rfc8986#section-4.1 [2] - https://datatracker.ietf.org/doc/html/rfc8986#section-4.16.1.2 Fixes: bdf3c0b9c10b ("seg6: add PSP flavor support for SRv6 End behavior") Signed-off-by: Andrea Mayer <[email protected]> --- net/ipv6/seg6_local.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index da8225591fe3..26cd4facbe36 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -775,11 +775,14 @@ static bool seg6_pop_srh(struct sk_buff *skb, int srhoff) skb_set_transport_header(skb, thoff); iph = ipv6_hdr(skb); - if (iph->nexthdr == NEXTHDR_ROUTING) { + if (srhoff == sizeof(*iph)) { iph->nexthdr = srh_nexthdr; } else { /* we must look for the extension header (EXTH, for short) that - * immediately precedes the SRH we have just removed. + * immediately precedes the SRH we have just removed, i.e. the + * EXTH that ends at srhoff. We find it by its position and not + * by its nexthdr, since another routing header (e.g. an SRH + * with SL = 0) may come before the removed SRH. * Then, we update the value of the EXTH nexthdr with the one * contained in the SRH nexthdr. */ @@ -807,7 +810,7 @@ static bool seg6_pop_srh(struct sk_buff *skb, int srhoff) break; } - if (hp->nexthdr == NEXTHDR_ROUTING) { + if (off == srhoff) { hp->nexthdr = srh_nexthdr; break; } @@ -823,6 +826,24 @@ static bool seg6_pop_srh(struct sk_buff *skb, int srhoff) return true; } +/* look up the SRH to be processed by the RFC8986 flavors. As End does, a + * routing header with SL = 0 is skipped (e.g. an SRH already consumed). + * This function returns NULL when no valid SRH is found. + */ +static struct ipv6_sr_hdr *seg6_flv8986_get_srh(struct sk_buff *skb) +{ + struct ipv6_sr_hdr *srh; + + srh = seg6_get_srh(skb, IP6_FH_F_SKIP_RH); + if (srh) + return srh; + + /* we take the first routing header without skipping it. In this way, + * seg6_get_srh_pktinfo() reports SL = 0 and not a missing SRH. + */ + return seg6_get_srh(skb, 0); +} + /* process the packet on the basis of the RFC8986 flavors set for the given * SRv6 End behavior instance. */ @@ -837,7 +858,7 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) __u32 flvmask; int srhoff; - srh = seg6_get_srh(skb, 0); + srh = seg6_flv8986_get_srh(skb); srhoff = srh ? ((unsigned char *)srh - skb->data) : 0; pinfo = seg6_get_srh_pktinfo(srh); #ifdef CONFIG_IPV6_SEG6_HMAC -- 2.43.0

