diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 0c3d1be7f129..0d952514d095 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -149,9 +149,7 @@ struct seg6_end_dt_info { int vrf_ifindex; int vrf_table; - /* tunneled packet family (IPv4 or IPv6). - * Protocol and header length are inferred from family. - */ + /* tunneled packet family (IPv4 or IPv6) */ u16 family; }; @@ -1056,6 +1054,40 @@ static int input_action_end_dx6(struct sk_buff *skb, return -EINVAL; } +/* Validate the length of the IPv4 header at skb->data (which must point to + * the network header) and report that length, options included. + * Only the version, the header length and the presence of the whole header + * are checked. + * The length is written only when SKB_NOT_DROPPED_YET is returned. + * + * skb pointers may change: after this call, the caller must evaluate again + * any pointer into the packet data. + */ +static enum skb_drop_reason +seg6_validate_iphdr_len(struct sk_buff *skb, int *hdrlen) +{ + enum skb_drop_reason reason; + const struct iphdr *iph; + int len; + + reason = pskb_may_pull_reason(skb, sizeof(struct iphdr)); + if (reason) + return reason; + + iph = ip_hdr(skb); + if (iph->version != 4 || iph->ihl < 5) + return SKB_DROP_REASON_IP_INHDR; + + len = ip_hdrlen(skb); + + reason = pskb_may_pull_reason(skb, len); + if (reason) + return reason; + + *hdrlen = len; + return SKB_NOT_DROPPED_YET; +} + static int input_action_end_dx4_finish(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -1085,14 +1117,18 @@ static int input_action_end_dx4_finish(struct net *net, struct sock *sk, static int input_action_end_dx4(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + SKB_DR(reason); + int hdrlen; + if (!decap_and_validate(skb, IPPROTO_IPIP)) goto drop; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) + reason = seg6_validate_iphdr_len(skb, &hdrlen); + if (reason) goto drop; skb->protocol = htons(ETH_P_IP); - skb_set_transport_header(skb, sizeof(struct iphdr)); + skb_set_transport_header(skb, hdrlen); nf_reset_ct(skb); if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled)) @@ -1102,7 +1138,7 @@ static int input_action_end_dx4(struct sk_buff *skb, return input_action_end_dx4_finish(dev_net(skb->dev), NULL, skb); drop: - kfree_skb(skb); + kfree_skb_reason(skb, reason); return -EINVAL; } @@ -1237,16 +1273,24 @@ static struct sk_buff *end_dt_vrf_core(struct sk_buff *skb, struct seg6_end_dt_info *info = &slwt->dt_info; struct net_device *vrf; __be16 protocol; + SKB_DR(reason); int hdrlen; vrf = end_dt_get_vrf_rcu(skb, info); if (unlikely(!vrf)) goto drop; + if (unlikely(info->family != AF_UNSPEC && info->family != family)) { + pr_warn_once("seg6local: SRv6 End.DT* family mismatch"); + goto drop; + } + switch (family) { case AF_INET: protocol = htons(ETH_P_IP); - hdrlen = sizeof(struct iphdr); + reason = seg6_validate_iphdr_len(skb, &hdrlen); + if (unlikely(reason)) + goto drop; break; case AF_INET6: protocol = htons(ETH_P_IPV6); @@ -1258,11 +1302,6 @@ static struct sk_buff *end_dt_vrf_core(struct sk_buff *skb, goto drop; } - if (unlikely(info->family != AF_UNSPEC && info->family != family)) { - pr_warn_once("seg6local: SRv6 End.DT* family mismatch"); - goto drop; - } - skb->protocol = protocol; skb_dst_drop(skb); @@ -1273,7 +1312,7 @@ static struct sk_buff *end_dt_vrf_core(struct sk_buff *skb, return end_dt_vrf_rcv(skb, family, vrf); drop: - kfree_skb(skb); + kfree_skb_reason(skb, reason); return ERR_PTR(-EINVAL); } @@ -1286,9 +1325,7 @@ static int input_action_end_dt4(struct sk_buff *skb, if (!decap_and_validate(skb, IPPROTO_IPIP)) goto drop; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) - goto drop; - + /* end_dt_vrf_core() validates the inner IPv4 header length */ skb = end_dt_vrf_core(skb, slwt, AF_INET); if (!skb) /* packet has been processed and consumed by the VRF */