diff mbox series

[net-next,08/20] rtnetlink: Update rtnl_dump_ifinfo for strict data checking

Message ID 20181004213355.14899-9-dsahern@kernel.org
State Changes Requested, archived
Delegated to: David Miller
Headers show
Series rtnetlink: Add support for rigid checking of data in dump request | expand

Commit Message

David Ahern Oct. 4, 2018, 9:33 p.m. UTC
From: David Ahern <dsahern@gmail.com>

Update rtnl_dump_ifinfo for strict data checking. If the flag is set,
the dump request is expected to have an ifinfomsg struct as the header
potentially followed by one or more attributes. Any data passed in the
header or as an attribute is taken as a request to influence the data
returned. Only values supported by the dump handler are allowed to be
non-0 or set in the request. At the moment only the IFA_TARGET_NETNSID,
IFLA_EXT_MASK, IFLA_MASTER, and IFLA_LINKINFO attributes are supported.

Existing code does not fail the dump if nlmsg_parse fails. That behavior
is kept for non-strict checking.

Signed-off-by: David Ahern <dsahern@gmail.com>
---
 net/core/rtnetlink.c | 97 +++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 69 insertions(+), 28 deletions(-)

Comments

Christian Brauner Oct. 5, 2018, 5:59 p.m. UTC | #1
On Thu, Oct 04, 2018 at 02:33:43PM -0700, David Ahern wrote:
> From: David Ahern <dsahern@gmail.com>
> 
> Update rtnl_dump_ifinfo for strict data checking. If the flag is set,
> the dump request is expected to have an ifinfomsg struct as the header
> potentially followed by one or more attributes. Any data passed in the
> header or as an attribute is taken as a request to influence the data
> returned. Only values supported by the dump handler are allowed to be
> non-0 or set in the request. At the moment only the IFA_TARGET_NETNSID,
> IFLA_EXT_MASK, IFLA_MASTER, and IFLA_LINKINFO attributes are supported.
> 
> Existing code does not fail the dump if nlmsg_parse fails. That behavior
> is kept for non-strict checking.
> 
> Signed-off-by: David Ahern <dsahern@gmail.com>
> ---
>  net/core/rtnetlink.c | 97 +++++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 69 insertions(+), 28 deletions(-)
> 
> diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
> index c3b434d724ea..4fd27b5db787 100644
> --- a/net/core/rtnetlink.c
> +++ b/net/core/rtnetlink.c
> @@ -1880,6 +1880,8 @@ EXPORT_SYMBOL_GPL(rtnl_get_net_ns_capable);
>  
>  static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
>  {
> +	struct netlink_ext_ack *extack = cb->extack;
> +	const struct nlmsghdr *nlh = cb->nlh;
>  	struct net *net = sock_net(skb->sk);
>  	struct net *tgt_net = net;
>  	int h, s_h;
> @@ -1892,44 +1894,84 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
>  	unsigned int flags = NLM_F_MULTI;
>  	int master_idx = 0;
>  	int netnsid = -1;
> -	int err;
> +	int err, i;
>  	int hdrlen;
>  
>  	s_h = cb->args[0];
>  	s_idx = cb->args[1];
>  
> -	/* A hack to preserve kernel<->userspace interface.
> -	 * The correct header is ifinfomsg. It is consistent with rtnl_getlink.
> -	 * However, before Linux v3.9 the code here assumed rtgenmsg and that's
> -	 * what iproute2 < v3.9.0 used.
> -	 * We can detect the old iproute2. Even including the IFLA_EXT_MASK
> -	 * attribute, its netlink message is shorter than struct ifinfomsg.
> -	 */
> -	hdrlen = nlmsg_len(cb->nlh) < sizeof(struct ifinfomsg) ?
> -		 sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
> +	if (cb->strict_check) {
> +		struct ifinfomsg *ifm;
>  
> -	if (nlmsg_parse(cb->nlh, hdrlen, tb, IFLA_MAX,
> -			ifla_policy, cb->extack) >= 0) {
> -		if (tb[IFLA_TARGET_NETNSID]) {
> -			netnsid = nla_get_s32(tb[IFLA_TARGET_NETNSID]);
> -			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
> -			if (IS_ERR(tgt_net))
> -				return PTR_ERR(tgt_net);
> +		hdrlen = sizeof(*ifm);
> +		if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) {
> +			NL_SET_ERR_MSG(extack, "Invalid header");
> +			return -EINVAL;
>  		}
>  
> -		if (tb[IFLA_EXT_MASK])
> -			ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
> -
> -		if (tb[IFLA_MASTER])
> -			master_idx = nla_get_u32(tb[IFLA_MASTER]);
> +		ifm = nlmsg_data(nlh);
> +		if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
> +		    ifm->ifi_change) {
> +			NL_SET_ERR_MSG(extack, "Invalid values in header for dump request");
> +			return -EINVAL;
> +		}
> +		if (ifm->ifi_index) {
> +			NL_SET_ERR_MSG(extack, "Filter by device index not supported");
> +			return -EINVAL;
> +		}
> +	} else {
> +		/* A hack to preserve kernel<->userspace interface.
> +		 * The correct header is ifinfomsg. It is consistent with
> +		 * rtnl_getlink. However, before Linux v3.9 the code here
> +		 * assumed rtgenmsg and that's what iproute2 < v3.9.0 used.
> +		 * We can detect the old iproute2. Even including the
> +		 * IFLA_EXT_MASK attribute, its netlink message is shorter
> +		 * than struct ifinfomsg.
> +		 */
> +		hdrlen = nlmsg_len(nlh) < sizeof(struct ifinfomsg) ?
> +			 sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
> +	}
>  
> -		if (tb[IFLA_LINKINFO])
> -			kind_ops = linkinfo_to_kind_ops(tb[IFLA_LINKINFO]);
> +	err = nlmsg_parse(nlh, hdrlen, tb, IFLA_MAX, ifla_policy, extack);
> +	if (err < 0) {
> +		if (cb->strict_check)
> +			return -EINVAL;
> +		goto walk_entries;
> +	}
>  
> -		if (master_idx || kind_ops)
> -			flags |= NLM_F_DUMP_FILTERED;
> +	for (i = 0; i <= IFLA_MAX; ++i) {
> +		if (!tb[i])
> +			continue;
> +		switch (i) {
> +		case IFLA_TARGET_NETNSID:
> +			netnsid = nla_get_s32(tb[i]);
> +			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
> +			if (IS_ERR(tgt_net)) {
> +				NL_SET_ERR_MSG(extack, "Invalid target namespace id");
> +				return PTR_ERR(tgt_net);
> +			}
> +			break;
> +		case IFLA_EXT_MASK:
> +			ext_filter_mask = nla_get_u32(tb[i]);
> +			break;
> +		case IFLA_MASTER:
> +			master_idx = nla_get_u32(tb[i]);
> +			break;
> +		case IFLA_LINKINFO:
> +			kind_ops = linkinfo_to_kind_ops(tb[i]);
> +			break;
> +		default:
> +			if (cb->strict_check) {
> +				NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
> +				return -EINVAL;
> +			}
> +		}

This might make sense to be split into two helpers for parsing:
<blablabla>_strict() and <blablabla>_lenient(). :)


>  	}
>  
> +	if (master_idx || kind_ops)
> +		flags |= NLM_F_DUMP_FILTERED;
> +
> +walk_entries:
>  	for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
>  		idx = 0;
>  		head = &tgt_net->dev_index_head[h];
> @@ -1941,8 +1983,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
>  			err = rtnl_fill_ifinfo(skb, dev, net,
>  					       RTM_NEWLINK,
>  					       NETLINK_CB(cb->skb).portid,
> -					       cb->nlh->nlmsg_seq, 0,
> -					       flags,
> +					       nlh->nlmsg_seq, 0, flags,
>  					       ext_filter_mask, 0, NULL, 0,
>  					       netnsid);
>  
> -- 
> 2.11.0
>
David Ahern Oct. 5, 2018, 7:22 p.m. UTC | #2
On 10/5/18 11:59 AM, Christian Brauner wrote:
>> +	err = nlmsg_parse(nlh, hdrlen, tb, IFLA_MAX, ifla_policy, extack);
>> +	if (err < 0) {
>> +		if (cb->strict_check)
>> +			return -EINVAL;
>> +		goto walk_entries;
>> +	}
>>  
>> -		if (master_idx || kind_ops)
>> -			flags |= NLM_F_DUMP_FILTERED;
>> +	for (i = 0; i <= IFLA_MAX; ++i) {
>> +		if (!tb[i])
>> +			continue;
>> +		switch (i) {
>> +		case IFLA_TARGET_NETNSID:
>> +			netnsid = nla_get_s32(tb[i]);
>> +			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
>> +			if (IS_ERR(tgt_net)) {
>> +				NL_SET_ERR_MSG(extack, "Invalid target namespace id");
>> +				return PTR_ERR(tgt_net);
>> +			}
>> +			break;
>> +		case IFLA_EXT_MASK:
>> +			ext_filter_mask = nla_get_u32(tb[i]);
>> +			break;
>> +		case IFLA_MASTER:
>> +			master_idx = nla_get_u32(tb[i]);
>> +			break;
>> +		case IFLA_LINKINFO:
>> +			kind_ops = linkinfo_to_kind_ops(tb[i]);
>> +			break;
>> +		default:
>> +			if (cb->strict_check) {
>> +				NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
>> +				return -EINVAL;
>> +			}
>> +		}
> 
> This might make sense to be split into two helpers for parsing:
> <blablabla>_strict() and <blablabla>_lenient(). :)

I thought about that, but there is so much overlap - they are mostly
common. Besides, ifinfomsg is the header for link dumps, and ifinfomsg
is the one that has been (ab)used for other message types, so strict
versus lenient does not really have a differentiator for this message
type - other than checking the elements of the struct.
Christian Brauner Oct. 7, 2018, 10:29 a.m. UTC | #3
On Fri, Oct 05, 2018 at 01:22:24PM -0600, David Ahern wrote:
> On 10/5/18 11:59 AM, Christian Brauner wrote:
> >> +	err = nlmsg_parse(nlh, hdrlen, tb, IFLA_MAX, ifla_policy, extack);
> >> +	if (err < 0) {
> >> +		if (cb->strict_check)
> >> +			return -EINVAL;
> >> +		goto walk_entries;
> >> +	}
> >>  
> >> -		if (master_idx || kind_ops)
> >> -			flags |= NLM_F_DUMP_FILTERED;
> >> +	for (i = 0; i <= IFLA_MAX; ++i) {
> >> +		if (!tb[i])
> >> +			continue;
> >> +		switch (i) {
> >> +		case IFLA_TARGET_NETNSID:
> >> +			netnsid = nla_get_s32(tb[i]);
> >> +			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
> >> +			if (IS_ERR(tgt_net)) {
> >> +				NL_SET_ERR_MSG(extack, "Invalid target namespace id");
> >> +				return PTR_ERR(tgt_net);
> >> +			}
> >> +			break;
> >> +		case IFLA_EXT_MASK:
> >> +			ext_filter_mask = nla_get_u32(tb[i]);
> >> +			break;
> >> +		case IFLA_MASTER:
> >> +			master_idx = nla_get_u32(tb[i]);
> >> +			break;
> >> +		case IFLA_LINKINFO:
> >> +			kind_ops = linkinfo_to_kind_ops(tb[i]);
> >> +			break;
> >> +		default:
> >> +			if (cb->strict_check) {
> >> +				NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
> >> +				return -EINVAL;
> >> +			}
> >> +		}
> > 
> > This might make sense to be split into two helpers for parsing:
> > <blablabla>_strict() and <blablabla>_lenient(). :)
> 
> I thought about that, but there is so much overlap - they are mostly
> common. Besides, ifinfomsg is the header for link dumps, and ifinfomsg
> is the one that has been (ab)used for other message types, so strict
> versus lenient does not really have a differentiator for this message
> type - other than checking the elements of the struct.

It's mostly about the function being extremely long and convoluted.
Having parts moved out into (a) descriptive helper(s) with whatever name
might make this way more readable than it is now especially with the new
handling we need for strict checking.
David Ahern Oct. 8, 2018, 1:29 a.m. UTC | #4
On 10/7/18 4:29 AM, Christian Brauner wrote:
>> I thought about that, but there is so much overlap - they are mostly
>> common. Besides, ifinfomsg is the header for link dumps, and ifinfomsg
>> is the one that has been (ab)used for other message types, so strict
>> versus lenient does not really have a differentiator for this message
>> type - other than checking the elements of the struct.
> 
> It's mostly about the function being extremely long and convoluted.
> Having parts moved out into (a) descriptive helper(s) with whatever name
> might make this way more readable than it is now especially with the new
> handling we need for strict checking.
> 

understood. In the next version I have pushed most of the checking into
helpers.
Christian Brauner Oct. 8, 2018, 9:47 a.m. UTC | #5
On Sun, Oct 07, 2018 at 07:29:13PM -0600, David Ahern wrote:
> On 10/7/18 4:29 AM, Christian Brauner wrote:
> >> I thought about that, but there is so much overlap - they are mostly
> >> common. Besides, ifinfomsg is the header for link dumps, and ifinfomsg
> >> is the one that has been (ab)used for other message types, so strict
> >> versus lenient does not really have a differentiator for this message
> >> type - other than checking the elements of the struct.
> > 
> > It's mostly about the function being extremely long and convoluted.
> > Having parts moved out into (a) descriptive helper(s) with whatever name
> > might make this way more readable than it is now especially with the new
> > handling we need for strict checking.
> > 
> 
> understood. In the next version I have pushed most of the checking into
> helpers.

Thank you!
diff mbox series

Patch

diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index c3b434d724ea..4fd27b5db787 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -1880,6 +1880,8 @@  EXPORT_SYMBOL_GPL(rtnl_get_net_ns_capable);
 
 static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 {
+	struct netlink_ext_ack *extack = cb->extack;
+	const struct nlmsghdr *nlh = cb->nlh;
 	struct net *net = sock_net(skb->sk);
 	struct net *tgt_net = net;
 	int h, s_h;
@@ -1892,44 +1894,84 @@  static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 	unsigned int flags = NLM_F_MULTI;
 	int master_idx = 0;
 	int netnsid = -1;
-	int err;
+	int err, i;
 	int hdrlen;
 
 	s_h = cb->args[0];
 	s_idx = cb->args[1];
 
-	/* A hack to preserve kernel<->userspace interface.
-	 * The correct header is ifinfomsg. It is consistent with rtnl_getlink.
-	 * However, before Linux v3.9 the code here assumed rtgenmsg and that's
-	 * what iproute2 < v3.9.0 used.
-	 * We can detect the old iproute2. Even including the IFLA_EXT_MASK
-	 * attribute, its netlink message is shorter than struct ifinfomsg.
-	 */
-	hdrlen = nlmsg_len(cb->nlh) < sizeof(struct ifinfomsg) ?
-		 sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
+	if (cb->strict_check) {
+		struct ifinfomsg *ifm;
 
-	if (nlmsg_parse(cb->nlh, hdrlen, tb, IFLA_MAX,
-			ifla_policy, cb->extack) >= 0) {
-		if (tb[IFLA_TARGET_NETNSID]) {
-			netnsid = nla_get_s32(tb[IFLA_TARGET_NETNSID]);
-			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
-			if (IS_ERR(tgt_net))
-				return PTR_ERR(tgt_net);
+		hdrlen = sizeof(*ifm);
+		if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) {
+			NL_SET_ERR_MSG(extack, "Invalid header");
+			return -EINVAL;
 		}
 
-		if (tb[IFLA_EXT_MASK])
-			ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
-
-		if (tb[IFLA_MASTER])
-			master_idx = nla_get_u32(tb[IFLA_MASTER]);
+		ifm = nlmsg_data(nlh);
+		if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
+		    ifm->ifi_change) {
+			NL_SET_ERR_MSG(extack, "Invalid values in header for dump request");
+			return -EINVAL;
+		}
+		if (ifm->ifi_index) {
+			NL_SET_ERR_MSG(extack, "Filter by device index not supported");
+			return -EINVAL;
+		}
+	} else {
+		/* A hack to preserve kernel<->userspace interface.
+		 * The correct header is ifinfomsg. It is consistent with
+		 * rtnl_getlink. However, before Linux v3.9 the code here
+		 * assumed rtgenmsg and that's what iproute2 < v3.9.0 used.
+		 * We can detect the old iproute2. Even including the
+		 * IFLA_EXT_MASK attribute, its netlink message is shorter
+		 * than struct ifinfomsg.
+		 */
+		hdrlen = nlmsg_len(nlh) < sizeof(struct ifinfomsg) ?
+			 sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
+	}
 
-		if (tb[IFLA_LINKINFO])
-			kind_ops = linkinfo_to_kind_ops(tb[IFLA_LINKINFO]);
+	err = nlmsg_parse(nlh, hdrlen, tb, IFLA_MAX, ifla_policy, extack);
+	if (err < 0) {
+		if (cb->strict_check)
+			return -EINVAL;
+		goto walk_entries;
+	}
 
-		if (master_idx || kind_ops)
-			flags |= NLM_F_DUMP_FILTERED;
+	for (i = 0; i <= IFLA_MAX; ++i) {
+		if (!tb[i])
+			continue;
+		switch (i) {
+		case IFLA_TARGET_NETNSID:
+			netnsid = nla_get_s32(tb[i]);
+			tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
+			if (IS_ERR(tgt_net)) {
+				NL_SET_ERR_MSG(extack, "Invalid target namespace id");
+				return PTR_ERR(tgt_net);
+			}
+			break;
+		case IFLA_EXT_MASK:
+			ext_filter_mask = nla_get_u32(tb[i]);
+			break;
+		case IFLA_MASTER:
+			master_idx = nla_get_u32(tb[i]);
+			break;
+		case IFLA_LINKINFO:
+			kind_ops = linkinfo_to_kind_ops(tb[i]);
+			break;
+		default:
+			if (cb->strict_check) {
+				NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
+				return -EINVAL;
+			}
+		}
 	}
 
+	if (master_idx || kind_ops)
+		flags |= NLM_F_DUMP_FILTERED;
+
+walk_entries:
 	for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
 		idx = 0;
 		head = &tgt_net->dev_index_head[h];
@@ -1941,8 +1983,7 @@  static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 			err = rtnl_fill_ifinfo(skb, dev, net,
 					       RTM_NEWLINK,
 					       NETLINK_CB(cb->skb).portid,
-					       cb->nlh->nlmsg_seq, 0,
-					       flags,
+					       nlh->nlmsg_seq, 0, flags,
 					       ext_filter_mask, 0, NULL, 0,
 					       netnsid);