seg6: fix HMAC validation when an extension header precedes the SRH

seg6_hmac_validate_skb() derived the SRH from skb_transport_header().
That only holds while the two coincide, which is not true on the
seg6_local input path.

ip6_rcv_core() leaves the transport header just past the IPv6 header.
A Hop-by-Hop options header is consumed before the route lookup and
advances it, but a Destination Options header is not: the seg6_local
lwtunnel is entered through an input redirect from the route lookup,
which bypasses the extension header handlers. The transport header
then still points at the Destination Options header while
seg6_get_srh() has located the real SRH further down the chain.

The HMAC is therefore computed over the Destination Options header,
and a packet carrying a valid HMAC TLV is dropped when
seg6_require_hmac is set. Such a packet is legitimate: RFC 8200 allows
Destination Options before a routing header, and get_srh() has walked
the header chain since commit 5829d70b0b ("ipv6: sr: fix get_srh() to
comply with IPv6 standard "RFC 8200"").

With a Fragment or an Authentication header in front of the SRH, the
same mistake also reads past the data pulled by seg6_get_srh(). This
happens before seg6_require_hmac is read, so the default configuration
is affected.

Reproduce by giving a node a seg6local End SID with
net.ipv6.conf.<dev>.seg6_require_hmac=1 and a key installed with
"ip sr hmac set <keyid> sha1", then sending

  IPv6 -> Destination Options -> SRH (carrying a valid HMAC TLV) -> payload

to that SID: it is dropped, while the same packet without the
Destination Options header passes.

Fixes: 5829d70b0b ("ipv6: sr: fix get_srh() to comply with IPv6 standard "RFC 8200"")
Assisted-by: LLM
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
Reviewed-by: Andrea Mayer <andrea.mayer@uniroma2.it>
Link: https://patch.msgid.link/20260926-b4-seg6-hmac-transport-header-v2-1-8087b76f6375@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Yuya Kusakabe 2026-09-26 20:19:27 +09:00 committed by Paolo Abeni
parent ddef4c2d6d
commit f9cb4d296d
4 changed files with 7 additions and 9 deletions

View File

@ -48,7 +48,8 @@ extern int seg6_hmac_info_add(struct net *net, u32 key,
extern int seg6_hmac_info_del(struct net *net, u32 key);
extern int seg6_push_hmac(struct net *net, struct in6_addr *saddr,
struct ipv6_sr_hdr *srh);
extern bool seg6_hmac_validate_skb(struct sk_buff *skb);
extern bool seg6_hmac_validate_skb(struct sk_buff *skb,
struct ipv6_sr_hdr *srh);
#ifdef CONFIG_IPV6_SEG6_HMAC
extern int seg6_hmac_net_init(struct net *net);
extern void seg6_hmac_net_exit(struct net *net);

View File

@ -387,7 +387,7 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev)
}
#ifdef CONFIG_IPV6_SEG6_HMAC
if (!seg6_hmac_validate_skb(skb)) {
if (!seg6_hmac_validate_skb(skb, hdr)) {
kfree_skb(skb);
return -1;
}

View File

@ -173,13 +173,12 @@ EXPORT_SYMBOL(seg6_hmac_compute);
*
* called with rcu_read_lock()
*/
bool seg6_hmac_validate_skb(struct sk_buff *skb)
bool seg6_hmac_validate_skb(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
{
u8 hmac_output[SEG6_HMAC_FIELD_LEN];
struct net *net = dev_net(skb->dev);
struct seg6_hmac_info *hinfo;
struct sr6_tlv_hmac *tlv;
struct ipv6_sr_hdr *srh;
struct inet6_dev *idev;
int require_hmac;
@ -187,8 +186,6 @@ bool seg6_hmac_validate_skb(struct sk_buff *skb)
if (!idev)
return false;
srh = (struct ipv6_sr_hdr *)skb_transport_header(skb);
tlv = seg6_get_tlv_hmac(srh);
require_hmac = READ_ONCE(idev->cnf.seg6_require_hmac);

View File

@ -222,7 +222,7 @@ static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
return NULL;
#ifdef CONFIG_IPV6_SEG6_HMAC
if (!seg6_hmac_validate_skb(skb))
if (!seg6_hmac_validate_skb(skb, srh))
return NULL;
#endif
@ -239,7 +239,7 @@ static bool decap_and_validate(struct sk_buff *skb, int proto)
return false;
#ifdef CONFIG_IPV6_SEG6_HMAC
if (srh && !seg6_hmac_validate_skb(skb))
if (srh && !seg6_hmac_validate_skb(skb, srh))
return false;
#endif
@ -771,7 +771,7 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt)
srhoff = srh ? ((unsigned char *)srh - skb->data) : 0;
pinfo = seg6_get_srh_pktinfo(srh);
#ifdef CONFIG_IPV6_SEG6_HMAC
if (srh && !seg6_hmac_validate_skb(skb))
if (srh && !seg6_hmac_validate_skb(skb, srh))
goto drop;
#endif
flvmask = finfo->flv_ops;