From patchwork Fri Dec 20 23:38:36 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214369 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="zhLy/VLc"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flZk3DQ4z9s29 for ; Sat, 21 Dec 2019 10:39:26 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726633AbfLTXjX (ORCPT ); Fri, 20 Dec 2019 18:39:23 -0500 Received: from mail-pg1-f193.google.com ([209.85.215.193]:38708 "EHLO mail-pg1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXjX (ORCPT ); Fri, 20 Dec 2019 18:39:23 -0500 Received: by mail-pg1-f193.google.com with SMTP id a33so5705722pgm.5 for ; Fri, 20 Dec 2019 15:39:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=3H5XZqaDC1mbheFPQAH51aSqmoBGxxCr4SkUcbX1YTI=; b=zhLy/VLcfEw91aScjJNQ/mMwcAFeEm//4DCexkaljSduRVV/Bkqa3lf3eXsJvQa4Um mDHPu/yILLv1zH+mAkoQVrQkRqC9HzZ0nwZmrK8VMs2FuqcPng9jQBUa25+t8iixJbiD wXF2ec7SnGRED1RTBBhHoraOLvfRaxAbGfaykLiU/AxFpMBZoc5upxQcT5d7Zs02edlK 6IOjuxk3+8FToGPbJTcGzaWNaBhIPUzl1xs9vofCjQ0Jok5oSt3T7ztmDk/aQMe5Ex6b oAVp053MBQSJkj54oKHZ/lvCaDQ9wustedtJLGZjvJXONncKL/r95I8d2Nla+13TyJNK pnpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=3H5XZqaDC1mbheFPQAH51aSqmoBGxxCr4SkUcbX1YTI=; b=G5/gEq2IiMmc2fA6tDh4uEhY2Sw46oHROnMLaLsAEgTGE03Dje7STOqgNvviMdbEdc 6R4mvHcT9Ny8S2p7xMkDaAA7rIEuVmm2vLZuJ3JmkjuygkxGM1h2obU7bPBxxi4xO5DY yaE1mspoQ3PAr/zzDIq8h2Iy5J3QOODCqbW81dHKkfCWSx8XvZ47ImzM9v7TQBm3gBDa K2IcLCHX1Wi6dDofsPTFrlyV6j+BAa1Vq7FyfadbljX1tdpYY5nFfGif4a7OYiqC6fFX XIewVWtNeKWhgPM4pMxDoTbyYEkdd4O5OonM4SJddVR+nmW57M3RVgkvueno50IeSvUu qDRw== X-Gm-Message-State: APjAAAV4jTz70Io3JGmnNoOBrFU968azfKvwKl6zsi9tCXSdRqbdhaVd ppYSw1cF06MQZVFCXVkVFrfs8Q== X-Google-Smtp-Source: APXvYqzz9QBjri38R3h5WSkyj5nIa8sgGsIhhsXDGdmgcHzxD9Sohk5vU1KIsrLE+hadrpZtHIFVBg== X-Received: by 2002:aa7:982d:: with SMTP id q13mr17940599pfl.152.1576885162505; Fri, 20 Dec 2019 15:39:22 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.21 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:21 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 1/9] ipeh: Fix destopts and hopopts counters on drop Date: Fri, 20 Dec 2019 15:38:36 -0800 Message-Id: <1576885124-14576-2-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert For destopts, bump IPSTATS_MIB_INHDRERRORS when limit of length of extension header is exceeded. For hop-by-hop options, bump IPSTATS_MIB_INHDRERRORS in same situations as for when destopts are dropped. Signed-off-by: Tom Herbert --- net/ipv6/exthdrs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index ab5add0..f605e4e 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -288,9 +288,9 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || !pskb_may_pull(skb, (skb_transport_offset(skb) + ((skb_transport_header(skb)[1] + 1) << 3)))) { +fail_and_free: __IP6_INC_STATS(dev_net(dst->dev), idev, IPSTATS_MIB_INHDRERRORS); -fail_and_free: kfree_skb(skb); return -1; } @@ -820,8 +820,10 @@ static const struct tlvtype_proc tlvprochopopt_lst[] = { int ipv6_parse_hopopts(struct sk_buff *skb) { + struct inet6_dev *idev = __in6_dev_get(skb->dev); struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); + struct dst_entry *dst = skb_dst(skb); int extlen; /* @@ -834,6 +836,8 @@ int ipv6_parse_hopopts(struct sk_buff *skb) !pskb_may_pull(skb, (sizeof(struct ipv6hdr) + ((skb_transport_header(skb)[1] + 1) << 3)))) { fail_and_free: + __IP6_INC_STATS(dev_net(dst->dev), idev, + IPSTATS_MIB_INHDRERRORS); kfree_skb(skb); return -1; } @@ -850,6 +854,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb) opt->nhoff = sizeof(struct ipv6hdr); return 1; } + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); return -1; } From patchwork Fri Dec 20 23:38:37 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214370 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="P8I4bJmE"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flZn3Bdnz9s29 for ; Sat, 21 Dec 2019 10:39:29 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726674AbfLTXj0 (ORCPT ); Fri, 20 Dec 2019 18:39:26 -0500 Received: from mail-pl1-f194.google.com ([209.85.214.194]:44812 "EHLO mail-pl1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXj0 (ORCPT ); Fri, 20 Dec 2019 18:39:26 -0500 Received: by mail-pl1-f194.google.com with SMTP id az3so4748285plb.11 for ; Fri, 20 Dec 2019 15:39:25 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=SYL1kT7BlNRGD0uXOAsrQDh4b39WpCrAp8bVRnIfWEk=; b=P8I4bJmEBUW6VSGmGcSX0beNEFmmoZWr0lqLXxs+ipH+J7Gh87K/kCoZKLc9QEDnPg 3IhM3Ygf0usHrG/TkLQxVAgH9TXRihDBuoNLn8UsnvqPOIqmZPfuELFxbzcQGrjjQ6aZ jqAT96NNp0X2XYfiqCnYqWo60VGWv1Bb9N/wcr5tR9DrzrgfScLVrOmqygcoIAfFMtzS wCFZeIBsaZIZoLYaPTLNXcEgQKlbEZZ0L5UoWczvv7Du4QlJ3QZY2n0g2ybxefUGfIaV tkqwQLUTVSEslU7zGNhCc9u4lzqyzB92PqIqmwyxVyvrAfRiZpMu1vdaNLeY6boMTR2x WRpw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=SYL1kT7BlNRGD0uXOAsrQDh4b39WpCrAp8bVRnIfWEk=; b=X3SyR8jWrTUQ3sjQL9Lq1jnNTHzOzD5e0OvTCAXpaJTKBSHs89hhoiEQhbXh1AEtEn HC0kg1dHHPBxSeoVsX+L/tMiAQMiBp5tEIcH5SHcGllKelfeHaX3Y1WZyFTmX451pvaP fpMU/ihZzq+4qKFA1yWkzB29hbV2dJrLqVfThdsoh+yhv7FKbGYFJ37+AGOb0DCm0PYO yr7ysGW9vAGQqGVv1VLNOo1FOMflz2lKvLW3XeDuc69fEzY8Od6wL7Yd9RitVVnzf4GF h6FXo1bPfIveZUnxRr+SvFO4xqTJj2WC2MbQHa3uzK/M50dhBI+Zk4v4bIYDFRibmXP6 v1xw== X-Gm-Message-State: APjAAAVponCf0Z+N45MhclDxsnnRnVzlxH9jgr3Yk9TjOogZfL6O9cQq W0uHfn/mfQeiBIJJOkRHTZ9hUA== X-Google-Smtp-Source: APXvYqzvathyC/ybFamLFZabE7mG/74kRFxEOPgUPtZEiStQiZaOhwB6f0f7Kb2jpJ7R6mB/SmtJGA== X-Received: by 2002:a17:90a:a48c:: with SMTP id z12mr19221892pjp.38.1576885165317; Fri, 20 Dec 2019 15:39:25 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:24 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 2/9] ipeh: Create exthdrs_options.c and ipeh.h Date: Fri, 20 Dec 2019 15:38:37 -0800 Message-Id: <1576885124-14576-3-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Create exthdrs_options.c to hold code related to specific Hop-by-Hop and Destination extension header options. Move related functions in exthdrs.c to the new file. Create include net/ipeh.h to contain common definitions for IP extension headers. Reviewed-by: Simon Horman Signed-off-by: Tom Herbert --- include/net/ipeh.h | 22 +++++ include/net/ipv6.h | 1 + net/ipv6/Makefile | 2 +- net/ipv6/exthdrs.c | 204 --------------------------------------------- net/ipv6/exthdrs_options.c | 201 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 205 deletions(-) create mode 100644 include/net/ipeh.h create mode 100644 net/ipv6/exthdrs_options.c diff --git a/include/net/ipeh.h b/include/net/ipeh.h new file mode 100644 index 0000000..ec2d186 --- /dev/null +++ b/include/net/ipeh.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _NET_IPEH_H +#define _NET_IPEH_H + +#include + +/* + * Parsing tlv encoded headers. + * + * Parsing function "func" returns true, if parsing succeed + * and false, if it failed. + * It MUST NOT touch skb->h. + */ +struct tlvtype_proc { + int type; + bool (*func)(struct sk_buff *skb, int offset); +}; + +extern const struct tlvtype_proc tlvprocdestopt_lst[]; +extern const struct tlvtype_proc tlvprochopopt_lst[]; + +#endif /* _NET_IPEH_H */ diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 4e95f6d..1d84394 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -20,6 +20,7 @@ #include #include #include +#include #define SIN6_LEN_RFC2133 24 diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 8ccf355..df3919b 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -10,7 +10,7 @@ ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \ route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \ raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \ exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \ - udp_offload.o seg6.o fib6_notifier.o + udp_offload.o seg6.o fib6_notifier.o exthdrs_options.o ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index f605e4e..09053a4 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -39,7 +39,6 @@ #include #include #include -#include #if IS_ENABLED(CONFIG_IPV6_MIP6) #include #endif @@ -51,19 +50,6 @@ #include -/* - * Parsing tlv encoded headers. - * - * Parsing function "func" returns true, if parsing succeed - * and false, if it failed. - * It MUST NOT touch skb->h. - */ - -struct tlvtype_proc { - int type; - bool (*func)(struct sk_buff *skb, int offset); -}; - /********************* Generic functions *********************/ @@ -200,80 +186,6 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, return false; } -/***************************** - Destination options header. - *****************************/ - -#if IS_ENABLED(CONFIG_IPV6_MIP6) -static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) -{ - struct ipv6_destopt_hao *hao; - struct inet6_skb_parm *opt = IP6CB(skb); - struct ipv6hdr *ipv6h = ipv6_hdr(skb); - int ret; - - if (opt->dsthao) { - net_dbg_ratelimited("hao duplicated\n"); - goto discard; - } - opt->dsthao = opt->dst1; - opt->dst1 = 0; - - hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff); - - if (hao->length != 16) { - net_dbg_ratelimited("hao invalid option length = %d\n", - hao->length); - goto discard; - } - - if (!(ipv6_addr_type(&hao->addr) & IPV6_ADDR_UNICAST)) { - net_dbg_ratelimited("hao is not an unicast addr: %pI6\n", - &hao->addr); - goto discard; - } - - ret = xfrm6_input_addr(skb, (xfrm_address_t *)&ipv6h->daddr, - (xfrm_address_t *)&hao->addr, IPPROTO_DSTOPTS); - if (unlikely(ret < 0)) - goto discard; - - if (skb_cloned(skb)) { - if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) - goto discard; - - /* update all variable using below by copied skbuff */ - hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + - optoff); - ipv6h = ipv6_hdr(skb); - } - - if (skb->ip_summed == CHECKSUM_COMPLETE) - skb->ip_summed = CHECKSUM_NONE; - - swap(ipv6h->saddr, hao->addr); - - if (skb->tstamp == 0) - __net_timestamp(skb); - - return true; - - discard: - kfree_skb(skb); - return false; -} -#endif - -static const struct tlvtype_proc tlvprocdestopt_lst[] = { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - { - .type = IPV6_TLV_HAO, - .func = ipv6_dest_hao, - }, -#endif - {-1, NULL} -}; - static int ipv6_destopt_rcv(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); @@ -702,122 +614,6 @@ void ipv6_exthdrs_exit(void) inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); } -/********************************** - Hop-by-hop options. - **********************************/ - -/* - * Note: we cannot rely on skb_dst(skb) before we assign it in ip6_route_input(). - */ -static inline struct inet6_dev *ipv6_skb_idev(struct sk_buff *skb) -{ - return skb_dst(skb) ? ip6_dst_idev(skb_dst(skb)) : __in6_dev_get(skb->dev); -} - -static inline struct net *ipv6_skb_net(struct sk_buff *skb) -{ - return skb_dst(skb) ? dev_net(skb_dst(skb)->dev) : dev_net(skb->dev); -} - -/* Router Alert as of RFC 2711 */ - -static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - - if (nh[optoff + 1] == 2) { - IP6CB(skb)->flags |= IP6SKB_ROUTERALERT; - memcpy(&IP6CB(skb)->ra, nh + optoff + 2, sizeof(IP6CB(skb)->ra)); - return true; - } - net_dbg_ratelimited("ipv6_hop_ra: wrong RA length %d\n", - nh[optoff + 1]); - kfree_skb(skb); - return false; -} - -/* Jumbo payload */ - -static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); - struct net *net = ipv6_skb_net(skb); - u32 pkt_len; - - if (nh[optoff + 1] != 4 || (optoff & 3) != 2) { - net_dbg_ratelimited("ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", - nh[optoff+1]); - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - goto drop; - } - - pkt_len = ntohl(*(__be32 *)(nh + optoff + 2)); - if (pkt_len <= IPV6_MAXPLEN) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2); - return false; - } - if (ipv6_hdr(skb)->payload_len) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff); - return false; - } - - if (pkt_len > skb->len - sizeof(struct ipv6hdr)) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INTRUNCATEDPKTS); - goto drop; - } - - if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) - goto drop; - - IP6CB(skb)->flags |= IP6SKB_JUMBOGRAM; - return true; - -drop: - kfree_skb(skb); - return false; -} - -/* CALIPSO RFC 5570 */ - -static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - - if (nh[optoff + 1] < 8) - goto drop; - - if (nh[optoff + 6] * 4 + 8 > nh[optoff + 1]) - goto drop; - - if (!calipso_validate(skb, nh + optoff)) - goto drop; - - return true; - -drop: - kfree_skb(skb); - return false; -} - -static const struct tlvtype_proc tlvprochopopt_lst[] = { - { - .type = IPV6_TLV_ROUTERALERT, - .func = ipv6_hop_ra, - }, - { - .type = IPV6_TLV_JUMBO, - .func = ipv6_hop_jumbo, - }, - { - .type = IPV6_TLV_CALIPSO, - .func = ipv6_hop_calipso, - }, - { -1, } -}; - int ipv6_parse_hopopts(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c new file mode 100644 index 0000000..032e072 --- /dev/null +++ b/net/ipv6/exthdrs_options.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_IPV6_MIP6) +#include +#endif + +/* Destination options header */ + +#if IS_ENABLED(CONFIG_IPV6_MIP6) +static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) +{ + struct ipv6_destopt_hao *hao; + struct inet6_skb_parm *opt = IP6CB(skb); + struct ipv6hdr *ipv6h = ipv6_hdr(skb); + int ret; + + if (opt->dsthao) { + net_dbg_ratelimited("hao duplicated\n"); + goto discard; + } + opt->dsthao = opt->dst1; + opt->dst1 = 0; + + hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff); + + if (hao->length != 16) { + net_dbg_ratelimited("hao invalid option length = %d\n", + hao->length); + goto discard; + } + + if (!(ipv6_addr_type(&hao->addr) & IPV6_ADDR_UNICAST)) { + net_dbg_ratelimited("hao is not an unicast addr: %pI6\n", + &hao->addr); + goto discard; + } + + ret = xfrm6_input_addr(skb, (xfrm_address_t *)&ipv6h->daddr, + (xfrm_address_t *)&hao->addr, IPPROTO_DSTOPTS); + if (unlikely(ret < 0)) + goto discard; + + if (skb_cloned(skb)) { + if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) + goto discard; + + /* update all variable using below by copied skbuff */ + hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + + optoff); + ipv6h = ipv6_hdr(skb); + } + + if (skb->ip_summed == CHECKSUM_COMPLETE) + skb->ip_summed = CHECKSUM_NONE; + + swap(ipv6h->saddr, hao->addr); + + if (skb->tstamp == 0) + __net_timestamp(skb); + + return true; + + discard: + kfree_skb(skb); + return false; +} +#endif + +const struct tlvtype_proc tlvprocdestopt_lst[] = { +#if IS_ENABLED(CONFIG_IPV6_MIP6) + { + .type = IPV6_TLV_HAO, + .func = ipv6_dest_hao, + }, +#endif + {-1, NULL} +}; + +/* Hop-by-hop options */ + +/* Note: we cannot rely on skb_dst(skb) before we assign it in + * ip6_route_input(). + */ +static inline struct inet6_dev *ipv6_skb_idev(struct sk_buff *skb) +{ + return skb_dst(skb) ? ip6_dst_idev(skb_dst(skb)) : + __in6_dev_get(skb->dev); +} + +static inline struct net *ipv6_skb_net(struct sk_buff *skb) +{ + return skb_dst(skb) ? dev_net(skb_dst(skb)->dev) : dev_net(skb->dev); +} + +/* Router Alert as of RFC 2711 */ + +static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + + if (nh[optoff + 1] == 2) { + IP6CB(skb)->flags |= IP6SKB_ROUTERALERT; + memcpy(&IP6CB(skb)->ra, nh + optoff + 2, + sizeof(IP6CB(skb)->ra)); + return true; + } + net_dbg_ratelimited("%s: wrong RA length %d\n", + __func__, nh[optoff + 1]); + kfree_skb(skb); + return false; +} + +/* Jumbo payload */ + +static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); + struct net *net = ipv6_skb_net(skb); + u32 pkt_len; + + if (nh[optoff + 1] != 4 || (optoff & 3) != 2) { + net_dbg_ratelimited("%s: wrong jumbo opt length/alignment %d\n", + __func__, nh[optoff + 1]); + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + goto drop; + } + + pkt_len = ntohl(*(__be32 *)(nh + optoff + 2)); + if (pkt_len <= IPV6_MAXPLEN) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff + 2); + return false; + } + if (ipv6_hdr(skb)->payload_len) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff); + return false; + } + + if (pkt_len > skb->len - sizeof(struct ipv6hdr)) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INTRUNCATEDPKTS); + goto drop; + } + + if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) + goto drop; + + IP6CB(skb)->flags |= IP6SKB_JUMBOGRAM; + return true; + +drop: + kfree_skb(skb); + return false; +} + +/* CALIPSO RFC 5570 */ + +static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + + if (nh[optoff + 1] < 8) + goto drop; + + if (nh[optoff + 6] * 4 + 8 > nh[optoff + 1]) + goto drop; + + if (!calipso_validate(skb, nh + optoff)) + goto drop; + + return true; + +drop: + kfree_skb(skb); + return false; +} + +const struct tlvtype_proc tlvprochopopt_lst[] = { + { + .type = IPV6_TLV_ROUTERALERT, + .func = ipv6_hop_ra, + }, + { + .type = IPV6_TLV_JUMBO, + .func = ipv6_hop_jumbo, + }, + { + .type = IPV6_TLV_CALIPSO, + .func = ipv6_hop_calipso, + }, + { -1, } +}; From patchwork Fri Dec 20 23:38:38 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214371 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="CRdLnHhi"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flZs52spz9sPJ for ; Sat, 21 Dec 2019 10:39:33 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726717AbfLTXja (ORCPT ); Fri, 20 Dec 2019 18:39:30 -0500 Received: from mail-pj1-f67.google.com ([209.85.216.67]:34139 "EHLO mail-pj1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXja (ORCPT ); Fri, 20 Dec 2019 18:39:30 -0500 Received: by mail-pj1-f67.google.com with SMTP id s94so4369865pjc.1 for ; Fri, 20 Dec 2019 15:39:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=5MH+FSuY7wbSJxeTrk2bf9VATemHXvzhP05f6h7xE/I=; b=CRdLnHhib6CLc/WwVfXuCf//NPOPSYf5sSxU5W7b7q68r7yuPotim090hpxZrGlH3s 8S+1rrNw8DJxpJYn6f56hSva0Pi7j+Ujg3fnq5jGQTHqirrHZYaSxmKae9O0n8XWWpLS dWh53ANunMBBVReciMINsmh7Fzi15p/gqBXBaV6ypXlzDt7GH7EFOJ+VKlm517oHXX0v Eo95WjTxM+Z2JikehBZwS9rIg7wBbs9aJKuE5elniij9CjvyKHWA/xceZIsDzF316/yH w+l3tJ/r7jRuK/4Xg6ikJIuNawnDwuRd2tMOG21wH1zPIRoZUFR9Lzp4Xo9Br8BN5KR7 kxJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=5MH+FSuY7wbSJxeTrk2bf9VATemHXvzhP05f6h7xE/I=; b=qrue4wcS6WD3wUMK8eYoLEwWLGTYEXXSfu+LWMW+x6rhLemVCwC4i+696F3DEst5JI QcnwvXrImDSQHzHXyVeoJAZnMDVs3vZjvzKJumMsIIVyXC6pZ3JmuCpUkG6iB6xkAXQs xtd0jjAkbnt7UCkGRXY7B4eA+XjUPtqZXdoaYNmCPtJwftQDPZp711g7/xPA5qH3Dgoj UbJxLre6VBGsBlc6dez6vFC++X92FrqPeqd3n3LbdqVkPYR7MQavu+N8Hf9ceJN7xHmA zqlWbF/vjQ+e4zyKradAPjSKtALgctXtZn7Wfe0BksOwoBszRaXisfuWsWKMt0yAp28l ozRw== X-Gm-Message-State: APjAAAVS1VBQA57C/o4mHY26/1Q+2HbKA1xH+9gNKUZpMZdUlc4A5f4n D5s1BFSft+8Mk97VeHIdg0EnCA== X-Google-Smtp-Source: APXvYqyPlVQO6kDXLp/YQyWrGqAKr3SE2tpIUHU9UdiidNTixALKIWbXQ/r7jn1DJQzp+k7wokOpsg== X-Received: by 2002:a17:90a:ba91:: with SMTP id t17mr19433907pjr.74.1576885169023; Fri, 20 Dec 2019 15:39:29 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.27 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:28 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 3/9] ipeh: Move generic EH functions to exthdrs_common.c Date: Fri, 20 Dec 2019 15:38:38 -0800 Message-Id: <1576885124-14576-4-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Move generic functions in exthdrs.c to new exthdrs_common.c so that exthdrs.c only contains functions that are specific to IPv6 processing, and exthdrs_common.c contains functions that are generic. These functions include those that will be used with IPv4 extension headers. Generic extension header related functions are prefixed by ipeh_. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 12 ++++ include/net/ipv6.h | 9 --- net/dccp/ipv6.c | 2 +- net/ipv6/Makefile | 1 + net/ipv6/calipso.c | 6 +- net/ipv6/exthdrs.c | 138 -------------------------------------------- net/ipv6/exthdrs_common.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/ipv6_sockglue.c | 2 +- net/ipv6/raw.c | 2 +- net/ipv6/tcp_ipv6.c | 2 +- net/ipv6/udp.c | 2 +- net/l2tp/l2tp_ip6.c | 2 +- net/sctp/ipv6.c | 2 +- 13 files changed, 167 insertions(+), 157 deletions(-) create mode 100644 net/ipv6/exthdrs_common.c diff --git a/include/net/ipeh.h b/include/net/ipeh.h index ec2d186..3b24831 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -19,4 +19,16 @@ struct tlvtype_proc { extern const struct tlvtype_proc tlvprocdestopt_lst[]; extern const struct tlvtype_proc tlvprochopopt_lst[]; +struct ipv6_txoptions; +struct ipv6_opt_hdr; + +struct ipv6_txoptions *ipeh_dup_options(struct sock *sk, + struct ipv6_txoptions *opt); +struct ipv6_txoptions *ipeh_renew_options(struct sock *sk, + struct ipv6_txoptions *opt, + int newtype, + struct ipv6_opt_hdr *newopt); +struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, + struct ipv6_txoptions *opt); + #endif /* _NET_IPEH_H */ diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 1d84394..6c4c834 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -429,15 +429,6 @@ int ip6_ra_control(struct sock *sk, int sel); int ipv6_parse_hopopts(struct sk_buff *skb); -struct ipv6_txoptions *ipv6_dup_options(struct sock *sk, - struct ipv6_txoptions *opt); -struct ipv6_txoptions *ipv6_renew_options(struct sock *sk, - struct ipv6_txoptions *opt, - int newtype, - struct ipv6_opt_hdr *newopt); -struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, - struct ipv6_txoptions *opt); - bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb, const struct inet6_skb_parm *opt); struct ipv6_txoptions *ipv6_update_options(struct sock *sk, diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c index 1e5e08c..deed6efa 100644 --- a/net/dccp/ipv6.c +++ b/net/dccp/ipv6.c @@ -515,7 +515,7 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk, if (!opt) opt = rcu_dereference(np->opt); if (opt) { - opt = ipv6_dup_options(newsk, opt); + opt = ipeh_dup_options(newsk, opt); RCU_INIT_POINTER(newnp->opt, opt); } inet_csk(newsk)->icsk_ext_hdr_len = 0; diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index df3919b..3033d3e 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_IPV6_SIT) += sit.o obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o obj-$(CONFIG_IPV6_GRE) += ip6_gre.o obj-$(CONFIG_IPV6_FOU) += fou6.o +obj-$(CONFIG_IPV6) += exthdrs_common.o obj-y += addrconf_core.o exthdrs_core.o ip6_checksum.o ip6_icmp.o obj-$(CONFIG_INET) += output_core.o protocol.o $(ipv6-offload) diff --git a/net/ipv6/calipso.c b/net/ipv6/calipso.c index 221c81f..9c84848 100644 --- a/net/ipv6/calipso.c +++ b/net/ipv6/calipso.c @@ -785,7 +785,7 @@ static int calipso_opt_update(struct sock *sk, struct ipv6_opt_hdr *hop) { struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)), *txopts; - txopts = ipv6_renew_options(sk, old, IPV6_HOPOPTS, hop); + txopts = ipeh_renew_options(sk, old, IPV6_HOPOPTS, hop); txopt_put(old); if (IS_ERR(txopts)) return PTR_ERR(txopts); @@ -1207,7 +1207,7 @@ static int calipso_req_setattr(struct request_sock *req, if (IS_ERR(new)) return PTR_ERR(new); - txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new); + txopts = ipeh_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new); kfree(new); @@ -1244,7 +1244,7 @@ static void calipso_req_delattr(struct request_sock *req) if (calipso_opt_del(req_inet->ipv6_opt->hopopt, &new)) return; /* Nothing to do */ - txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new); + txopts = ipeh_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new); if (!IS_ERR(txopts)) { txopts = xchg(&req_inet->ipv6_opt, txopts); diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 09053a4..ea87017 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -789,144 +789,6 @@ void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *pr } EXPORT_SYMBOL(ipv6_push_frag_opts); -struct ipv6_txoptions * -ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt) -{ - struct ipv6_txoptions *opt2; - - opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC); - if (opt2) { - long dif = (char *)opt2 - (char *)opt; - memcpy(opt2, opt, opt->tot_len); - if (opt2->hopopt) - *((char **)&opt2->hopopt) += dif; - if (opt2->dst0opt) - *((char **)&opt2->dst0opt) += dif; - if (opt2->dst1opt) - *((char **)&opt2->dst1opt) += dif; - if (opt2->srcrt) - *((char **)&opt2->srcrt) += dif; - refcount_set(&opt2->refcnt, 1); - } - return opt2; -} -EXPORT_SYMBOL_GPL(ipv6_dup_options); - -static void ipv6_renew_option(int renewtype, - struct ipv6_opt_hdr **dest, - struct ipv6_opt_hdr *old, - struct ipv6_opt_hdr *new, - int newtype, char **p) -{ - struct ipv6_opt_hdr *src; - - src = (renewtype == newtype ? new : old); - if (!src) - return; - - memcpy(*p, src, ipv6_optlen(src)); - *dest = (struct ipv6_opt_hdr *)*p; - *p += CMSG_ALIGN(ipv6_optlen(*dest)); -} - -/** - * ipv6_renew_options - replace a specific ext hdr with a new one. - * - * @sk: sock from which to allocate memory - * @opt: original options - * @newtype: option type to replace in @opt - * @newopt: new option of type @newtype to replace (user-mem) - * @newoptlen: length of @newopt - * - * Returns a new set of options which is a copy of @opt with the - * option type @newtype replaced with @newopt. - * - * @opt may be NULL, in which case a new set of options is returned - * containing just @newopt. - * - * @newopt may be NULL, in which case the specified option type is - * not copied into the new set of options. - * - * The new set of options is allocated from the socket option memory - * buffer of @sk. - */ -struct ipv6_txoptions * -ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt, - int newtype, struct ipv6_opt_hdr *newopt) -{ - int tot_len = 0; - char *p; - struct ipv6_txoptions *opt2; - - if (opt) { - if (newtype != IPV6_HOPOPTS && opt->hopopt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->hopopt)); - if (newtype != IPV6_RTHDRDSTOPTS && opt->dst0opt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst0opt)); - if (newtype != IPV6_RTHDR && opt->srcrt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->srcrt)); - if (newtype != IPV6_DSTOPTS && opt->dst1opt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt)); - } - - if (newopt) - tot_len += CMSG_ALIGN(ipv6_optlen(newopt)); - - if (!tot_len) - return NULL; - - tot_len += sizeof(*opt2); - opt2 = sock_kmalloc(sk, tot_len, GFP_ATOMIC); - if (!opt2) - return ERR_PTR(-ENOBUFS); - - memset(opt2, 0, tot_len); - refcount_set(&opt2->refcnt, 1); - opt2->tot_len = tot_len; - p = (char *)(opt2 + 1); - - ipv6_renew_option(IPV6_HOPOPTS, &opt2->hopopt, - (opt ? opt->hopopt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt, - (opt ? opt->dst0opt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_RTHDR, - (struct ipv6_opt_hdr **)&opt2->srcrt, - (opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_DSTOPTS, &opt2->dst1opt, - (opt ? opt->dst1opt : NULL), - newopt, newtype, &p); - - opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) + - (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) + - (opt2->srcrt ? ipv6_optlen(opt2->srcrt) : 0); - opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0); - - return opt2; -} - -struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, - struct ipv6_txoptions *opt) -{ - /* - * ignore the dest before srcrt unless srcrt is being included. - * --yoshfuji - */ - if (opt && opt->dst0opt && !opt->srcrt) { - if (opt_space != opt) { - memcpy(opt_space, opt, sizeof(*opt_space)); - opt = opt_space; - } - opt->opt_nflen -= ipv6_optlen(opt->dst0opt); - opt->dst0opt = NULL; - } - - return opt; -} -EXPORT_SYMBOL_GPL(ipv6_fixup_options); - /** * fl6_update_dst - update flowi destination address with info given * by srcrt option, if any. diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c new file mode 100644 index 0000000..2c68184 --- /dev/null +++ b/net/ipv6/exthdrs_common.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Extension header and TLV library code that is not specific to IPv6. */ +#include +#include + +struct ipv6_txoptions * +ipeh_dup_options(struct sock *sk, struct ipv6_txoptions *opt) +{ + struct ipv6_txoptions *opt2; + + opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC); + if (opt2) { + long dif = (char *)opt2 - (char *)opt; + + memcpy(opt2, opt, opt->tot_len); + if (opt2->hopopt) + *((char **)&opt2->hopopt) += dif; + if (opt2->dst0opt) + *((char **)&opt2->dst0opt) += dif; + if (opt2->dst1opt) + *((char **)&opt2->dst1opt) += dif; + if (opt2->srcrt) + *((char **)&opt2->srcrt) += dif; + refcount_set(&opt2->refcnt, 1); + } + return opt2; +} +EXPORT_SYMBOL_GPL(ipeh_dup_options); + +static void ipeh_renew_option(int renewtype, + struct ipv6_opt_hdr **dest, + struct ipv6_opt_hdr *old, + struct ipv6_opt_hdr *new, + int newtype, char **p) +{ + struct ipv6_opt_hdr *src; + + src = (renewtype == newtype ? new : old); + if (!src) + return; + + memcpy(*p, src, ipv6_optlen(src)); + *dest = (struct ipv6_opt_hdr *)*p; + *p += CMSG_ALIGN(ipv6_optlen(*dest)); +} + +/** + * ipeh_renew_options - replace a specific ext hdr with a new one. + * + * @sk: sock from which to allocate memory + * @opt: original options + * @newtype: option type to replace in @opt + * @newopt: new option of type @newtype to replace (user-mem) + * @newoptlen: length of @newopt + * + * Returns a new set of options which is a copy of @opt with the + * option type @newtype replaced with @newopt. + * + * @opt may be NULL, in which case a new set of options is returned + * containing just @newopt. + * + * @newopt may be NULL, in which case the specified option type is + * not copied into the new set of options. + * + * The new set of options is allocated from the socket option memory + * buffer of @sk. + */ +struct ipv6_txoptions * +ipeh_renew_options(struct sock *sk, struct ipv6_txoptions *opt, + int newtype, struct ipv6_opt_hdr *newopt) +{ + int tot_len = 0; + char *p; + struct ipv6_txoptions *opt2; + + if (opt) { + if (newtype != IPV6_HOPOPTS && opt->hopopt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->hopopt)); + if (newtype != IPV6_RTHDRDSTOPTS && opt->dst0opt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst0opt)); + if (newtype != IPV6_RTHDR && opt->srcrt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->srcrt)); + if (newtype != IPV6_DSTOPTS && opt->dst1opt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt)); + } + + if (newopt) + tot_len += CMSG_ALIGN(ipv6_optlen(newopt)); + + if (!tot_len) + return NULL; + + tot_len += sizeof(*opt2); + opt2 = sock_kmalloc(sk, tot_len, GFP_ATOMIC); + if (!opt2) + return ERR_PTR(-ENOBUFS); + + memset(opt2, 0, tot_len); + refcount_set(&opt2->refcnt, 1); + opt2->tot_len = tot_len; + p = (char *)(opt2 + 1); + + ipeh_renew_option(IPV6_HOPOPTS, &opt2->hopopt, + (opt ? opt->hopopt : NULL), + newopt, newtype, &p); + ipeh_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt, + (opt ? opt->dst0opt : NULL), + newopt, newtype, &p); + ipeh_renew_option(IPV6_RTHDR, + (struct ipv6_opt_hdr **)&opt2->srcrt, + (opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL), + newopt, newtype, &p); + ipeh_renew_option(IPV6_DSTOPTS, &opt2->dst1opt, + (opt ? opt->dst1opt : NULL), + newopt, newtype, &p); + + opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) + + (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) + + (opt2->srcrt ? ipv6_optlen(opt2->srcrt) : 0); + opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0); + + return opt2; +} +EXPORT_SYMBOL(ipeh_renew_options); + +struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, + struct ipv6_txoptions *opt) +{ + /* ignore the dest before srcrt unless srcrt is being included. + * --yoshfuji + */ + if (opt && opt->dst0opt && !opt->srcrt) { + if (opt_space != opt) { + memcpy(opt_space, opt, sizeof(*opt_space)); + opt = opt_space; + } + opt->opt_nflen -= ipv6_optlen(opt->dst0opt); + opt->dst0opt = NULL; + } + + return opt; +} +EXPORT_SYMBOL_GPL(ipeh_fixup_options); diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 79fc012..7810988 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -427,7 +427,7 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk)); - opt = ipv6_renew_options(sk, opt, optname, new); + opt = ipeh_renew_options(sk, opt, optname, new); kfree(new); if (IS_ERR(opt)) { retv = PTR_ERR(opt); diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index dfe5e60..1bffc05 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -889,7 +889,7 @@ static int rawv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) } if (flowlabel) opt = fl6_merge_options(&opt_space, flowlabel, opt); - opt = ipv6_fixup_options(&opt_space, opt); + opt = ipeh_fixup_options(&opt_space, opt); fl6.flowi6_proto = proto; fl6.flowi6_mark = ipc6.sockc.mark; diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index df5fd91..406ffe0 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1250,7 +1250,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * if (!opt) opt = rcu_dereference(np->opt); if (opt) { - opt = ipv6_dup_options(newsk, opt); + opt = ipeh_dup_options(newsk, opt); RCU_INIT_POINTER(newnp->opt, opt); } inet_csk(newsk)->icsk_ext_hdr_len = 0; diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 9fec580..426fff3 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -1391,7 +1391,7 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) } if (flowlabel) opt = fl6_merge_options(&opt_space, flowlabel, opt); - opt = ipv6_fixup_options(&opt_space, opt); + opt = ipeh_fixup_options(&opt_space, opt); ipc6.opt = opt; fl6.flowi6_proto = sk->sk_protocol; diff --git a/net/l2tp/l2tp_ip6.c b/net/l2tp/l2tp_ip6.c index d148766..3d5ac76 100644 --- a/net/l2tp/l2tp_ip6.c +++ b/net/l2tp/l2tp_ip6.c @@ -590,7 +590,7 @@ static int l2tp_ip6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) } if (flowlabel) opt = fl6_merge_options(&opt_space, flowlabel, opt); - opt = ipv6_fixup_options(&opt_space, opt); + opt = ipeh_fixup_options(&opt_space, opt); ipc6.opt = opt; fl6.flowi6_proto = sk->sk_protocol; diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index bc734cf..279d2d2 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c @@ -441,7 +441,7 @@ static void sctp_v6_copy_ip_options(struct sock *sk, struct sock *newsk) rcu_read_lock(); opt = rcu_dereference(np->opt); if (opt) { - opt = ipv6_dup_options(newsk, opt); + opt = ipeh_dup_options(newsk, opt); if (!opt) pr_err("%s: Failed to copy ip options\n", __func__); } From patchwork Fri Dec 20 23:38:39 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214372 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="El9KClSj"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flZt56sKz9sPW for ; Sat, 21 Dec 2019 10:39:34 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726750AbfLTXjc (ORCPT ); Fri, 20 Dec 2019 18:39:32 -0500 Received: from mail-pj1-f65.google.com ([209.85.216.65]:52475 "EHLO mail-pj1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXjc (ORCPT ); Fri, 20 Dec 2019 18:39:32 -0500 Received: by mail-pj1-f65.google.com with SMTP id w23so4773467pjd.2 for ; Fri, 20 Dec 2019 15:39:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=8bnCEFhTLD7HUdTgeeE3FhxQ9K8LBjdeLIzCTtaMk5A=; b=El9KClSjw2zrfjA+MjVtjrj9pLYiUCQ4trk5vVe02f1GgAO5rDZ8GzuLZ8p86SArVk wGeEM40AWUXwBSINRy56GAgJQ8I9vkiEueBxDc+zsp7oj3tW5aDsCad5sgGap+iohNY9 6ovW34qPR2HVEw1Mm3deUjPG9IQz1bFNFIH4bh4ppqOReSpXIE9Qd7ZeixnzbqYOAYU2 X8sHMTAuPw3mNgx3Z2abigL61P+DBICQh94yUzq2a7+iShCHNg41Bhv3+DK/0xmvoZcc v6QSThDOovpgnTzAD42+jYAjjHt4ioCUSgMxJOeXTpTBub5Pg0kfdbW6s/ZJx+EijlK0 IUyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=8bnCEFhTLD7HUdTgeeE3FhxQ9K8LBjdeLIzCTtaMk5A=; b=UHyYOjakU16eoJWX+qbdlvzgNJBM39F+XwyjlhS2I7j1SAU6e0r8EUyKrFQd1geSQU UhP9On85YUOZWisHE1ZWMlebKGacDQrwsInJa3NmDrxbEaCFtKxbQhPAnD6TNjE6ou7S S2wv9E2tzPUphGdI0OBb1qDtNdwwjpk4SUggGs/k7JkFphkKfc4kBZ71KOoeTUBGUcWt 4bsxbc/H95h/9GBr1X3UOYaubX82qRy3hQZmmjirKvCf8CVmdwxsTCJIWjgybm2r6qN3 fCTCL42FglER9M+YIbIy1tdVVBeyJDgZT54um33nlV8wBmzKKU1CMGjF2pfrxcj20346 c77g== X-Gm-Message-State: APjAAAWbZDFSM1pOp0iAYl6nFvy98MWo1PVTEcvkXyMGb0drdejjY5Y8 OG+vRczXocoL5PHlyQLeEXjBNKKdRkY= X-Google-Smtp-Source: APXvYqyOJ05dJ4W+sgDdzAx5pOCZpDyn2+7f4LRJQQMOoLU6Dec3IxfHaA8OP5ROJKVOfXxTGeGozg== X-Received: by 2002:a17:90a:e28e:: with SMTP id d14mr19796082pjz.56.1576885171250; Fri, 20 Dec 2019 15:39:31 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.30 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:30 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 4/9] ipeh: Generic TLV parser Date: Fri, 20 Dec 2019 15:38:39 -0800 Message-Id: <1576885124-14576-5-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Create a generic TLV parser. This will be used with various extension headers that carry options including Destination, Hop-by-Hop, Segment Routing TLVs, and other cases of simple stateless TLV parsing. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 11 ++++ net/ipv6/exthdrs.c | 142 ++-------------------------------------------- net/ipv6/exthdrs_common.c | 137 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 136 deletions(-) diff --git a/include/net/ipeh.h b/include/net/ipeh.h index 3b24831..81f92f8 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -31,4 +31,15 @@ struct ipv6_txoptions *ipeh_renew_options(struct sock *sk, struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); +/* The generic TLV parser assumes that the type value of PAD1 is 0, and PADN + * is 1. This is true for IPv6 Destination and Hop-by-Hop Options. For Segment + * Routing TLVs, PAD1 is also 0, however PADN is 4 so the latter necessitates + * some change to the parser to support Segment Routing TLVs. + */ +#define IPEH_TLV_PAD1 0 +#define IPEH_TLV_PADN 1 + +bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, + int max_count, int off, int len); + #endif /* _NET_IPEH_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index ea87017..b094efe 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -54,138 +54,6 @@ Generic functions *********************/ -/* An unknown option is detected, decide what to do */ - -static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, - bool disallow_unknowns) -{ - if (disallow_unknowns) { - /* If unknown TLVs are disallowed by configuration - * then always silently drop packet. Note this also - * means no ICMP parameter problem is sent which - * could be a good property to mitigate a reflection DOS - * attack. - */ - - goto drop; - } - - switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) { - case 0: /* ignore */ - return true; - - case 1: /* drop packet */ - break; - - case 3: /* Send ICMP if not a multicast address and drop packet */ - /* Actually, it is redundant check. icmp_send - will recheck in any case. - */ - if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) - break; - /* fall through */ - case 2: /* send ICMP PARM PROB regardless and drop packet */ - icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); - return false; - } - -drop: - kfree_skb(skb); - return false; -} - -/* Parse tlv encoded option header (hop-by-hop or destination) */ - -static bool ip6_parse_tlv(const struct tlvtype_proc *procs, - struct sk_buff *skb, - int max_count) -{ - int len = (skb_transport_header(skb)[1] + 1) << 3; - const unsigned char *nh = skb_network_header(skb); - int off = skb_network_header_len(skb); - const struct tlvtype_proc *curr; - bool disallow_unknowns = false; - int tlv_count = 0; - int padlen = 0; - - if (unlikely(max_count < 0)) { - disallow_unknowns = true; - max_count = -max_count; - } - - if (skb_transport_offset(skb) + len > skb_headlen(skb)) - goto bad; - - off += 2; - len -= 2; - - while (len > 0) { - int optlen = nh[off + 1] + 2; - int i; - - switch (nh[off]) { - case IPV6_TLV_PAD1: - optlen = 1; - padlen++; - if (padlen > 7) - goto bad; - break; - - case IPV6_TLV_PADN: - /* RFC 2460 states that the purpose of PadN is - * to align the containing header to multiples - * of 8. 7 is therefore the highest valid value. - * See also RFC 4942, Section 2.1.9.5. - */ - padlen += optlen; - if (padlen > 7) - goto bad; - /* RFC 4942 recommends receiving hosts to - * actively check PadN payload to contain - * only zeroes. - */ - for (i = 2; i < optlen; i++) { - if (nh[off + i] != 0) - goto bad; - } - break; - - default: /* Other TLV code so scan list */ - if (optlen > len) - goto bad; - - tlv_count++; - if (tlv_count > max_count) - goto bad; - - for (curr = procs; curr->type >= 0; curr++) { - if (curr->type == nh[off]) { - /* type specific length/alignment - checks will be performed in the - func(). */ - if (curr->func(skb, off) == false) - return false; - break; - } - } - if (curr->type < 0 && - !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) - return false; - - padlen = 0; - break; - } - off += optlen; - len -= optlen; - } - - if (len == 0) - return true; -bad: - kfree_skb(skb); - return false; -} - static int ipv6_destopt_rcv(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); @@ -216,8 +84,9 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) dstbuf = opt->dst1; #endif - if (ip6_parse_tlv(tlvprocdestopt_lst, skb, - init_net.ipv6.sysctl.max_dst_opts_cnt)) { + if (ipeh_parse_tlv(tlvprocdestopt_lst, skb, + init_net.ipv6.sysctl.max_dst_opts_cnt, + 2, extlen - 2)) { skb->transport_header += extlen; opt = IP6CB(skb); #if IS_ENABLED(CONFIG_IPV6_MIP6) @@ -643,8 +512,9 @@ int ipv6_parse_hopopts(struct sk_buff *skb) goto fail_and_free; opt->flags |= IP6SKB_HOPBYHOP; - if (ip6_parse_tlv(tlvprochopopt_lst, skb, - init_net.ipv6.sysctl.max_hbh_opts_cnt)) { + if (ipeh_parse_tlv(tlvprochopopt_lst, skb, + init_net.ipv6.sysctl.max_hbh_opts_cnt, + 2, extlen - 2)) { skb->transport_header += extlen; opt = IP6CB(skb); opt->nhoff = sizeof(struct ipv6hdr); diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 2c68184..d0c4ec3 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -142,3 +142,140 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, return opt; } EXPORT_SYMBOL_GPL(ipeh_fixup_options); + +/* An unknown option is detected, decide what to do */ + +static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, + bool disallow_unknowns) +{ + if (disallow_unknowns) { + /* If unknown TLVs are disallowed by configuration + * then always silently drop packet. Note this also + * means no ICMP parameter problem is sent which + * could be a good property to mitigate a reflection DOS + * attack. + */ + + goto drop; + } + + switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) { + case 0: /* ignore */ + return true; + + case 1: /* drop packet */ + break; + + case 3: /* Send ICMP if not a multicast address and drop packet */ + /* Actually, it is redundant check. icmp_send + * will recheck in any case. + */ + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) + break; + /* fall through */ + case 2: /* send ICMP PARM PROB regardless and drop packet */ + icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); + return false; + } + +drop: + kfree_skb(skb); + return false; +} + +/* Generic extension header TLV parser + * + * Arguments: + * - skb_transport_header points to the extension header containing options + * - off is offset from skb_transport_header where first TLV is + * - len is length of TLV block + */ +bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, + int max_count, int off, int len) +{ + const unsigned char *nh = skb_network_header(skb); + const struct tlvtype_proc *curr; + bool disallow_unknowns = false; + int tlv_count = 0; + int padlen = 0; + + if (unlikely(max_count < 0)) { + disallow_unknowns = true; + max_count = -max_count; + } + + if (skb_transport_offset(skb) + off + len > skb_headlen(skb)) + goto bad; + + /* ops function based offset on network header */ + off += skb_network_header_len(skb); + + while (len > 0) { + int optlen = nh[off + 1] + 2; + int i; + + switch (nh[off]) { + case IPEH_TLV_PAD1: + optlen = 1; + padlen++; + if (padlen > 7) + goto bad; + break; + + case IPEH_TLV_PADN: + /* RFC 2460 states that the purpose of PadN is + * to align the containing header to multiples + * of 8. 7 is therefore the highest valid value. + * See also RFC 4942, Section 2.1.9.5. + */ + padlen += optlen; + if (padlen > 7) + goto bad; + + /* RFC 4942 recommends receiving hosts to + * actively check PadN payload to contain + * only zeroes. + */ + for (i = 2; i < optlen; i++) { + if (nh[off + i] != 0) + goto bad; + } + break; + + default: /* Other TLV code so scan list */ + if (optlen > len) + goto bad; + + tlv_count++; + if (tlv_count > max_count) + goto bad; + + for (curr = procs; curr->type >= 0; curr++) { + if (curr->type == nh[off]) { + /* type specific length/alignment + * checks will be performed in the + * func(). + */ + if (curr->func(skb, off) == false) + return false; + break; + } + } + if (curr->type < 0 && + !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) + return false; + + padlen = 0; + break; + } + off += optlen; + len -= optlen; + } + + if (len == 0) + return true; +bad: + kfree_skb(skb); + return false; +} +EXPORT_SYMBOL(ipeh_parse_tlv); From patchwork Fri Dec 20 23:38:40 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214373 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="QL3oX++X"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flZx5nwdz9s29 for ; Sat, 21 Dec 2019 10:39:37 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726783AbfLTXjg (ORCPT ); Fri, 20 Dec 2019 18:39:36 -0500 Received: from mail-pg1-f194.google.com ([209.85.215.194]:39286 "EHLO mail-pg1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXje (ORCPT ); Fri, 20 Dec 2019 18:39:34 -0500 Received: by mail-pg1-f194.google.com with SMTP id b137so5700383pga.6 for ; Fri, 20 Dec 2019 15:39:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=PF2xxBw/8ZTqgRxuoSlACHBaNaHpk9hBOATBfltk5zo=; b=QL3oX++XcJeBmVbfS7+grUbOPv8k0zN7KCcKBJ7SZR/FnJM4uwR7rbpiJ/KVoRSQv0 Z5U/hhE7rxPkkKc1wYL/MXSpk7fFGLq4c0nG3y75+T+Fej1bJNWx7IWw9zK+Mmvswx5E qZIKBPY3hE0ZyCNjfZpZBHhk7+iW8WN/HP5Wz5h9YUhLiObS71cKHEi9u/nGz//0ndQt xCeV2MdMxSjcrOBZg1UUiE9909Jsw9SKAXG4VIFR3W1fac/1enNULVkkPCKry2OdwC2t yLIMrKiFzWSmsD0//9UJxtDC/t8YgWEKXsu9v9HSnStNpKezQiJKpwgCTSQ9SevhGlp5 qauA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=PF2xxBw/8ZTqgRxuoSlACHBaNaHpk9hBOATBfltk5zo=; b=NwC68i3BFSXyaxM7dQpQn6bwvT7TH7WWADi2EjgYHMewmaxy8VLZgCta9I9NwPPKrg YMvW98omDJdOzwoQCVyiO7XNBhNPAbeSzCYZrHL2OUy7i3yhu8QCZlZ32qJxmIocwyho BHAG1gJJMtrI3FlsmRLhd0z9gX4cm12uMfuilZFLoCQe/dRrQEq1iHWZh/7riMYUf0Ae OV+Ok1k4jHbTJk137Z/edh/G/3VfgrjlbgmBUa3UZukax1avA8kTxcEYXIS1XXueFhIV gzZ4ONpTZXx72VtTSXexDqlKyfMMmu3HppINoM4ZTRMGOb7DJTYA/sBR+1dhksEHexom 1f1w== X-Gm-Message-State: APjAAAUZ5m45FGNfYTxlRerj1BwmmaiDKuUbZR+flptc/wqH6aqY0MJ+ hpg5YxWzsM87uhWw4hTooDxtNA== X-Google-Smtp-Source: APXvYqx4t4wV/isLHY5BjdKQ31Ywu4Ozx7AawIZxirobE2zvmCsZzbh9d00PpaqIN+CrvQTm7h6cZw== X-Received: by 2002:a63:1c13:: with SMTP id c19mr17866481pgc.450.1576885173373; Fri, 20 Dec 2019 15:39:33 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:32 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 5/9] ipeh: Add callback to ipeh_parse_tlv to handle errors Date: Fri, 20 Dec 2019 15:38:40 -0800 Message-Id: <1576885124-14576-6-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Add a callback function to ipeh_parse_tlv so that the caller can handler parsing errors in an appropriate way. The enum ipeh_parse_errors contains the various errors and the particular error is an argument of the callback. ipv6_parse_error is the callback function for parsing IPv6 TLVs. This mostly subsumes the functionality of ip6_tlvopt_unknown which is removed. The callback is called at various points in ipeh_parse_tlv when an error is encountered. If the callback returns false then the packet is discarded, else on true being returned the error is not fatal and TLV processing continues (for instance, an unknown TLV is to be ignored). Signed-off-by: Tom Herbert --- include/net/ipeh.h | 17 +++++++++- net/ipv6/exthdrs.c | 59 ++++++++++++++++++++++++++++++++-- net/ipv6/exthdrs_common.c | 80 ++++++++++++++++++----------------------------- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/include/net/ipeh.h b/include/net/ipeh.h index 81f92f8..3d2bec6 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -31,6 +31,19 @@ struct ipv6_txoptions *ipeh_renew_options(struct sock *sk, struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); +/* Generic extension header TLV parser */ + +enum ipeh_parse_errors { + IPEH_PARSE_ERR_PAD1, /* Excessive PAD1 */ + IPEH_PARSE_ERR_PADN, /* Excessive PADN */ + IPEH_PARSE_ERR_PADNZ, /* Non-zero padding data */ + IPEH_PARSE_ERR_EH_TOOBIG, /* Length of EH exceeds limit */ + IPEH_PARSE_ERR_OPT_TOOBIG, /* Option size exceeds limit */ + IPEH_PARSE_ERR_OPT_TOOMANY, /* Option count exceeds limit */ + IPEH_PARSE_ERR_OPT_UNK_DISALW, /* Unknown option disallowed */ + IPEH_PARSE_ERR_OPT_UNK, /* Unknown option */ +}; + /* The generic TLV parser assumes that the type value of PAD1 is 0, and PADN * is 1. This is true for IPv6 Destination and Hop-by-Hop Options. For Segment * Routing TLVs, PAD1 is also 0, however PADN is 4 so the latter necessitates @@ -40,6 +53,8 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, #define IPEH_TLV_PADN 1 bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, - int max_count, int off, int len); + int max_count, int off, int len, + bool (*parse_error)(struct sk_buff *skb, + int off, enum ipeh_parse_errors error)); #endif /* _NET_IPEH_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index b094efe..d36ef0c 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -54,6 +54,61 @@ Generic functions *********************/ +/* Handle parse errors from ipeh generic TLV parser */ +static bool ipv6_parse_error(struct sk_buff *skb, int off, + enum ipeh_parse_errors error) +{ + switch (error) { + case IPEH_PARSE_ERR_OPT_UNK_DISALW: + /* If unknown TLVs are disallowed by configuration + * then always silently drop packet. Note this also + * means no ICMP parameter problem is sent which + * could be a good property to mitigate a reflection DOS + * attack. + */ + + break; + case IPEH_PARSE_ERR_OPT_UNK: + /* High order two bits of option type indicate action to + * take on unrecognized option. + */ + switch ((skb_network_header(skb)[off] & 0xC0) >> 6) { + case 0: + /* ignore */ + return true; + + case 1: /* drop packet */ + break; + + case 3: /* Send ICMP if not a multicast address and drop packet + * + * Actually, it is redundant check. icmp_send + * will recheck in any case. + */ + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) + break; + + /* fall through */ + case 2: /* send ICMP PARM PROB regardless and drop packet */ + icmpv6_send(skb, ICMPV6_PARAMPROB, + ICMPV6_UNK_OPTION, off); + break; + } + break; + default: + /* Silent drop on other errors */ + + break; + } + + /* Will be dropping packet */ + + __IP6_INC_STATS(dev_net(skb->dev), __in6_dev_get(skb->dev), + IPSTATS_MIB_INHDRERRORS); + + return false; +} + static int ipv6_destopt_rcv(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); @@ -86,7 +141,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) if (ipeh_parse_tlv(tlvprocdestopt_lst, skb, init_net.ipv6.sysctl.max_dst_opts_cnt, - 2, extlen - 2)) { + 2, extlen - 2, ipv6_parse_error)) { skb->transport_header += extlen; opt = IP6CB(skb); #if IS_ENABLED(CONFIG_IPV6_MIP6) @@ -514,7 +569,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb) opt->flags |= IP6SKB_HOPBYHOP; if (ipeh_parse_tlv(tlvprochopopt_lst, skb, init_net.ipv6.sysctl.max_hbh_opts_cnt, - 2, extlen - 2)) { + 2, extlen - 2, ipv6_parse_error)) { skb->transport_header += extlen; opt = IP6CB(skb); opt->nhoff = sizeof(struct ipv6hdr); diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index d0c4ec3..4d8a799 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -143,55 +143,18 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, } EXPORT_SYMBOL_GPL(ipeh_fixup_options); -/* An unknown option is detected, decide what to do */ - -static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, - bool disallow_unknowns) -{ - if (disallow_unknowns) { - /* If unknown TLVs are disallowed by configuration - * then always silently drop packet. Note this also - * means no ICMP parameter problem is sent which - * could be a good property to mitigate a reflection DOS - * attack. - */ - - goto drop; - } - - switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) { - case 0: /* ignore */ - return true; - - case 1: /* drop packet */ - break; - - case 3: /* Send ICMP if not a multicast address and drop packet */ - /* Actually, it is redundant check. icmp_send - * will recheck in any case. - */ - if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) - break; - /* fall through */ - case 2: /* send ICMP PARM PROB regardless and drop packet */ - icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); - return false; - } - -drop: - kfree_skb(skb); - return false; -} - /* Generic extension header TLV parser * * Arguments: * - skb_transport_header points to the extension header containing options * - off is offset from skb_transport_header where first TLV is * - len is length of TLV block + * - parse_error is protocol specific function to handle parser errors */ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, - int max_count, int off, int len) + int max_count, int off, int len, + bool (*parse_error)(struct sk_buff *skb, + int off, enum ipeh_parse_errors error)) { const unsigned char *nh = skb_network_header(skb); const struct tlvtype_proc *curr; @@ -204,8 +167,15 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, max_count = -max_count; } - if (skb_transport_offset(skb) + off + len > skb_headlen(skb)) - goto bad; + if (skb_transport_offset(skb) + off + len > skb_headlen(skb)) { + if (!parse_error(skb, skb_transport_offset(skb), + IPEH_PARSE_ERR_EH_TOOBIG)) { + kfree_skb(skb); + return false; + } + + len = skb_headlen(skb) - skb_transport_offset(skb) - off; + } /* ops function based offset on network header */ off += skb_network_header_len(skb); @@ -218,8 +188,10 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, case IPEH_TLV_PAD1: optlen = 1; padlen++; - if (padlen > 7) + if (padlen > 7 && + !parse_error(skb, off, IPEH_PARSE_ERR_PAD1)) goto bad; + break; case IPEH_TLV_PADN: @@ -229,7 +201,8 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, * See also RFC 4942, Section 2.1.9.5. */ padlen += optlen; - if (padlen > 7) + if (padlen > 7 && + !parse_error(skb, off, IPEH_PARSE_ERR_PADN)) goto bad; /* RFC 4942 recommends receiving hosts to @@ -237,17 +210,21 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, * only zeroes. */ for (i = 2; i < optlen; i++) { - if (nh[off + i] != 0) + if (nh[off + i] != 0 && + !parse_error(skb, off + i, + IPEH_PARSE_ERR_PADNZ)) goto bad; } break; default: /* Other TLV code so scan list */ - if (optlen > len) + if (optlen > len && + !parse_error(skb, off, IPEH_PARSE_ERR_OPT_TOOBIG)) goto bad; tlv_count++; - if (tlv_count > max_count) + if (tlv_count > max_count && + !parse_error(skb, off, IPEH_PARSE_ERR_OPT_TOOMANY)) goto bad; for (curr = procs; curr->type >= 0; curr++) { @@ -262,8 +239,11 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, } } if (curr->type < 0 && - !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) - return false; + !parse_error(skb, off, + disallow_unknowns ? + IPEH_PARSE_ERR_OPT_UNK_DISALW : + IPEH_PARSE_ERR_OPT_UNK)) + goto bad; padlen = 0; break; From patchwork Fri Dec 20 23:38:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214374 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="JEhJbrjo"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flb02yjcz9s29 for ; Sat, 21 Dec 2019 10:39:40 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726786AbfLTXji (ORCPT ); Fri, 20 Dec 2019 18:39:38 -0500 Received: from mail-pj1-f65.google.com ([209.85.216.65]:53327 "EHLO mail-pj1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXjh (ORCPT ); Fri, 20 Dec 2019 18:39:37 -0500 Received: by mail-pj1-f65.google.com with SMTP id n96so4779984pjc.3 for ; Fri, 20 Dec 2019 15:39:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=HJ4laIcfGu3dph281bLiMf+m7CSf6SeAvVChcp9L+Ak=; b=JEhJbrjoMR7tQUxA6p2UE8MVdVTZHaH4QQvfQBOxqs5w+RgQoucVPB5I3K46G7Gnq2 iIPd7Ix1eSu6X/gm3hsPGMgB6Nzyr8bP4m0eX9efvIU9sn9zRyse/sLzyzZPKO6XssRD VcZiODYj7+nou7XyCVyrMmjjyQQZiAQ7G0HC7bsczkjni3sDi/L+aycply3A9TWNfN2u UR4OSdkfg3Ftgex6TUMtFE86yBWh0dCwauT0TM+7rkS0Ca8trNWI57C60yFy+9U4CSaX X86Bp1jVmy9DvJU14GmI2+V1rDpY0F2kmC05nMLgEpxjNwQUmEehDoqXvXOQJd4ClQZ5 +dBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=HJ4laIcfGu3dph281bLiMf+m7CSf6SeAvVChcp9L+Ak=; b=Pz6xOIgET3ppV1Zo0chOEdbOrmAJiyKIYwMmxDFWynPzeqBSGItsuhGTeobO8ueRpT IQDHfXwThydJzf4kGkpNWyVSmwKZMeG87y1zriG/r8KUztiV0Fi1duU9dPNJrWYSH/eg aeoyxMfnALeeU994zhgv8D+6ww25Stsq60RVMtkyqT5kEAf3jqjbMFpowDNwnabifCIa I8ZFT+V+th81bPaLWq0b8ESYZK9gu0ERAZBLGc/tMjDCfdrhy3r/DJ/V411Rq9TTYgVC khLrsxwTpF+vTE0f8vXeO4oUgLtvUlf4RfCXnylpFYK76/6DRcr3i8vaQEtM/iH/cWg/ 7iyA== X-Gm-Message-State: APjAAAU7GZfJyTqWQ1OCZLj76LsPnDRMyp4b2s+rb8y0UlNXsZrbtxKD 7Znx/bHZfEe1aYQoWGVWiaYy9A== X-Google-Smtp-Source: APXvYqyHAY8VQNIqnQzuumTuKmg7myhszC/13fm1TLpoVPMha5KP/NI3oY2KD4gN0pSuFBe2+PjhHw== X-Received: by 2002:a17:90a:db49:: with SMTP id u9mr18544118pjx.13.1576885176266; Fri, 20 Dec 2019 15:39:36 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:35 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 6/9] ip6tlvs: Registration of TLV handlers and parameters Date: Fri, 20 Dec 2019 15:38:41 -0800 Message-Id: <1576885124-14576-7-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Create a single TLV parameter table that holds meta information for IPv6 Hop-by-Hop and Destination TLVs. The data structure is composed of a 256 element array of u8's (one entry for each TLV type to allow O(1) lookup). Each entry provides an offset into an array of TLV proc data structures which follows the array of u8s. The TLV proc data structure contains parameters and handler functions for receiving and transmitting TLVs. The zero'th element in the TLV proc array provides default parameters for TLVs. A class attribute indicates the type of extension header in which the TLV may be used (e.g. Hop-by-Hop options, Destination options, or Destination options before the routing header). Functions are defined to manipulate entries in the TLV parameter table. * tlv_{set|unset}_proc set a TLV proc entry (ops and parameters) * tlv_{set|unset}_params set parameters only Receive TLV lookup and processing is modified to be a lookup in the TLV parameter table. An init table containing parameters for TLVs supported by the kernel is used to initialize the TLV table. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 107 +++++++++++++++++-- include/net/ipv6.h | 3 + include/uapi/linux/ipeh.h | 16 +++ net/ipv6/exthdrs.c | 14 ++- net/ipv6/exthdrs_common.c | 260 ++++++++++++++++++++++++++++++++++++++++++--- net/ipv6/exthdrs_options.c | 63 +++++++---- 6 files changed, 414 insertions(+), 49 deletions(-) create mode 100644 include/uapi/linux/ipeh.h diff --git a/include/net/ipeh.h b/include/net/ipeh.h index 3d2bec6..fc7d543 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -11,13 +11,105 @@ * and false, if it failed. * It MUST NOT touch skb->h. */ -struct tlvtype_proc { - int type; - bool (*func)(struct sk_buff *skb, int offset); +struct tlv_ops { + bool (*func)(unsigned int class, struct sk_buff *skb, int offset); }; -extern const struct tlvtype_proc tlvprocdestopt_lst[]; -extern const struct tlvtype_proc tlvprochopopt_lst[]; +struct tlv_rx_params { + unsigned char class : 4; +}; + +struct tlv_tx_params { +}; + +struct tlv_params { + struct tlv_rx_params r; + struct tlv_tx_params t; +}; + +struct tlv_proc { + struct tlv_ops ops; + struct tlv_params params; +}; + +struct tlv_type { + struct tlv_proc proc; +}; + +struct tlv_proc_init { + int type; + struct tlv_proc proc; +}; + +struct tlv_param_table_data { + unsigned char entries[256]; + unsigned char count; + struct rcu_head rcu; + struct tlv_type types[0]; +}; + +struct tlv_param_table { + struct tlv_param_table_data __rcu *data; +}; + +extern struct tlv_param_table ipv6_tlv_param_table; + +int __ipeh_tlv_set(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_params *params, + const struct tlv_ops *ops); + +static inline int ipeh_tlv_set_params(struct tlv_param_table *tlv_param_table, + unsigned char type, + const struct tlv_params *params) +{ + return __ipeh_tlv_set(tlv_param_table, type, params, NULL); +} + +static inline int ipeh_tlv_set_proc(struct tlv_param_table *tlv_param_table, + unsigned char type, + const struct tlv_proc *proc) +{ + return __ipeh_tlv_set(tlv_param_table, type, + &proc->params, &proc->ops); +} + +int __ipeh_tlv_unset(struct tlv_param_table *tlv_param_table, + unsigned char type, bool params_only); + +static inline int ipeh_tlv_unset_params(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + return __ipeh_tlv_unset(tlv_param_table, type, true); +} + +static inline int ipeh_tlv_unset_proc(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + return __ipeh_tlv_unset(tlv_param_table, type, false); +} + +/* ipeh_tlv_get_proc_by_type assumes rcu_read_lock is held */ +static inline struct tlv_proc *ipeh_tlv_get_proc_by_type( + struct tlv_param_table *tlv_param_table, unsigned char type) +{ + struct tlv_param_table_data *tpt = + rcu_dereference(tlv_param_table->data); + + return &tpt->types[tpt->entries[type]].proc; +} + +/* ipeh_tlv_get_proc assumes rcu_read_lock is held */ +static inline struct tlv_proc *ipeh_tlv_get_proc( + struct tlv_param_table *tlv_param_table, + const __u8 *tlv) +{ + return ipeh_tlv_get_proc_by_type(tlv_param_table, tlv[0]); +} + +int ipeh_exthdrs_init(struct tlv_param_table *tlv_param_table, + const struct tlv_proc_init *init_params, + int num_init_params); +void ipeh_exthdrs_fini(struct tlv_param_table *tlv_param_table); struct ipv6_txoptions; struct ipv6_opt_hdr; @@ -52,8 +144,9 @@ enum ipeh_parse_errors { #define IPEH_TLV_PAD1 0 #define IPEH_TLV_PADN 1 -bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, - int max_count, int off, int len, +bool ipeh_parse_tlv(unsigned int class, + struct tlv_param_table *tlv_param_table, + struct sk_buff *skb, int max_count, int off, int len, bool (*parse_error)(struct sk_buff *skb, int off, enum ipeh_parse_errors error)); diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 6c4c834..e290e90 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -429,6 +429,9 @@ int ip6_ra_control(struct sock *sk, int sel); int ipv6_parse_hopopts(struct sk_buff *skb); +int ipv6_exthdrs_options_init(void); +void ipv6_exthdrs_options_exit(void); + bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb, const struct inet6_skb_parm *opt); struct ipv6_txoptions *ipv6_update_options(struct sock *sk, diff --git a/include/uapi/linux/ipeh.h b/include/uapi/linux/ipeh.h new file mode 100644 index 0000000..c4302b7 --- /dev/null +++ b/include/uapi/linux/ipeh.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* ipeh.h - IP extension header TLV management */ + +#ifndef _UAPI_LINUX_IPEH_H +#define _UAPI_LINUX_IPEH_H + +/* Flags for EH type that can use a TLV option */ +#define IPEH_TLV_CLASS_FLAG_HOPOPT BIT(0) +#define IPEH_TLV_CLASS_FLAG_RTRDSTOPT BIT(1) +#define IPEH_TLV_CLASS_FLAG_DSTOPT BIT(2) + +#define IPEH_TLV_CLASS_FLAG_MASK (IPEH_TLV_CLASS_FLAG_HOPOPT | \ + IPEH_TLV_CLASS_FLAG_RTRDSTOPT | \ + IPEH_TLV_CLASS_FLAG_DSTOPT) + +#endif /* _UAPI_LINUX_IPEH_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index d36ef0c..0c7a059 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -47,6 +47,7 @@ #ifdef CONFIG_IPV6_SEG6_HMAC #include #endif +#include #include @@ -139,7 +140,8 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) dstbuf = opt->dst1; #endif - if (ipeh_parse_tlv(tlvprocdestopt_lst, skb, + if (ipeh_parse_tlv(IPEH_TLV_CLASS_FLAG_DSTOPT, + &ipv6_tlv_param_table, skb, init_net.ipv6.sysctl.max_dst_opts_cnt, 2, extlen - 2, ipv6_parse_error)) { skb->transport_header += extlen; @@ -522,8 +524,13 @@ int __init ipv6_exthdrs_init(void) if (ret) goto out_destopt; + ret = ipv6_exthdrs_options_init(); + if (ret) + goto out_nodata; out: return ret; +out_nodata: + inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); out_destopt: inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); out_rthdr: @@ -533,6 +540,7 @@ int __init ipv6_exthdrs_init(void) void ipv6_exthdrs_exit(void) { + ipv6_exthdrs_options_exit(); inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); @@ -567,8 +575,8 @@ int ipv6_parse_hopopts(struct sk_buff *skb) goto fail_and_free; opt->flags |= IP6SKB_HOPBYHOP; - if (ipeh_parse_tlv(tlvprochopopt_lst, skb, - init_net.ipv6.sysctl.max_hbh_opts_cnt, + if (ipeh_parse_tlv(IPEH_TLV_CLASS_FLAG_HOPOPT, &ipv6_tlv_param_table, + skb, init_net.ipv6.sysctl.max_hbh_opts_cnt, 2, extlen - 2, ipv6_parse_error)) { skb->transport_header += extlen; opt = IP6CB(skb); diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 4d8a799..c72adb7 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -151,14 +151,15 @@ EXPORT_SYMBOL_GPL(ipeh_fixup_options); * - len is length of TLV block * - parse_error is protocol specific function to handle parser errors */ -bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, - int max_count, int off, int len, +bool ipeh_parse_tlv(unsigned int class, + struct tlv_param_table *tlv_param_table, + struct sk_buff *skb, int max_count, int off, int len, bool (*parse_error)(struct sk_buff *skb, int off, enum ipeh_parse_errors error)) { const unsigned char *nh = skb_network_header(skb); - const struct tlvtype_proc *curr; bool disallow_unknowns = false; + const struct tlv_proc *curr; int tlv_count = 0; int padlen = 0; @@ -180,6 +181,8 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, /* ops function based offset on network header */ off += skb_network_header_len(skb); + rcu_read_lock(); + while (len > 0) { int optlen = nh[off + 1] + 2; int i; @@ -227,19 +230,14 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, !parse_error(skb, off, IPEH_PARSE_ERR_OPT_TOOMANY)) goto bad; - for (curr = procs; curr->type >= 0; curr++) { - if (curr->type == nh[off]) { - /* type specific length/alignment - * checks will be performed in the - * func(). - */ - if (curr->func(skb, off) == false) - return false; - break; - } - } - if (curr->type < 0 && - !parse_error(skb, off, + curr = ipeh_tlv_get_proc(tlv_param_table, &nh[off]); + if ((curr->params.r.class & class) && curr->ops.func) { + /* Handler will apply additional checks to + * the TLV + */ + if (!curr->ops.func(class, skb, off)) + return false; + } else if (!parse_error(skb, off, disallow_unknowns ? IPEH_PARSE_ERR_OPT_UNK_DISALW : IPEH_PARSE_ERR_OPT_UNK)) @@ -252,10 +250,238 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb, len -= optlen; } - if (len == 0) + if (len == 0) { + rcu_read_unlock(); return true; + } bad: + rcu_read_unlock(); kfree_skb(skb); return false; } EXPORT_SYMBOL(ipeh_parse_tlv); + +/* TLV parameter table functions and structures */ + +/* Default (unset) values for TLV parameters */ +static const struct tlv_proc tlv_default_proc = { +}; + +static DEFINE_MUTEX(tlv_mutex); + +static size_t tlv_param_table_size(unsigned char count) +{ + return sizeof(struct tlv_param_table_data) + + (count * sizeof(struct tlv_type)); +} + +static void tlv_param_table_release(struct rcu_head *rcu) +{ + struct tlv_param_table_data *tpt = + container_of(rcu, struct tlv_param_table_data, rcu); + + kvfree(tpt); +} + +/* mutex held */ +static int __tlv_set_one(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_params *params, + const struct tlv_ops *ops) +{ + struct tlv_param_table_data *tpt, *told; + struct tlv_type *ttype; + + told = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + + /* Create new TLV table. If there is no exsiting entry then we are + * adding a new one to the table, else we're modifying an entry. + */ + tpt = kvmalloc(tlv_param_table_size(told->count + !told->entries[type]), + GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + memcpy(tpt, told, tlv_param_table_size(told->count)); + + if (!told->entries[type]) { + memset(&tpt->types[told->count], 0, sizeof(struct tlv_type)); + tpt->entries[type] = told->count; + tpt->count = told->count + 1; + } + + ttype = &tpt->types[tpt->entries[type]]; + + ttype->proc.params = *params; + ttype->proc.ops = ops ? *ops : tlv_default_proc.ops; + + rcu_assign_pointer(tlv_param_table->data, tpt); + call_rcu(&told->rcu, tlv_param_table_release); + + return 0; +} + +int __ipeh_tlv_set(struct tlv_param_table *tlv_param_table, unsigned char type, + const struct tlv_params *params, const struct tlv_ops *ops) +{ + int retv; + + if (type < 2) + return -EINVAL; + + mutex_lock(&tlv_mutex); + retv = __tlv_set_one(tlv_param_table, type, params, ops); + mutex_unlock(&tlv_mutex); + + return retv; +} +EXPORT_SYMBOL(__ipeh_tlv_set); + +/* mutex held */ +static int __tlv_unset_one(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + struct tlv_param_table_data *tpt, *told; + unsigned int i, pos; + + told = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + + if (!told->entries[type]) + return 0; + + tpt = kvmalloc(tlv_param_table_size(told->count - 1), + GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + pos = told->entries[type]; + + memcpy(tpt->types, told->types, pos * sizeof(struct tlv_type)); + memcpy(&tpt->types[pos], &told->types[pos + 1], + (told->count - pos - 1) * sizeof(struct tlv_type)); + + for (i = 0; i < 256; i++) { + if (told->entries[i] > pos) + tpt->entries[i] = told->entries[i] - 1; + else + tpt->entries[i] = told->entries[i]; + } + + /* Clear entry for type being unset (point to default params) */ + tpt->entries[type] = 0; + + tpt->count = told->count - 1; + + rcu_assign_pointer(tlv_param_table->data, tpt); + call_rcu(&told->rcu, tlv_param_table_release); + + return 0; +} + +/* tlv_internal_proc_type is used to check it the TLV proc was set + * internally. This is deduced by checking if any operations are defined. + */ +static bool tlv_internal_proc_type(struct tlv_proc *proc) +{ + return !!proc->ops.func; +} + +int __ipeh_tlv_unset(struct tlv_param_table *tlv_param_table, + unsigned char type, bool params_only) +{ + struct tlv_proc *tproc; + int retv; + + if (type < 2) + return -EINVAL; + + mutex_lock(&tlv_mutex); + + tproc = ipeh_tlv_get_proc_by_type(tlv_param_table, type); + + if (params_only && tlv_internal_proc_type(tproc)) { + /* TLV was set by internal source, so maintain the + * non-parameter fields (i.e. the operations). + */ + retv = __tlv_set_one(tlv_param_table, type, + &tlv_default_proc.params, + &tproc->ops); + } else { + retv = __tlv_unset_one(tlv_param_table, type); + } + + mutex_unlock(&tlv_mutex); + + return retv; +} +EXPORT_SYMBOL(__ipeh_tlv_unset); + +int ipeh_exthdrs_init(struct tlv_param_table *tlv_param_table, + const struct tlv_proc_init *tlv_init_params, + int num_init_params) +{ + struct tlv_param_table_data *tpt; + int pos = 0, i; + size_t tsize; + + tsize = tlv_param_table_size(num_init_params + 1); + + tpt = kvmalloc(tsize, GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + memset(tpt, 0, tsize); + + /* Zeroth TLV proc entry is default */ + tpt->types[pos++].proc = tlv_default_proc; + + for (i = 0; i < num_init_params; i++, pos++) { + const struct tlv_proc_init *tpi = &tlv_init_params[i]; + + if (WARN_ON(tpi->type < 2)) { + /* Padding TLV initialized? */ + goto err_inval; + } + if (WARN_ON(tpt->entries[tpi->type])) { + /* TLV type already set */ + goto err_inval; + } + + tpt->types[pos].proc = tpi->proc; + tpt->entries[tpi->type] = pos; + } + + tpt->count = pos; + + RCU_INIT_POINTER(tlv_param_table->data, tpt); + + return 0; + +err_inval: + kvfree(tpt); + return -EINVAL; +} +EXPORT_SYMBOL(ipeh_exthdrs_init); + +static void tlv_destroy_param_table(struct tlv_param_table *tlv_param_table) +{ + struct tlv_param_table_data *tpt; + + mutex_lock(&tlv_mutex); + + tpt = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + if (tpt) { + rcu_assign_pointer(tlv_param_table->data, NULL); + call_rcu(&tpt->rcu, tlv_param_table_release); + } + + mutex_unlock(&tlv_mutex); +} + +void ipeh_exthdrs_fini(struct tlv_param_table *tlv_param_table) +{ + tlv_destroy_param_table(tlv_param_table); +} +EXPORT_SYMBOL(ipeh_exthdrs_fini); diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index 032e072..d4b373e 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -11,11 +11,12 @@ #if IS_ENABLED(CONFIG_IPV6_MIP6) #include #endif +#include /* Destination options header */ #if IS_ENABLED(CONFIG_IPV6_MIP6) -static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) +static bool ipv6_dest_hao(unsigned int class, struct sk_buff *skb, int optoff) { struct ipv6_destopt_hao *hao; struct inet6_skb_parm *opt = IP6CB(skb); @@ -74,16 +75,6 @@ static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) } #endif -const struct tlvtype_proc tlvprocdestopt_lst[] = { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - { - .type = IPV6_TLV_HAO, - .func = ipv6_dest_hao, - }, -#endif - {-1, NULL} -}; - /* Hop-by-hop options */ /* Note: we cannot rely on skb_dst(skb) before we assign it in @@ -102,7 +93,7 @@ static inline struct net *ipv6_skb_net(struct sk_buff *skb) /* Router Alert as of RFC 2711 */ -static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) +static bool ipv6_hop_ra(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -120,7 +111,7 @@ static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) /* Jumbo payload */ -static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) +static bool ipv6_hop_jumbo(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); @@ -164,7 +155,8 @@ static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) /* CALIPSO RFC 5570 */ -static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) +static bool ipv6_hop_calipso(unsigned int class, struct sk_buff *skb, + int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -184,18 +176,45 @@ static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) return false; } -const struct tlvtype_proc tlvprochopopt_lst[] = { +static const struct tlv_proc_init tlv_ipv6_init_params[] __initconst = { +#if IS_ENABLED(CONFIG_IPV6_MIP6) { - .type = IPV6_TLV_ROUTERALERT, - .func = ipv6_hop_ra, + .type = IPV6_TLV_HAO, + + .proc.ops.func = ipv6_dest_hao, + .proc.params.r.class = IPEH_TLV_CLASS_FLAG_DSTOPT, }, +#endif { - .type = IPV6_TLV_JUMBO, - .func = ipv6_hop_jumbo, + .type = IPV6_TLV_ROUTERALERT, + + .proc.ops.func = ipv6_hop_ra, + .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, }, { - .type = IPV6_TLV_CALIPSO, - .func = ipv6_hop_calipso, + .type = IPV6_TLV_JUMBO, + + .proc.ops.func = ipv6_hop_jumbo, + .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, + }, + { + .type = IPV6_TLV_CALIPSO, + + .proc.ops.func = ipv6_hop_calipso, + .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, }, - { -1, } }; + +struct tlv_param_table __rcu ipv6_tlv_param_table; +EXPORT_SYMBOL(ipv6_tlv_param_table); + +int __init ipv6_exthdrs_options_init(void) +{ + return ipeh_exthdrs_init(&ipv6_tlv_param_table, tlv_ipv6_init_params, + ARRAY_SIZE(tlv_ipv6_init_params)); +} + +void ipv6_exthdrs_options_exit(void) +{ + ipeh_exthdrs_fini(&ipv6_tlv_param_table); +} From patchwork Fri Dec 20 23:38:42 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214375 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="1qfF0TJ9"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flb23l7Mz9s29 for ; Sat, 21 Dec 2019 10:39:42 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726829AbfLTXjl (ORCPT ); Fri, 20 Dec 2019 18:39:41 -0500 Received: from mail-pf1-f195.google.com ([209.85.210.195]:41121 "EHLO mail-pf1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726470AbfLTXjj (ORCPT ); Fri, 20 Dec 2019 18:39:39 -0500 Received: by mail-pf1-f195.google.com with SMTP id w62so6050936pfw.8 for ; Fri, 20 Dec 2019 15:39:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=RPse04hfQW1AZuc7Y63A7daYLzZOS0dopSNOKrbS55I=; b=1qfF0TJ9eQNAKsy6X6u1aDZpwXm4xmTy6wlHbJUwfJ5+k1V1ONI7HeDLiOvwA9gxZY eyOSljD+oiK8Ldq3U+bqADtxtSij/m+Y3deF76wHwjvrO7wu76llzkrIJg2pFnyte7Kh 3QHtaIIwVIEJ85JLjrWKv1CfUhPhQgTfer0XCQb2Nf6lgBKMIHrJ8MhTr4giZZBvBl/W ZcDmnLCy/juPXlNGN2yAF7g5DkXOv5uD+fwDnrsV/KlDZ9MLmGxQHy5lywkVzi9R9ZzC vigp9UYwIPm8I816O+KqBvWpJyp7jdlG2468SLAao5SJOFUp3sokrt/5rIsGebA4WmBd xf2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=RPse04hfQW1AZuc7Y63A7daYLzZOS0dopSNOKrbS55I=; b=Y+gnqf8hm1hJKYgn3R4GHFVGYD7TIeDeog0OrtBYD0Wj9xBIfNEZ+KJBnItsw5Hl2i hk2Ku91K350EE5Pli0s/jfcnWbHabcIRihkjpbS4Y80nWePTnw3jGLYIPLUeyPDiLvzI 0bEYx6ELywAOiHYy89YCFDSYUqcs3tc77IIw0/lugKadlv2hYMl6ny8yjnKOuY2PPRA2 P4iomYIGX0TpfZIRnJSPaxBcdRIvDp/BAdBMpYD7B2ps1fhH4AeST238mWAEovncPg7G xa/IYFQJUnbfLbi86SlVffThNtCJZilcPfKeaV4RkMuLIKusK8HwAWYBro3psYZk23JL P/AA== X-Gm-Message-State: APjAAAWMKWYDi/0pvsj/rZfG8Bobb3Woz5YuoAg4/FwpxFEUM1QrEPrx 8C2BpWLymBw+fH/HBTusnmshhA== X-Google-Smtp-Source: APXvYqxQ7EgmkcfLFIdc9isXX6fyuO7Dv1zJJiYUOD2rGQqiZnR6vO4Y37eUsOuFC22U05z2an33sw== X-Received: by 2002:a63:a508:: with SMTP id n8mr17106342pgf.278.1576885178484; Fri, 20 Dec 2019 15:39:38 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.36 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:37 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 7/9] ip6tlvs: Add TX parameters Date: Fri, 20 Dec 2019 15:38:42 -0800 Message-Id: <1576885124-14576-8-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Define a number of transmit parameters for TLV Parameter table definitions. These will be used for validating TLVs that are set on a socket. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 18 ++++++++++++++++ include/uapi/linux/ipeh.h | 8 +++++++ net/ipv6/exthdrs_common.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- net/ipv6/exthdrs_options.c | 45 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/include/net/ipeh.h b/include/net/ipeh.h index fc7d543..6f46e2c 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -20,6 +20,17 @@ struct tlv_rx_params { }; struct tlv_tx_params { + unsigned char admin_perm : 2; + unsigned char user_perm : 2; + unsigned char class : 3; + unsigned char rsvd : 1; + unsigned char align_mult : 4; + unsigned char align_off : 4; + unsigned char data_len_mult : 4; + unsigned char data_len_off : 4; + unsigned char min_data_len; + unsigned char max_data_len; + unsigned short preferred_order; }; struct tlv_params { @@ -54,6 +65,13 @@ struct tlv_param_table { extern struct tlv_param_table ipv6_tlv_param_table; +/* Preferred TLV ordering for HBH and Dest options (placed by increasing order) + */ +#define IPEH_TLV_PREF_ORDER_HAO 10 +#define IPEH_TLV_PREF_ORDER_ROUTERALERT 20 +#define IPEH_TLV_PREF_ORDER_JUMBO 30 +#define IPEH_TLV_PREF_ORDER_CALIPSO 40 + int __ipeh_tlv_set(struct tlv_param_table *tlv_param_table, unsigned char type, const struct tlv_params *params, const struct tlv_ops *ops); diff --git a/include/uapi/linux/ipeh.h b/include/uapi/linux/ipeh.h index c4302b7..dbf0728 100644 --- a/include/uapi/linux/ipeh.h +++ b/include/uapi/linux/ipeh.h @@ -13,4 +13,12 @@ IPEH_TLV_CLASS_FLAG_RTRDSTOPT | \ IPEH_TLV_CLASS_FLAG_DSTOPT) +/* TLV permissions values */ +enum { + IPEH_TLV_PERM_NONE, + IPEH_TLV_PERM_WITH_CHECK, + IPEH_TLV_PERM_NO_CHECK, + IPEH_TLV_PERM_MAX = IPEH_TLV_PERM_NO_CHECK +}; + #endif /* _UAPI_LINUX_IPEH_H */ diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index c72adb7..414f375a 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -3,6 +3,7 @@ /* Extension header and TLV library code that is not specific to IPv6. */ #include #include +#include struct ipv6_txoptions * ipeh_dup_options(struct sock *sk, struct ipv6_txoptions *opt) @@ -265,6 +266,13 @@ EXPORT_SYMBOL(ipeh_parse_tlv); /* Default (unset) values for TLV parameters */ static const struct tlv_proc tlv_default_proc = { + .params.t = { + .admin_perm = IPEH_TLV_PERM_NO_CHECK, + .user_perm = IPEH_TLV_PERM_NONE, + .align_mult = (4 - 1), /* Default alignment: 4n + 2 */ + .align_off = 2, + .max_data_len = 255, + }, }; static DEFINE_MUTEX(tlv_mutex); @@ -284,16 +292,45 @@ static void tlv_param_table_release(struct rcu_head *rcu) } /* mutex held */ +static int check_order(struct tlv_param_table_data *tpt, unsigned char type, + unsigned short order) +{ + int i; + + if (!order) + return -EINVAL; + + for (i = 2; i < 256; i++) { + struct tlv_type *ttype = &tpt->types[tpt->entries[i]]; + + if (!tpt->entries[i]) + continue; + + if (order == ttype->proc.params.t.preferred_order && + i != type) + return -EALREADY; + } + + return 0; +} + +/* mutex held */ static int __tlv_set_one(struct tlv_param_table *tlv_param_table, unsigned char type, const struct tlv_params *params, const struct tlv_ops *ops) { struct tlv_param_table_data *tpt, *told; struct tlv_type *ttype; + int retv; told = rcu_dereference_protected(tlv_param_table->data, lockdep_is_held(&tlv_mutex)); + /* Check preferred order */ + retv = check_order(told, type, params->t.preferred_order); + if (retv) + return retv; + /* Create new TLV table. If there is no exsiting entry then we are * adding a new one to the table, else we're modifying an entry. */ @@ -422,7 +459,7 @@ int ipeh_exthdrs_init(struct tlv_param_table *tlv_param_table, int num_init_params) { struct tlv_param_table_data *tpt; - int pos = 0, i; + int pos = 0, i, j; size_t tsize; tsize = tlv_param_table_size(num_init_params + 1); @@ -448,6 +485,20 @@ int ipeh_exthdrs_init(struct tlv_param_table *tlv_param_table, goto err_inval; } + if (WARN_ON(!tpi->proc.params.t.preferred_order)) { + /* Preferred order must be non-zero */ + goto err_inval; + } + + for (j = 0; j < i; j++) { + const struct tlv_proc_init *tpix = &tlv_init_params[j]; + + if (WARN_ON(tpi->proc.params.t.preferred_order == + tpix->proc.params.t.preferred_order)) { + /* Preferred order must be unique */ + goto err_inval; + } + } tpt->types[pos].proc = tpi->proc; tpt->entries[tpi->type] = pos; } diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index d4b373e..3b50b58 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -183,6 +183,17 @@ static const struct tlv_proc_init tlv_ipv6_init_params[] __initconst = { .proc.ops.func = ipv6_dest_hao, .proc.params.r.class = IPEH_TLV_CLASS_FLAG_DSTOPT, + + .proc.params.t = { + .preferred_order = IPEH_TLV_PREF_ORDER_HAO, + .admin_perm = IPEH_TLV_PERM_NO_CHECK, + .user_perm = IPEH_TLV_PERM_NONE, + .class = IPEH_TLV_CLASS_FLAG_DSTOPT, + .align_mult = (8 - 1), /* Align to 8n + 6 */ + .align_off = 6, + .min_data_len = 16, + .max_data_len = 16, + }, }, #endif { @@ -190,18 +201,52 @@ static const struct tlv_proc_init tlv_ipv6_init_params[] __initconst = { .proc.ops.func = ipv6_hop_ra, .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = IPEH_TLV_PREF_ORDER_ROUTERALERT, + .admin_perm = IPEH_TLV_PERM_NO_CHECK, + .user_perm = IPEH_TLV_PERM_NONE, + .class = IPEH_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (2 - 1), /* Align to 2n */ + .min_data_len = 2, + .max_data_len = 2, + }, }, { .type = IPV6_TLV_JUMBO, .proc.ops.func = ipv6_hop_jumbo, .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = IPEH_TLV_PREF_ORDER_JUMBO, + .admin_perm = IPEH_TLV_PERM_NO_CHECK, + .user_perm = IPEH_TLV_PERM_NONE, + .class = IPEH_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (4 - 1), /* Align to 4n + 2 */ + .align_off = 2, + .min_data_len = 4, + .max_data_len = 4, + }, }, { .type = IPV6_TLV_CALIPSO, .proc.ops.func = ipv6_hop_calipso, .proc.params.r.class = IPEH_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = IPEH_TLV_PREF_ORDER_CALIPSO, + .admin_perm = IPEH_TLV_PERM_NO_CHECK, + .user_perm = IPEH_TLV_PERM_NONE, + .class = IPEH_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (4 - 1), /* Align to 4n + 2 */ + .align_off = 2, + .min_data_len = 8, + .max_data_len = 252, + .data_len_mult = (4 - 1), + /* Length is multiple of 4 */ + }, }, }; From patchwork Fri Dec 20 23:38:43 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214376 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="a6fUy1cC"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flb45q7Cz9s29 for ; Sat, 21 Dec 2019 10:39:44 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726853AbfLTXjn (ORCPT ); Fri, 20 Dec 2019 18:39:43 -0500 Received: from mail-pf1-f196.google.com ([209.85.210.196]:36911 "EHLO mail-pf1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726831AbfLTXjl (ORCPT ); Fri, 20 Dec 2019 18:39:41 -0500 Received: by mail-pf1-f196.google.com with SMTP id p14so6059640pfn.4 for ; Fri, 20 Dec 2019 15:39:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=uA6X0LEsDaNLz3zI+Jdi+//l6V1FOgkf9CtBgX90POc=; b=a6fUy1cCmYALO0xrkjy7oCkAzGsH5qrb2tzeIx1Ow+vWqLNvRmzJSfqAV8g6ls/XSX xfO57/XlaDZQjD/5VyFTbbUOguzCeDNS7Bp9WSqqP1Pbsg2DG2qi6CDluVqsY+6GN3nS ZCV3H3WW8iTIxmpOr707XhQT0j2ilDffQr3qdZ+uB4ztk+TsuWhTFYsGM8vsLPMwZNUi QHaRcr5hlVNw/WDnVstnFooZ6fjqPB+xU30Us6z5XF4iyfVhfZDps0OSecqT70HtQyha hAdmqpA1JUFQ40oM7LKZDUmlUpoTf31Nn58+oU/VaoPAICRD1UT8I1Q0louhwNzh3A7S tZ2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=uA6X0LEsDaNLz3zI+Jdi+//l6V1FOgkf9CtBgX90POc=; b=iVU0Z/NM7hkMs/WZggWQPELF4VbaUf6CI8pz4Vcdvgj6rRCAVIzB5MPAhnzVFyFY43 TZN+5Ys6mGZcBUMBiBSdsffwSX6Zp8cZoIjnrDo7mZGKxGjzLpEUNqp4IoQsjsRkXgDZ vjQBknOJz+i3gu8kDWUsLkz3gwKrMgMPMAPDfr52nbaySuCUKhSEUhXcKG09rDcA9U9E tAd+o7obFA+sRsG7dTz3t6RGntiZbWpREMgy3jxydTxdBX4qaGz69716lcESIjz/g+sC zuAFCK5gVQEcJktcD7B6T0p/o6+2yGyC06rBfdOMMex0UMWpXEb54L90dNZsJSqQq9EZ 5b+Q== X-Gm-Message-State: APjAAAUauDv7YSE+zag/tWlM5uMONtw6AFbuCZAI4IeC6Y8QMlqX8Qji Lmm7WcS8v8nbdC4toXhjAGfxTA== X-Google-Smtp-Source: APXvYqw68S55UJccpaltNkNWNtrl3wQM7yZ/kn/XBjrDaRkQU2voALsff23ntBf9nsWc17K5fSLd4A== X-Received: by 2002:a63:2808:: with SMTP id o8mr17436520pgo.39.1576885180508; Fri, 20 Dec 2019 15:39:40 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.39 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:39 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 8/9] ip6tlvs: Add netlink interface Date: Fri, 20 Dec 2019 15:38:43 -0800 Message-Id: <1576885124-14576-9-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Add a netlink interface to manage the TX TLV parameters. Managed parameters include those for validating and sending TLVs being sent such as alignment, TLV ordering, length limits, etc. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 16 +++ include/net/ipv6.h | 1 + include/uapi/linux/in6.h | 6 + include/uapi/linux/ipeh.h | 29 +++++ net/ipv6/exthdrs_common.c | 279 +++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/exthdrs_options.c | 81 ++++++++++++- 6 files changed, 410 insertions(+), 2 deletions(-) diff --git a/include/net/ipeh.h b/include/net/ipeh.h index 6f46e2c..7ddbda1 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -3,6 +3,7 @@ #define _NET_IPEH_H #include +#include /* * Parsing tlv encoded headers. @@ -106,6 +107,21 @@ static inline int ipeh_tlv_unset_proc(struct tlv_param_table *tlv_param_table, return __ipeh_tlv_unset(tlv_param_table, type, false); } +extern const struct nla_policy ipeh_tlv_nl_policy[]; + +int ipeh_tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int ipeh_tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int ipeh_tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int ipeh_tlv_nl_dump(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct netlink_callback *cb); + /* ipeh_tlv_get_proc_by_type assumes rcu_read_lock is held */ static inline struct tlv_proc *ipeh_tlv_get_proc_by_type( struct tlv_param_table *tlv_param_table, unsigned char type) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index e290e90..68b7fb8 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 9f2273a..d5fe3d9 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -297,4 +297,10 @@ struct in6_flowlabel_req { * ... * MRT6_MAX */ + + /* NETLINK_GENERIC related info for IPv6 TLVs */ + +#define IPV6_TLV_GENL_NAME "ipv6-tlv" +#define IPV6_TLV_GENL_VERSION 0x1 + #endif /* _UAPI_LINUX_IN6_H */ diff --git a/include/uapi/linux/ipeh.h b/include/uapi/linux/ipeh.h index dbf0728..fb1d6e5 100644 --- a/include/uapi/linux/ipeh.h +++ b/include/uapi/linux/ipeh.h @@ -21,4 +21,33 @@ enum { IPEH_TLV_PERM_MAX = IPEH_TLV_PERM_NO_CHECK }; +/* NETLINK_GENERIC related info for IP TLVs */ + +enum { + IPEH_TLV_ATTR_UNSPEC, + IPEH_TLV_ATTR_TYPE, /* u8, > 1 */ + IPEH_TLV_ATTR_ORDER, /* u16 */ + IPEH_TLV_ATTR_ADMIN_PERM, /* u8, perm value (0 to 2) */ + IPEH_TLV_ATTR_USER_PERM, /* u8, perm value (0 to 2) */ + IPEH_TLV_ATTR_CLASS, /* u8, 3 bit flags */ + IPEH_TLV_ATTR_ALIGN_MULT, /* u8, 1 to 16 */ + IPEH_TLV_ATTR_ALIGN_OFF, /* u8, 0 to 15 */ + IPEH_TLV_ATTR_MIN_DATA_LEN, /* u8 (option data length) */ + IPEH_TLV_ATTR_MAX_DATA_LEN, /* u8 (option data length) */ + IPEH_TLV_ATTR_DATA_LEN_MULT, /* u8, 1 to 16 */ + IPEH_TLV_ATTR_DATA_LEN_OFF, /* u8, 0 to 15 */ + + __IPEH_TLV_ATTR_MAX, +}; + +#define IPEH_TLV_ATTR_MAX (__IPEH_TLV_ATTR_MAX - 1) + +enum { + IPEH_TLV_CMD_SET, + IPEH_TLV_CMD_UNSET, + IPEH_TLV_CMD_GET, + + __IPEH_TLV_CMD_MAX, +}; + #endif /* _UAPI_LINUX_IPEH_H */ diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 414f375a..dc5ff04 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -454,6 +454,285 @@ int __ipeh_tlv_unset(struct tlv_param_table *tlv_param_table, } EXPORT_SYMBOL(__ipeh_tlv_unset); +const struct nla_policy ipeh_tlv_nl_policy[IPEH_TLV_ATTR_MAX + 1] = { + [IPEH_TLV_ATTR_TYPE] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_ORDER] = { .type = NLA_U16, }, + [IPEH_TLV_ATTR_ADMIN_PERM] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_USER_PERM] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_CLASS] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_ALIGN_MULT] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_ALIGN_OFF] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_MIN_DATA_LEN] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_MAX_DATA_LEN] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_DATA_LEN_OFF] = { .type = NLA_U8, }, + [IPEH_TLV_ATTR_DATA_LEN_MULT] = { .type = NLA_U8, }, +}; +EXPORT_SYMBOL(ipeh_tlv_nl_policy); + +int ipeh_tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + struct tlv_params new_params; + struct tlv_proc *tproc; + unsigned char type; + int retv = -EINVAL; + unsigned int v; + + if (!info->attrs[IPEH_TLV_ATTR_TYPE]) { + NL_SET_ERR_MSG(info->extack, "No TLV type"); + return -EINVAL; + } + + type = nla_get_u8(info->attrs[IPEH_TLV_ATTR_TYPE]); + if (type < 2) { + NL_SET_ERR_MSG(info->extack, + "Invalid TLV type (less than 2)"); + return -EINVAL; + } + + rcu_read_lock(); + + /* Base new parameters on existing ones */ + tproc = ipeh_tlv_get_proc_by_type(tlv_param_table, type); + new_params = tproc->params; + + if (info->attrs[IPEH_TLV_ATTR_ORDER]) { + v = nla_get_u16(info->attrs[IPEH_TLV_ATTR_ORDER]); + new_params.t.preferred_order = v; + } + + if (info->attrs[IPEH_TLV_ATTR_ADMIN_PERM]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_ADMIN_PERM]); + if (v > IPEH_TLV_PERM_MAX) { + NL_SET_ERR_MSG(info->extack, + "Bad admin perm value"); + goto out; + } + new_params.t.admin_perm = v; + } + + if (info->attrs[IPEH_TLV_ATTR_USER_PERM]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_USER_PERM]); + if (v > IPEH_TLV_PERM_MAX) { + NL_SET_ERR_MSG(info->extack, + "Bad user perm value"); + goto out; + } + new_params.t.user_perm = v; + } + + if (info->attrs[IPEH_TLV_ATTR_CLASS]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_CLASS]); + if (!v || (v & ~IPEH_TLV_CLASS_FLAG_MASK)) { + NL_SET_ERR_MSG(info->extack, "Bad TLV class"); + goto out; + } + new_params.t.class = v; + } + + if (info->attrs[IPEH_TLV_ATTR_ALIGN_MULT]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_ALIGN_MULT]); + if (v > 16 || v < 1) { + NL_SET_ERR_MSG(info->extack, + "Alignment must be < 16 and > 0"); + goto out; + } + new_params.t.align_mult = v - 1; + } + + if (info->attrs[IPEH_TLV_ATTR_ALIGN_OFF]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_ALIGN_OFF]); + if (v > 15) { + NL_SET_ERR_MSG(info->extack, + "Alignment offset must be < 16"); + goto out; + } + new_params.t.align_off = v; + } + + if (info->attrs[IPEH_TLV_ATTR_MAX_DATA_LEN]) + new_params.t.max_data_len = + nla_get_u8(info->attrs[IPEH_TLV_ATTR_MAX_DATA_LEN]); + + if (info->attrs[IPEH_TLV_ATTR_MIN_DATA_LEN]) + new_params.t.min_data_len = + nla_get_u8(info->attrs[IPEH_TLV_ATTR_MIN_DATA_LEN]); + + if (new_params.t.min_data_len > new_params.t.max_data_len) { + NL_SET_ERR_MSG(info->extack, + "Min data length must be less than or equal to max data length"); + goto out; + } + + if (info->attrs[IPEH_TLV_ATTR_DATA_LEN_MULT]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_DATA_LEN_MULT]); + if (v > 16 || v < 1) { + NL_SET_ERR_MSG(info->extack, + "Length multiple must be < 16 and > 0"); + goto out; + } + new_params.t.data_len_mult = v - 1; + } + + if (info->attrs[IPEH_TLV_ATTR_DATA_LEN_OFF]) { + v = nla_get_u8(info->attrs[IPEH_TLV_ATTR_DATA_LEN_OFF]); + if (v > 15) { + NL_SET_ERR_MSG(info->extack, + "Data length offset must be < 16"); + goto out; + } + new_params.t.data_len_off = v; + } + + retv = ipeh_tlv_set_params(tlv_param_table, type, &new_params); + +out: + rcu_read_unlock(); + return retv; +} +EXPORT_SYMBOL(ipeh_tlv_nl_cmd_set); + +int ipeh_tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + unsigned char type; + + if (!info->attrs[IPEH_TLV_ATTR_TYPE]) { + NL_SET_ERR_MSG(info->extack, "No TLV type"); + return -EINVAL; + } + + type = nla_get_u8(info->attrs[IPEH_TLV_ATTR_TYPE]); + if (type < 2) { + NL_SET_ERR_MSG(info->extack, + "Invalid TLV type (less than 2)"); + return -EINVAL; + } + + return ipeh_tlv_unset_params(tlv_param_table, type); +} +EXPORT_SYMBOL(ipeh_tlv_nl_cmd_unset); + +static int tlv_fill_info(struct tlv_proc *tproc, unsigned char type, + struct sk_buff *msg, bool admin) +{ + struct tlv_params *tp = &tproc->params; + int ret = 0; + + if (nla_put_u8(msg, IPEH_TLV_ATTR_TYPE, type) || + nla_put_u16(msg, IPEH_TLV_ATTR_ORDER, tp->t.preferred_order) || + nla_put_u8(msg, IPEH_TLV_ATTR_USER_PERM, tp->t.user_perm) || + (admin && nla_put_u8(msg, IPEH_TLV_ATTR_ADMIN_PERM, + tp->t.admin_perm)) || + nla_put_u8(msg, IPEH_TLV_ATTR_CLASS, tp->t.class) || + nla_put_u8(msg, IPEH_TLV_ATTR_ALIGN_MULT, tp->t.align_mult + 1) || + nla_put_u8(msg, IPEH_TLV_ATTR_ALIGN_OFF, tp->t.align_off) || + nla_put_u8(msg, IPEH_TLV_ATTR_MIN_DATA_LEN, tp->t.min_data_len) || + nla_put_u8(msg, IPEH_TLV_ATTR_MAX_DATA_LEN, tp->t.max_data_len) || + nla_put_u8(msg, IPEH_TLV_ATTR_DATA_LEN_MULT, + tp->t.data_len_mult + 1) || + nla_put_u8(msg, IPEH_TLV_ATTR_DATA_LEN_OFF, tp->t.data_len_off)) + ret = -1; + + return ret; +} + +static int tlv_dump_info(struct tlv_proc *tproc, unsigned char type, + struct genl_family *tlv_nl_family, u32 portid, + u32 seq, u32 flags, struct sk_buff *skb, u8 cmd, + bool admin) +{ + void *hdr; + + hdr = genlmsg_put(skb, portid, seq, tlv_nl_family, flags, cmd); + if (!hdr) + return -ENOMEM; + + if (tlv_fill_info(tproc, type, skb, admin) < 0) { + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; + } + + genlmsg_end(skb, hdr); + + return 0; +} + +int ipeh_tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + struct tlv_proc *tproc; + struct sk_buff *msg; + unsigned char type; + int ret; + + if (!info->attrs[IPEH_TLV_ATTR_TYPE]) { + NL_SET_ERR_MSG(info->extack, "No TLV type"); + return -EINVAL; + } + + type = nla_get_u8(info->attrs[IPEH_TLV_ATTR_TYPE]); + if (type < 2) { + NL_SET_ERR_MSG(info->extack, + "Invalid TLV type (less than 2)"); + return -EINVAL; + } + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + rcu_read_lock(); + + tproc = ipeh_tlv_get_proc_by_type(tlv_param_table, type); + ret = tlv_dump_info(tproc, type, tlv_nl_family, info->snd_portid, + info->snd_seq, 0, msg, info->genlhdr->cmd, + netlink_capable(skb, CAP_NET_ADMIN)); + + rcu_read_unlock(); + + if (ret < 0) { + nlmsg_free(msg); + return ret; + } + + return genlmsg_reply(msg, info); +} +EXPORT_SYMBOL(ipeh_tlv_nl_cmd_get); + +int ipeh_tlv_nl_dump(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct netlink_callback *cb) +{ + struct tlv_proc *tproc; + int idx = 0, ret, i; + + rcu_read_lock(); + + for (i = 2; i < 256; i++) { + if (idx++ < cb->args[0]) + continue; + + tproc = ipeh_tlv_get_proc_by_type(tlv_param_table, i); + ret = tlv_dump_info(tproc, i, tlv_nl_family, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + skb, IPEH_TLV_CMD_GET, + netlink_capable(cb->skb, CAP_NET_ADMIN)); + if (ret) + break; + } + + rcu_read_unlock(); + + cb->args[0] = idx; + return skb->len; +} +EXPORT_SYMBOL(ipeh_tlv_nl_dump); + int ipeh_exthdrs_init(struct tlv_param_table *tlv_param_table, const struct tlv_proc_init *tlv_init_params, int num_init_params) diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index 3b50b58..c1889f6 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #if IS_ENABLED(CONFIG_IPV6_MIP6) @@ -253,13 +254,89 @@ static const struct tlv_proc_init tlv_ipv6_init_params[] __initconst = { struct tlv_param_table __rcu ipv6_tlv_param_table; EXPORT_SYMBOL(ipv6_tlv_param_table); +static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb); + +static const struct genl_ops ipv6_tlv_nl_ops[] = { +{ + .cmd = IPEH_TLV_CMD_SET, + .doit = ipv6_tlv_nl_cmd_set, + .flags = GENL_ADMIN_PERM, +}, +{ + .cmd = IPEH_TLV_CMD_UNSET, + .doit = ipv6_tlv_nl_cmd_unset, + .flags = GENL_ADMIN_PERM, +}, +{ + .cmd = IPEH_TLV_CMD_GET, + .doit = ipv6_tlv_nl_cmd_get, + .dumpit = ipv6_tlv_nl_dump, +}, +}; + +struct genl_family ipv6_tlv_nl_family __ro_after_init = { + .hdrsize = 0, + .name = IPV6_TLV_GENL_NAME, + .version = IPV6_TLV_GENL_VERSION, + .maxattr = IPEH_TLV_ATTR_MAX, + .policy = ipeh_tlv_nl_policy, + .netnsok = true, + .parallel_ops = true, + .ops = ipv6_tlv_nl_ops, + .n_ops = ARRAY_SIZE(ipv6_tlv_nl_ops), + .module = THIS_MODULE, +}; + +static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info) +{ + return ipeh_tlv_nl_cmd_set(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info) +{ + return ipeh_tlv_nl_cmd_unset(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info) +{ + return ipeh_tlv_nl_cmd_get(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + return ipeh_tlv_nl_dump(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, cb); +} + int __init ipv6_exthdrs_options_init(void) { - return ipeh_exthdrs_init(&ipv6_tlv_param_table, tlv_ipv6_init_params, - ARRAY_SIZE(tlv_ipv6_init_params)); + int err; + + err = genl_register_family(&ipv6_tlv_nl_family); + if (err) + goto genl_fail; + + ipeh_exthdrs_init(&ipv6_tlv_param_table, tlv_ipv6_init_params, + ARRAY_SIZE(tlv_ipv6_init_params)); + if (err) + goto ipv6_fail; + + return 0; + +ipv6_fail: + genl_unregister_family(&ipv6_tlv_nl_family); +genl_fail: + return err; } void ipv6_exthdrs_options_exit(void) { ipeh_exthdrs_fini(&ipv6_tlv_param_table); + genl_unregister_family(&ipv6_tlv_nl_family); } From patchwork Fri Dec 20 23:38:44 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1214377 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="s8MhbmEZ"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 47flb56hpFz9sPJ for ; Sat, 21 Dec 2019 10:39:45 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726874AbfLTXjo (ORCPT ); Fri, 20 Dec 2019 18:39:44 -0500 Received: from mail-pg1-f181.google.com ([209.85.215.181]:46841 "EHLO mail-pg1-f181.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726842AbfLTXjo (ORCPT ); Fri, 20 Dec 2019 18:39:44 -0500 Received: by mail-pg1-f181.google.com with SMTP id z124so5687793pgb.13 for ; Fri, 20 Dec 2019 15:39:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Qzd3NOzB4HaXIjfUqGVJfwjtak16N0/DcNY9ohlROHc=; b=s8MhbmEZaeE0j0ZJfMY11D+DEaDgqd2V62O2A2M6+vO4vvVjHnJRkunWYp+6VaoRn6 C+lbQcBuh4TRupmHatyc9oWf7f6IcOh94DZgMHyqzWvPQ0uIQrdIcnK7ANwPOv7dwiuA frsqGnoWmH91MRLk6witJi6l2+KxlcT0Ql/sf1yUfSkjdw8Wm4NLldjOq2YP4Sv70Dac 7UL9f2OqeUtJnuhH4d/i0JDREmgVErKKgbyhxWIVOMgzKWaM4fvm5p9JYZFQx2wqUZ42 foinjUeSo85B+z4EL4VzcLbiP7gsd4rmEhTIvK9FIwTE/c5eVuhCuTvm0bF827BDMwP3 aRmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Qzd3NOzB4HaXIjfUqGVJfwjtak16N0/DcNY9ohlROHc=; b=FKQoiYxyvCx4Oiw603qy0f1Viih8AEiaiq/h851BHEXJI6NMA3lrRRG2vwCMglECEt ggA95G3HHLkt0gCoxlXeu0vCu6GRGHVoSVyp5VV3eflYF7bjcOkyjER2pXGiNJ2NzAgC lNUNIth+dwG84IFIeKk9YeMJaSzA/XuE5Sqe/uXd42CRK3QG0qwBt4XvVdCz3Lz2q9Ci ZGPz86HWMwPEKxG7lFLBLWLNGTphtwVoSkUPmTjo56MruGuStpcITUhldYLUOLTFjtEs OUSSsjsJTuFFF+07Zju5XyUQSo9MHax5xe9NSlUU75zQFuKRs5yBVnEcrQQGlHQrAwY8 IPNg== X-Gm-Message-State: APjAAAWIas29pYS/XfT5Yxg3Ro8P3oExf7/lq08XtVBF0BWX8SQMhFlt y7ZcJm/qjw29Wr9/RgCKD4KQbQ== X-Google-Smtp-Source: APXvYqz4jlYbCSpDJPY4dGxKNCbOY/oBn1RmyhOAyDQBQovDZE3RPCxbD2jksYv09cLmb1eQyqsuUQ== X-Received: by 2002:a63:e0c:: with SMTP id d12mr17038537pgl.3.1576885182498; Fri, 20 Dec 2019 15:39:42 -0800 (PST) Received: from localhost.localdomain (c-73-202-182-113.hsd1.ca.comcast.net. [73.202.182.113]) by smtp.gmail.com with ESMTPSA id 207sm14833555pfu.88.2019.12.20.15.39.41 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 20 Dec 2019 15:39:41 -0800 (PST) From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org, simon.horman@netronome.com Cc: Tom Herbert , Tom Herbert Subject: [PATCH v6 net-next 9/9] ip6tlvs: Validation of TX Destination and Hop-by-Hop options Date: Fri, 20 Dec 2019 15:38:44 -0800 Message-Id: <1576885124-14576-10-git-send-email-tom@herbertland.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1576885124-14576-1-git-send-email-tom@herbertland.com> References: <1576885124-14576-1-git-send-email-tom@herbertland.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Tom Herbert Validate Destination and Hop-by-Hop options being set for transmit. This uses the information in the TLV parameters table to validate various aspects of both individual TLVs as well as a list of TLVs in an extension header. There are two levels of validation that can be performed: simple checks and deep checks. Simple checks validate only the most basic properties such as that the TLV list fits into the EH. Deep checks do a fine grained validation that includes perferred ordering, length limits, and length alignment. With proper permissions set in the TLV parameter table, this patch allows non-privileged users to send TLVs. Given that TLVs are open ended and potentially a source of DOS attack, deep checks are performed to limit the format that a non-privileged user can send. If deep checks are enabled, a canonical format for sending TLVs is enforced (in adherence with the robustness principle). A TLV must be well ordered with respect to the preferred order for the TLV. Each TLV must be aligned as described in the parameter table. Minimal padding (one padding TLV) is used to align TLVs. The length of the extension header as well as the count of non-padding TLVs is checked against max_*_opts_len and max_*_opts_cnt. For individual TLVs, length limits and length alignment is checked. Signed-off-by: Tom Herbert --- include/net/ipeh.h | 22 +++ net/ipv6/datagram.c | 51 ++++-- net/ipv6/exthdrs_common.c | 395 ++++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/ipv6_sockglue.c | 39 ++--- 4 files changed, 468 insertions(+), 39 deletions(-) diff --git a/include/net/ipeh.h b/include/net/ipeh.h index 7ddbda1..bbc6339 100644 --- a/include/net/ipeh.h +++ b/include/net/ipeh.h @@ -157,6 +157,28 @@ struct ipv6_txoptions *ipeh_renew_options(struct sock *sk, struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); +int ipeh_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned int optname, bool admin, + unsigned int max_len, unsigned int max_cnt); +int ipeh_opt_validate_single_tlv(struct net *net, + struct tlv_param_table *tlv_param_table, + unsigned int optname, const __u8 *tlv, + size_t len, bool deleting, bool admin); +int ipeh_opt_check_perm(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_txoptions *txopt, int optname, bool admin); + +struct ipv6_txoptions *ipeh_txopt_from_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, + unsigned int max_len, + unsigned int max_cnt); + /* Generic extension header TLV parser */ enum ipeh_parse_errors { diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c index 390bedd..fd850f4 100644 --- a/net/ipv6/datagram.c +++ b/net/ipv6/datagram.c @@ -839,7 +839,10 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, break; case IPV6_2292HOPOPTS: - case IPV6_HOPOPTS: + case IPV6_HOPOPTS: { + int max_len = net->ipv6.sysctl.max_hbh_opts_len; + int max_cnt = net->ipv6.sysctl.max_hbh_opts_cnt; + if (opt->hopopt || cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -851,15 +854,24 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + + err = ipeh_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_HOPOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + opt->opt_nflen += len; opt->hopopt = hdr; break; + } + + case IPV6_2292DSTOPTS: { + int max_len = net->ipv6.sysctl.max_dst_opts_len; + int max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; - case IPV6_2292DSTOPTS: if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -871,10 +883,14 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + err = ipeh_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_DSTOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + if (opt->dst1opt) { err = -EINVAL; goto exit_f; @@ -882,9 +898,13 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, opt->opt_flen += len; opt->dst1opt = hdr; break; + } case IPV6_DSTOPTS: - case IPV6_RTHDRDSTOPTS: + case IPV6_RTHDRDSTOPTS: { + int max_len = net->ipv6.sysctl.max_dst_opts_len; + int max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; + if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -896,10 +916,15 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + + err = ipeh_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_DSTOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + if (cmsg->cmsg_type == IPV6_DSTOPTS) { opt->opt_flen += len; opt->dst1opt = hdr; @@ -908,7 +933,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, opt->dst0opt = hdr; } break; - + } case IPV6_2292RTHDR: case IPV6_RTHDR: if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) { diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index dc5ff04..7312378 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -262,6 +262,331 @@ bool ipeh_parse_tlv(unsigned int class, } EXPORT_SYMBOL(ipeh_parse_tlv); +/* TLV validation functions */ + +/* Validate a single non-padding TLV */ +static int __ipeh_opt_validate_single_tlv(struct net *net, const __u8 *tlv, + struct tlv_proc *tproc, + unsigned int class, bool *deep_check, + bool deleting, bool admin) +{ + struct tlv_tx_params *tptx = &tproc->params.t; + + if (tlv[0] < 2) /* Must be non-padding */ + return -EINVAL; + + /* Check permissions */ + switch (admin ? tptx->admin_perm : tptx->user_perm) { + case IPEH_TLV_PERM_NO_CHECK: + /* Allowed with no deep checks */ + *deep_check = false; + return 0; + case IPEH_TLV_PERM_WITH_CHECK: + /* Allowed with deep checks */ + *deep_check = true; + break; + default: + /* No permission */ + return -EPERM; + } + + /* Perform deep checks on the TLV */ + + /* Check class */ + if ((tptx->class & class) != class) + return -EINVAL; + + /* Don't bother checking lengths when deleting, the TLV is only + * needed here for lookup + */ + if (deleting) { + /* Don't bother with deep checks when deleting */ + *deep_check = false; + } else { + /* Check length */ + if (tlv[1] < tptx->min_data_len || tlv[1] > tptx->max_data_len) + return -EINVAL; + + /* Check length alignment */ + if ((tlv[1] % (tptx->data_len_mult + 1)) != tptx->data_len_off) + return -EINVAL; + } + + return 0; +} + +static unsigned int optname_to_tlv_class(int optname) +{ + switch (optname) { + case IPV6_HOPOPTS: + return IPEH_TLV_CLASS_FLAG_HOPOPT; + case IPV6_RTHDRDSTOPTS: + return IPEH_TLV_CLASS_FLAG_RTRDSTOPT; + case IPV6_DSTOPTS: + return IPEH_TLV_CLASS_FLAG_DSTOPT; + default: + return -1U; + } +} + +static int __ipeh_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned int optname, bool deleting, + bool admin, unsigned int max_len, + unsigned int max_cnt) +{ + bool deep_check = !admin, did_deep_check = false; + unsigned int opt_len, tlv_len, offset; + unsigned int padding = 0, numpad = 0; + unsigned short prev_tlv_order = 0; + bool nonzero_padding = false; + unsigned int class, cnt = 0; + struct tlv_tx_params *tptx; + int retc, ret = -EINVAL; + __u8 *tlv = (__u8 *)opt; + struct tlv_proc *tproc; + + opt_len = ipv6_optlen(opt); + offset = sizeof(*opt); + + class = optname_to_tlv_class(optname); + + rcu_read_lock(); + + while (offset < opt_len) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + tlv_len = 1; + padding++; + numpad++; + break; + case IPV6_TLV_PADN: { + int i; + + if (offset + 1 >= opt_len) + goto out; + + tlv_len = tlv[offset + 1] + 2; + + if (offset + tlv_len > opt_len) + goto out; + + for (i = 0; i < tlv_len; i++) { + if (tlv[i] != 0) { + nonzero_padding = true; + break; + } + } + + padding += tlv_len; + numpad++; + break; + } + default: + if (offset + 1 >= opt_len) + goto out; + + tlv_len = tlv[offset + 1] + 2; + + if (offset + tlv_len > opt_len) + goto out; + + tproc = ipeh_tlv_get_proc(tlv_param_table, + &tlv[offset]); + tptx = &tproc->params.t; + + retc = __ipeh_opt_validate_single_tlv(net, &tlv[offset], + tproc, class, + &deep_check, + deleting, admin); + if (retc < 0) { + ret = retc; + goto out; + } + + if (deep_check) { + /* Check for too many options */ + if (++cnt > max_cnt) { + ret = -E2BIG; + goto out; + } + + /* Check order */ + if (tptx->preferred_order < prev_tlv_order) + goto out; + + /* Check alignment */ + if ((offset % (tptx->align_mult + 1)) != + tptx->align_off) + goto out; + + /* Check for right amount of padding */ + if (numpad > 1 || padding > tptx->align_mult || + nonzero_padding) + goto out; + + prev_tlv_order = tptx->preferred_order; + + did_deep_check = true; + } + nonzero_padding = false; + padding = 0; + numpad = 0; + } + offset += tlv_len; + } + + /* Check trailing padding. Note this covers the case option list + * only contains padding. + */ + if (deep_check && (numpad > 1 || padding > 7 || nonzero_padding)) + goto out; + + /* If we did at least one deep check apply length limit */ + if (did_deep_check && opt_len > max_len) { + ret = -EMSGSIZE; + goto out; + } + + /* All good */ + ret = 0; +out: + rcu_read_unlock(); + + return ret; +} + +/** + * ipeh_opt_validate_tlvs - Validate TLVs. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @opt: The option header + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @admin: Set for privileged user + * @max_len: Maximum length for TLV + * @max_cnt: Maximum number of non-padding TLVs + * + * Description: + * Walks the TLVs in a list to verify that the TLV lengths and other + * parameters are in bounds for a Destination or Hop-by-Hop option. + * Return -EINVAL is there is a problem, zero otherwise. + */ +int ipeh_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, unsigned int optname, + bool admin, unsigned int max_len, + unsigned int max_cnt) +{ + return __ipeh_opt_validate_tlvs(net, tlv_param_table, opt, optname, + false, admin, max_len, max_cnt); +} +EXPORT_SYMBOL(ipeh_opt_validate_tlvs); + +/** + * ipeh_opt_validate_single_tlv - Check that a single TLV is valid. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @tlv: The TLV as array of bytes + * @len: Length of buffer holding TLV + * @deleting: TLV is being deleted + * @admin: Set for privileged user + * + * Description: + * Validates a single TLV. The TLV must be non-padding type. The length + * of the TLV (as determined by the second byte that gives length of the + * option data) must match @len. + */ +int ipeh_opt_validate_single_tlv(struct net *net, + struct tlv_param_table *tlv_param_table, + unsigned int optname, const __u8 *tlv, + size_t len, bool deleting, bool admin) +{ + struct tlv_proc *tproc; + unsigned int class; + bool deep_check; + int ret = 0; + + class = optname_to_tlv_class(optname); + + if (tlv[0] < 2) + return -EINVAL; + + if (len < 2) + return -EINVAL; + + if (tlv[1] + 2 != len) + return -EINVAL; + + rcu_read_lock(); + + tproc = ipeh_tlv_get_proc(tlv_param_table, tlv); + + ret = __ipeh_opt_validate_single_tlv(net, tlv, tproc, class, + &deep_check, deleting, admin); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(ipeh_opt_validate_single_tlv); + +/** + * ipeh_opt_check_perm - Check that current capabilities allows modifying + * txopts. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @txopts: TX options from the socket + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @admin: Set for privileged user + * + * Description: + * + * Checks whether the permissions of TLV that are set on a socket permit + * modificationr. + * + */ +int ipeh_opt_check_perm(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_txoptions *txopt, int optname, bool admin) +{ + struct ipv6_opt_hdr *opt; + int retv = -EPERM; + + if (!txopt) + return 0; + + switch (optname) { + case IPV6_HOPOPTS: + opt = txopt->hopopt; + break; + case IPV6_RTHDRDSTOPTS: + opt = txopt->dst0opt; + break; + case IPV6_DSTOPTS: + opt = txopt->dst1opt; + break; + default: + goto out; + } + + if (!opt) { + retv = 0; + goto out; + } + + /* Just call the validate function on the options as being + * deleted. + */ + retv = __ipeh_opt_validate_tlvs(net, tlv_param_table, opt, optname, + true, admin, -1U, -1U); + +out: + return retv; +} +EXPORT_SYMBOL(ipeh_opt_check_perm); + /* TLV parameter table functions and structures */ /* Default (unset) values for TLV parameters */ @@ -454,6 +779,76 @@ int __ipeh_tlv_unset(struct tlv_param_table *tlv_param_table, } EXPORT_SYMBOL(__ipeh_tlv_unset); +/* Utility function tp create TX options from a setsockopt that is setting + * options on a socket. + */ +struct ipv6_txoptions *ipeh_txopt_from_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, + unsigned int max_len, + unsigned int max_cnt) +{ + struct ipv6_opt_hdr *new = NULL; + struct net *net = sock_net(sk); + int retv; + + /* remove any sticky options header with a zero option + * length, per RFC3542. + */ + if (optlen == 0) { + optval = NULL; + } else if (!optval) { + return ERR_PTR(-EINVAL); + } else if (optlen < sizeof(struct ipv6_opt_hdr) || + optlen & 0x7 || optlen > 8 * 255) { + return ERR_PTR(-EINVAL); + } else { + new = memdup_user(optval, optlen); + if (IS_ERR(new)) + return (struct ipv6_txoptions *)new; + if (unlikely(ipv6_optlen(new) > optlen)) { + kfree(new); + return ERR_PTR(-EINVAL); + } + } + + if (optname != IPV6_RTHDR) { + bool cap = ns_capable(net->user_ns, CAP_NET_RAW); + + /* First check if we have permission to delete + * the existing options on the socket. + */ + retv = ipeh_opt_check_perm(net, tlv_param_table, + opt, optname, cap); + if (retv < 0) { + kfree(new); + return ERR_PTR(retv); + } + + /* Check permissions and other validations on new + * TLVs + */ + if (new) { + retv = ipeh_opt_validate_tlvs(net, tlv_param_table, + new, optname, cap, + max_len, max_cnt); + if (retv < 0) { + kfree(new); + return ERR_PTR(retv); + } + } + } + + opt = ipeh_renew_options(sk, opt, optname, new); + kfree(new); + + return opt; +} +EXPORT_SYMBOL(ipeh_txopt_from_opt); + const struct nla_policy ipeh_tlv_nl_policy[IPEH_TLV_ATTR_MAX + 1] = { [IPEH_TLV_ATTR_TYPE] = { .type = NLA_U8, }, [IPEH_TLV_ATTR_ORDER] = { .type = NLA_U16, }, diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 7810988..d0f7693 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -395,40 +395,27 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, case IPV6_RTHDR: case IPV6_DSTOPTS: { + unsigned int max_len = -1U, max_cnt = -1U; struct ipv6_txoptions *opt; - struct ipv6_opt_hdr *new = NULL; - /* hop-by-hop / destination options are privileged option */ - retv = -EPERM; - if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW)) + switch (optname) { + case IPV6_HOPOPTS: + max_len = net->ipv6.sysctl.max_hbh_opts_len; + max_cnt = net->ipv6.sysctl.max_hbh_opts_cnt; break; - - /* remove any sticky options header with a zero option - * length, per RFC3542. - */ - if (optlen == 0) - optval = NULL; - else if (!optval) - goto e_inval; - else if (optlen < sizeof(struct ipv6_opt_hdr) || - optlen & 0x7 || optlen > 8 * 255) - goto e_inval; - else { - new = memdup_user(optval, optlen); - if (IS_ERR(new)) { - retv = PTR_ERR(new); + case IPV6_RTHDRDSTOPTS: + case IPV6_DSTOPTS: + max_len = net->ipv6.sysctl.max_dst_opts_len; + max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; break; - } - if (unlikely(ipv6_optlen(new) > optlen)) { - kfree(new); - goto e_inval; - } } opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk)); - opt = ipeh_renew_options(sk, opt, optname, new); - kfree(new); + opt = ipeh_txopt_from_opt(sk, &ipv6_tlv_param_table, opt, + optname, optval, optlen, max_len, + max_cnt); + if (IS_ERR(opt)) { retv = PTR_ERR(opt); break;