Call ip6_parse_tlv from segment routing receive function to properly parse TLVs and to leverage to existing implementation that already parses Destination and Hop-by-Hop Options. This includes applying the denial of service mitigations and limits to processing segment routing TLVs.
Signed-off-by: Tom Herbert <t...@quantonium.net> --- include/net/seg6.h | 5 +++++ include/net/seg6_hmac.h | 2 +- net/ipv6/exthdrs.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++--- net/ipv6/seg6_hmac.c | 16 +++------------- net/ipv6/seg6_local.c | 21 ++++++++++++++------ 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/include/net/seg6.h b/include/net/seg6.h index 8b2dc68..b7d8a94 100644 --- a/include/net/seg6.h +++ b/include/net/seg6.h @@ -38,6 +38,11 @@ static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from, skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum); } +static inline unsigned int seg6_tlv_offset(struct ipv6_sr_hdr *srh) +{ + return sizeof(*srh) + ((srh->first_segment + 1) << 4); +} + struct seg6_pernet_data { struct mutex lock; struct in6_addr __rcu *tun_src; diff --git a/include/net/seg6_hmac.h b/include/net/seg6_hmac.h index 7fda469..6ad33e2 100644 --- a/include/net/seg6_hmac.h +++ b/include/net/seg6_hmac.h @@ -53,7 +53,7 @@ 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, int optoff); extern int seg6_hmac_init(void); extern void seg6_hmac_exit(void); extern int seg6_hmac_net_init(struct net *net); diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index a394d20..7d14c0d 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -112,7 +112,8 @@ static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, return false; } -/* Parse tlv encoded option header (hop-by-hop or destination) +/* Parse tlv encoded option header (hop-by-hop, destination, or + * segment routing header) * * Arguments: * procs - TLV proc structure @@ -365,14 +366,42 @@ static void seg6_update_csum(struct sk_buff *skb) (__be32 *)addr); } +static bool seg6_recv_hmac(struct sk_buff *skb, int optoff) +{ + if (!seg6_hmac_validate_skb(skb, optoff)) { + kfree_skb(skb); + return false; + } + + return true; +} + +static const struct tlvtype_proc tlvprocsrhopt_lst[] = { +#ifdef CONFIG_IPV6_SEG6_HMAC + { + .type = SR6_TLV_HMAC, + .func = seg6_recv_hmac, + }, +#endif + {-1, NULL} +}; + +static bool seg6_srhopt_unknown(struct sk_buff *skb, int optoff, + bool disallow_unknowns) +{ + /* Unknown segment routing header options are ignored */ + + return !disallow_unknowns; +} + static int ipv6_srh_rcv(struct sk_buff *skb) { struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); + int accept_seg6, tlvoff, tlvlen; struct ipv6_sr_hdr *hdr; struct inet6_dev *idev; struct in6_addr *addr; - int accept_seg6; hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); @@ -387,8 +416,24 @@ static int ipv6_srh_rcv(struct sk_buff *skb) return -1; } + tlvoff = seg6_tlv_offset(hdr); + tlvlen = ipv6_optlen((struct ipv6_opt_hdr *)hdr) - tlvoff; + + if (tlvlen) { + if (tlvlen > net->ipv6.sysctl.max_srh_opts_len) { + kfree_skb(skb); + return -1; + } + + if (!ip6_parse_tlv(tlvprocsrhopt_lst, skb, + init_net.ipv6.sysctl.max_srh_opts_cnt, + tlvoff, tlvlen, seg6_srhopt_unknown)) + return -1; + } + #ifdef CONFIG_IPV6_SEG6_HMAC - if (!seg6_hmac_validate_skb(skb)) { + if (idev->cnf.seg6_require_hmac > 0 && !sr_has_hmac(hdr)) { + /* mandatory check but no HMAC tlv */ kfree_skb(skb); return -1; } diff --git a/net/ipv6/seg6_hmac.c b/net/ipv6/seg6_hmac.c index 8546f94..18f82f2 100644 --- a/net/ipv6/seg6_hmac.c +++ b/net/ipv6/seg6_hmac.c @@ -240,7 +240,7 @@ 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, int optoff) { u8 hmac_output[SEG6_HMAC_FIELD_LEN]; struct net *net = dev_net(skb->dev); @@ -251,23 +251,13 @@ bool seg6_hmac_validate_skb(struct sk_buff *skb) idev = __in6_dev_get(skb->dev); - srh = (struct ipv6_sr_hdr *)skb_transport_header(skb); - - tlv = seg6_get_tlv_hmac(srh); - - /* mandatory check but no tlv */ - if (idev->cnf.seg6_require_hmac > 0 && !tlv) - return false; - /* no check */ if (idev->cnf.seg6_require_hmac < 0) return true; - /* check only if present */ - if (idev->cnf.seg6_require_hmac == 0 && !tlv) - return true; + srh = (struct ipv6_sr_hdr *)skb_transport_header(skb); - /* now, seg6_require_hmac >= 0 && tlv */ + tlv = (struct sr6_tlv_hmac *)(skb_network_header(skb) + optoff); hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); if (!hinfo) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 78155fd..d486ed8 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -92,6 +92,19 @@ static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb) return srh; } +static bool seg6_local_hmac_validate_skb(struct sk_buff *skb, + struct ipv6_sr_hdr *srh) +{ +#ifdef CONFIG_IPV6_SEG6_HMAC + int off = sr_hmac_offset(srh); + + return off ? seg6_hmac_validate_skb(skb, off) : + (__in6_dev_get(skb->dev)->cnf.seg6_require_hmac <= 0); +#else + return true; +#endif +} + static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb) { struct ipv6_sr_hdr *srh; @@ -103,10 +116,8 @@ static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb) if (srh->segments_left == 0) return NULL; -#ifdef CONFIG_IPV6_SEG6_HMAC - if (!seg6_hmac_validate_skb(skb)) + if (!seg6_local_hmac_validate_skb(skb, srh)) return NULL; -#endif return srh; } @@ -120,10 +131,8 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) if (srh && srh->segments_left > 0) return false; -#ifdef CONFIG_IPV6_SEG6_HMAC - if (srh && !seg6_hmac_validate_skb(skb)) + if (srh && !seg6_local_hmac_validate_skb(skb, srh)) return false; -#endif if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0) return false; -- 2.7.4