From patchwork Wed Jan 23 05:31:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1029666 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 (mailfrom) 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="RlDAMC8N"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43kv7N6xc7z9s3q for ; Wed, 23 Jan 2019 16:32:32 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726322AbfAWFcb (ORCPT ); Wed, 23 Jan 2019 00:32:31 -0500 Received: from mail-pl1-f196.google.com ([209.85.214.196]:36304 "EHLO mail-pl1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725899AbfAWFcb (ORCPT ); Wed, 23 Jan 2019 00:32:31 -0500 Received: by mail-pl1-f196.google.com with SMTP id g9so565527plo.3 for ; Tue, 22 Jan 2019 21:32:30 -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=UbX6g+MF38QXeh2gFhHobjji4OJTSPwBYcmGdHmU400=; b=RlDAMC8N1J8ng47t75vMH+9JGrZC5WjMsKfQK1ZTaR2y8X9y+8XWD4pwmhjlVilTym kp7Iog1u9EDGv3Cc/8g7FD/517vdHw9fxq+vQx5AQ6hOIPHxKsXLWrTuYWX0aZSdHqMK IdQVNPqrycbZVu8tCMyuUxVzCL0bZB4xu9BaROmGUAbmCceRtCFdh3i2MqPtbFEKc3p8 Qa08/QcSSeLBmtBSjPiBjenkqmkcFRQrXBiLpAQNuw3S5P92vH9toEnJbIjJtHMED5Uq 9AHFYZLvUq4KiyDjWxdmIXDdvYPR1b974q5rApp9RmTGprt4QVCm0p6aqa0u3iqTRjz+ m05Q== 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=UbX6g+MF38QXeh2gFhHobjji4OJTSPwBYcmGdHmU400=; b=U6kkKbgvCXhXo5PLlpW2OG7EMoivKs6SGNPI6uWV/KfTnqebQTqV3IQSH6Xyvz2QWl RGK8+i6eDUlbx6OKuxJm2hIDxGKzGNsV3d0/Lek2BpesOK7RKYqqDkTrojdqYbHfwcoj SSRfxt4/HW/Gee4BI00vZYCe0hZxFTHMsM9nlnzeXMhDpZ+JyAJ+MvxvnL/6NEtHMbWK j6jwVVbUtPePPjPxQ09082NlrWGzoo/jGNqiF20q2XKOKgbq3D6o6tSpdmPfbMOqjujn eK876TdMMypIhrG8pPXUPbw0jBbXnDIjE/j5dVHVGI6izEpvad3CcRTiRa4jy2Zu/awF UFPw== X-Gm-Message-State: AJcUukfRZRvuhZFcAIu4DEf3wOurDVUuK44kbbiNRQ9qnTJrblmOAVob q8mxyfcJbkSt37qx6lR2EylD8Q== X-Google-Smtp-Source: ALg8bN4lQjPTqxN9WKumUD9NN3pnCQZVIzOcMJOSeaYrwokQ+vVzbfRoRQ/M3dLpY5fUEIuyWkjiYQ== X-Received: by 2002:a17:902:a5c3:: with SMTP id t3mr869809plq.117.1548221550508; Tue, 22 Jan 2019 21:32:30 -0800 (PST) Received: from localhost.localdomain (c-73-223-249-119.hsd1.ca.comcast.net. [73.223.249.119]) by smtp.gmail.com with ESMTPSA id o66sm31700948pgo.75.2019.01.22.21.32.29 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 22 Jan 2019 21:32:30 -0800 (PST) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH net-next 4/5] ip6tlvs: Validation of TX Destination and Hop-by-Hop options Date: Tue, 22 Jan 2019 21:31:22 -0800 Message-Id: <1548221483-3085-5-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1548221483-3085-1-git-send-email-tom@quantonium.net> References: <1548221483-3085-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Validate Destination and Hop-by-Hop options. 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 EH. There are two levels of validation that can be performed: simple checks and deep checks. Simple checks validate on the most basic properties such as that the TLV list fits into the EH. Deep checks do a fine grained validation. With proper permissions in the TLV 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 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, lengths length alignment is checked. --- include/net/ipv6.h | 7 ++ net/ipv6/datagram.c | 27 ++-- net/ipv6/exthdrs_options.c | 298 +++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/ipv6_sockglue.c | 30 ++++- 4 files changed, 348 insertions(+), 14 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 3d3b5a1..0bd659b 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -379,6 +379,13 @@ struct ipv6_txoptions *ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); +int ipv6_opt_validate_tlvs(struct net *net, struct ipv6_opt_hdr *opt, + unsigned int optname, bool admin); +int ipv6_opt_validate_single_tlv(struct net *net, unsigned int optname, + unsigned char *tlv, size_t len, + bool deleting, bool admin); +int ipv6_opt_check_perm(struct net *net, struct sock *sk, + int optname, bool admin); struct tlv_tx_param { unsigned char preferred_order; unsigned char admin_perm : 2; diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c index ee4a4e5..ef50439 100644 --- a/net/ipv6/datagram.c +++ b/net/ipv6/datagram.c @@ -853,10 +853,13 @@ 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 = ipv6_opt_validate_tlvs(net, hdr, IPV6_HOPOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW)); + if (err < 0) goto exit_f; - } + opt->opt_nflen += len; opt->hopopt = hdr; break; @@ -873,10 +876,13 @@ 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 = ipv6_opt_validate_tlvs(net, hdr, IPV6_DSTOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW)); + if (err < 0) goto exit_f; - } + if (opt->dst1opt) { err = -EINVAL; goto exit_f; @@ -898,10 +904,13 @@ 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 = ipv6_opt_validate_tlvs(net, hdr, cmsg->cmsg_type, + ns_capable(net->user_ns, + CAP_NET_RAW)); + if (err < 0) goto exit_f; - } + if (cmsg->cmsg_type == IPV6_DSTOPTS) { opt->opt_flen += len; opt->dst1opt = hdr; diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index da9e257..401f8ff 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -162,6 +162,304 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, } EXPORT_SYMBOL_GPL(ipv6_fixup_options); +/* TLV validation functions */ + +/* Validate a single non-padding TLV */ +static int __ipv6_opt_validate_single_tlv(struct net *net, unsigned char *tlv, + struct tlv_tx_param *tptx, + unsigned int class, bool *deep_check, + bool deleting, bool admin) +{ + if (tlv[0] < 2) /* Must be non-padding */ + return -EINVAL; + + /* Check permissions */ + switch (admin ? tptx->admin_perm : tptx->user_perm) { + case IPV6_TLV_PERM_NO_CHECK: + /* Allowed with no deep checks */ + *deep_check = false; + return 0; + case IPV6_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 IPV6_TLV_CLASS_FLAG_HOPOPT; + case IPV6_RTHDRDSTOPTS: + return IPV6_TLV_CLASS_FLAG_RTRDSTOPT; + case IPV6_DSTOPTS: + return IPV6_TLV_CLASS_FLAG_DSTOPT; + default: + return -1U; + } +} + +static int __ipv6_opt_validate_tlvs(struct net *net, struct ipv6_opt_hdr *opt, + unsigned int optname, bool deleting, + bool admin) +{ + unsigned int max_len = 0, max_cnt = 0, cnt = 0; + unsigned char *tlv = (unsigned char *)opt; + bool deep_check, did_deep_check = false; + unsigned int opt_len, tlv_len, offset; + unsigned int padding = 0, numpad = 0; + unsigned char prev_tlv_order = 0; + struct tlv_tx_param *tptx; + int retc, ret = -EINVAL; + unsigned int class; + + opt_len = ipv6_optlen(opt); + offset = sizeof(*opt); + + class = optname_to_tlv_class(optname); + + switch (optname) { + case IPV6_HOPOPTS: + max_len = net->ipv6.sysctl.max_hbh_opts_len; + max_cnt = net->ipv6.sysctl.max_hbh_opts_cnt; + break; + 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; + } + + rcu_read_lock(); + + while (offset < opt_len) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + tlv_len = 1; + padding++; + numpad++; + break; + case IPV6_TLV_PADN: + if (offset + 1 >= opt_len) + goto out; + + tlv_len = tlv[offset + 1] + 2; + + if (offset + tlv_len > opt_len) + goto out; + + 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; + + tptx = tlv_deref_tx_params(tlv[offset]); + retc = __ipv6_opt_validate_single_tlv(net, &tlv[offset], + tptx, 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) + goto out; + + prev_tlv_order = tptx->preferred_order; + } + + padding = 0; + numpad = 0; + did_deep_check = true; + } + offset += tlv_len; + } + + /* 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; +} + +/** + * __ipv6_opt_validate_tlvs - Validate TLVs. + * @net: Current net + * @opt: The option header + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @admin: Set for privileged user + * + * 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 ipv6_opt_validate_tlvs(struct net *net, struct ipv6_opt_hdr *opt, + unsigned int optname, bool admin) +{ + return __ipv6_opt_validate_tlvs(net, opt, optname, false, admin); +} +EXPORT_SYMBOL(ipv6_opt_validate_tlvs); + +/** + * ipv6_opt_validate_single - Check that a single TLV is valid. + * @net: Current net + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @tlv: The TLV as array of bytes + * @len: Length of buffer holding TLV + * + * 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 ipv6_opt_validate_single_tlv(struct net *net, unsigned int optname, + unsigned char *tlv, size_t len, + bool deleting, bool admin) +{ + struct tlv_tx_param *tptx; + unsigned int class; + bool deep_check; + int ret = 0; + + class = optname_to_tlv_class(optname); + + switch (tlv[0]) { + case IPV6_TLV_PAD1: + case IPV6_TLV_PADN: + return -EINVAL; + default: + break; + } + + if (len < 2) + return -EINVAL; + + if (tlv[1] + 2 != len) + return -EINVAL; + + rcu_read_lock(); + + tptx = tlv_deref_tx_params(tlv[0]); + + ret = __ipv6_opt_validate_single_tlv(net, tlv, tptx, class, + &deep_check, deleting, admin); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(ipv6_opt_validate_single_tlv); + +/** + * ipv6_opt_check_perm - Check is current capabilities allows modify + * txopts. + * @net: Current net + * @sk: 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 ipv6_opt_check_perm(struct net *net, struct sock *sk, int optname, + bool admin) +{ + struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)); + struct ipv6_opt_hdr *opt; + int retv = -EPERM; + + if (!old) + return 0; + + switch (optname) { + case IPV6_HOPOPTS: + opt = old->hopopt; + break; + case IPV6_RTHDRDSTOPTS: + opt = old->dst0opt; + break; + case IPV6_DSTOPTS: + opt = old->dst1opt; + break; + default: + goto out; + } + + /* Just call the validate function on the options as being + * deleted. + */ + retv = __ipv6_opt_validate_tlvs(net, opt, optname, true, admin); + +out: + txopt_put(old); + return retv; +} +EXPORT_SYMBOL(ipv6_opt_check_perm); + /* Destination options header */ #if IS_ENABLED(CONFIG_IPV6_MIP6) diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 973e215..009c8a4 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -400,11 +400,6 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, 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)) - break; - /* remove any sticky options header with a zero option * length, per RFC3542. */ @@ -427,6 +422,31 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, } } + 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 = ipv6_opt_check_perm(net, sk, optname, cap); + if (retv < 0) { + kfree(new); + break; + } + + /* Check permissions and other validations on new + * TLVs + */ + if (new) { + retv = ipv6_opt_validate_tlvs(net, new, + optname, cap); + if (retv < 0) { + kfree(new); + break; + } + } + } + opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk)); opt = ipv6_renew_options(sk, opt, optname, new);