[PATCH net-next v2 5/5] bridge: Use ndisc_parse_options() to parse ND options in br_nd_send()

Danielle Ratson <[email protected]>
Newsgroups org.kernel.vger.netdev,dev.linux.lists.bridge,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Replace the manual ND option parsing loop in br_nd_send() with
ndisc_parse_options(), which provides proper validation and avoids the
class of bugs that were fixed by commit 53fc685243bd ("bridge: Avoid
infinite loop when suppressing NS messages with invalid options") and
commit 850837965af1 ("bridge: br_nd_send: validate ND option lengths").

Use ndisc_opt_addr_data() to extract the source link-layer address
from the parsed options, which correctly validates the option length
for the underlying device type.

Export ndisc_parse_options() so that it can be resolved from the bridge
when it is built as a module (CONFIG_BRIDGE=m); otherwise modpost fails
with an undefined symbol.

Reviewed-by: Petr Machata <[email protected]>
Acked-by: Nikolay Aleksandrov <[email protected]>
Signed-off-by: Danielle Ratson <[email protected]>
---

Notes:
    v2:
    	* Use EXPORT_SYMBOL_GPL() instead of EXPORT_SYMBOL() to export
    	  ndisc_parse_options().

 net/bridge/br_arp_nd_proxy.c | 32 +++++++++++++++++---------------
 net/ipv6/ndisc.c             |  1 +
 2 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 6b6de0eff38c..b6e5a86b6a92 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -255,15 +255,16 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
 {
 	struct net_device *dev = request->dev;
 	struct net_bridge_vlan_group *vg;
+	struct ndisc_options ndopts;
 	struct nd_msg *na, *ns;
 	struct sk_buff *reply;
 	struct ipv6hdr *pip6;
 	int na_olen = 8; /* opt hdr + ETH_ALEN for target */
 	int ns_olen;
-	int i, len;
 	u8 *daddr;
 	bool dad;
 	u16 pvid;
+	int len;
 
 	if (!dev)
 		return;
@@ -284,20 +285,21 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
 	daddr = eth_hdr(request)->h_source;
 	ns = (struct nd_msg *)skb_transport_header(request);
 
-	/* Do we need option processing ? */
-	ns_olen = request->len - (skb_network_offset(request) +
-				  sizeof(struct ipv6hdr)) - sizeof(*ns);
-	for (i = 0; i < ns_olen - 1; i += (ns->opt[i + 1] << 3)) {
-		if (!ns->opt[i + 1] || i + (ns->opt[i + 1] << 3) > ns_olen) {
-			kfree_skb(reply);
-			return;
-		}
-		if (ns->opt[i] == ND_OPT_SOURCE_LL_ADDR) {
-			if ((ns->opt[i + 1] << 3) >=
-			    sizeof(struct nd_opt_hdr) + ETH_ALEN)
-				daddr = ns->opt + i + sizeof(struct nd_opt_hdr);
-			break;
-		}
+	/* Derive the option length from the IPv6 payload length so that any
+	 * trailing L2 padding in the skb is not parsed as ND options.
+	 */
+	ns_olen = ntohs(ipv6_hdr(request)->payload_len) - sizeof(*ns);
+	if (!ndisc_parse_options(dev, ns->opt, ns_olen, &ndopts)) {
+		kfree_skb(reply);
+		return;
+	}
+
+	if (ndopts.nd_opts_src_lladdr) {
+		u8 *lladdr;
+
+		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);
+		if (lladdr)
+			daddr = lladdr;
 	}
 
 	dad = ipv6_addr_any(&ipv6_hdr(request)->saddr);
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index fe36b3f51285..2ceb655c4229 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -283,6 +283,7 @@ struct ndisc_options *ndisc_parse_options(const struct net_device *dev,
 	}
 	return ndopts;
 }
+EXPORT_SYMBOL_GPL(ndisc_parse_options);
 
 int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
 {
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.