CVE-2016-3698: Improper Access Control (CWE-284)
value: libndp before 1.6, as used in NetworkManager, does not properly validate the origin of Neighbor Discovery Protocol (NDP) messages, which allows remote attackers to conduct man-in-the-middle attacks or cause a denial of service (network connectivity disruption) by advertising a node as a router from a non-local network.
CWE description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVSS v3 base score: 8.1
Published: 2016-06-13T19:59Z
Repository: https://github.com/jpirko/libndp
Language: C
Files changed: libndp/libndp.c
Commit date: 2016-05-17 12:25:44 +0200
Commit message: libndp: validate the IPv6 hop limit

None of the NDP messages should ever come from a non-local network; as
stated in RFC4861's 6.1.1 (RS), 6.1.2 (RA), 7.1.1 (NS), 7.1.2 (NA),
and 8.1. (redirect):

  - The IP Hop Limit field has a value of 255, i.e., the packet
    could not possibly have been forwarded by a router.

This fixes CVE-2016-3698.

Reported by: Julien BERNARD <julien.bernard@viagenie.ca>
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>

Diff: diff --git a/libndp/libndp.c b/libndp/libndp.c
index 8b7e609..5472c86 100644
--- a/libndp/libndp.c
+++ b/libndp/libndp.c
@@ -135,14 +135,14 @@ static void *myzalloc(size_t size)
 {
 	return calloc(1, size);
 }
 
 static int myrecvfrom6(int sockfd, void *buf, size_t *buflen, int flags,
-		       struct in6_addr *addr, uint32_t *ifindex)
+		       struct in6_addr *addr, uint32_t *ifindex, int *hoplimit)
 {
 	struct sockaddr_in6 sin6;
-	unsigned char cbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+	unsigned char cbuf[2 * CMSG_SPACE(sizeof(struct in6_pktinfo))];
 	struct iovec iovec;
 	struct msghdr msghdr;
 	struct cmsghdr *cmsghdr;
 	ssize_t len;
 
@@ -166,17 +166,30 @@ static int myrecvfrom6(int sockfd, void *buf, size_t *buflen, int flags,
 	 * value right after.
 	 */
 	*ifindex = sin6.sin6_scope_id;
         for (cmsghdr = CMSG_FIRSTHDR(&msghdr); cmsghdr;
 	     cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) {
-		if (cmsghdr->cmsg_level == IPPROTO_IPV6 &&
-		    cmsghdr->cmsg_type == IPV6_PKTINFO &&
-		    cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
-			struct in6_pktinfo *pktinfo;
+		if (cmsghdr->cmsg_level != IPPROTO_IPV6)
+			continue;
+
+		switch(cmsghdr->cmsg_type) {
+		case IPV6_PKTINFO:
+			if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+				struct in6_pktinfo *pktinfo;
+
+				pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsghdr);
+				*ifindex = pktinfo->ipi6_ifindex;
+			}
+			break;
+		case IPV6_HOPLIMIT:
+			if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) {
+				int *val;
 
-			pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsghdr);
-			*ifindex = pktinfo->ipi6_ifindex;
+				val = (int *) CMSG_DATA(cmsghdr);
+				*hoplimit = *val;
+			}
+			break;
 		}
 	}
 	*addr = sin6.sin6_addr;
 
 	return 0;
@@ -247,10 +260,19 @@ static int ndp_sock_open(struct ndp *ndp)
 		err(ndp, "Failed to setsockopt IPV6_MULTICAST_HOPS.");
 		err = -errno;
 		goto close_sock;
 	}
 
+	val = 1;
+	ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+			 &val, sizeof(val));
+	if (ret == -1) {
+		err(ndp, "Failed to setsockopt IPV6_RECVHOPLIMIT,.");
+		err = -errno;
+		goto close_sock;
+	}
+
 	ndp->sock = sock;
 	return 0;
 close_sock:
 	close(sock);
 	return err;
@@ -289,10 +311,11 @@ struct ndp_msg {
 #define NDP_MSG_BUFLEN 1500
 	unsigned char			buf[NDP_MSG_BUFLEN];
 	size_t				len;
 	struct in6_addr			addrto;
 	uint32_t			ifindex;
+	int				hoplimit;
 	struct icmp6_hdr *		icmp6_hdr;
 	unsigned char *			opts_start; /* pointer to buf at the
 						       place where opts start */
 	union {
 		struct ndp_msggeneric	generic;
@@ -1695,17 +1718,23 @@ static int ndp_sock_recv(struct ndp *ndp)
 	if (!msg)
 		return -ENOMEM;
 
 	len = ndp_msg_payload_maxlen(msg);
 	err = myrecvfrom6(ndp->sock, msg->buf, &len, 0,
-			  &msg->addrto, &msg->ifindex);
+			  &msg->addrto, &msg->ifindex, &msg->hoplimit);
 	if (err) {
 		err(ndp, "Failed to receive message");
 		goto free_msg;
 	}
-	dbg(ndp, "rcvd from: %s, ifindex: %u",
-		 str_in6_addr(&msg->addrto), msg->ifindex);
+	dbg(ndp, "rcvd from: %s, ifindex: %u, hoplimit: %d",
+		 str_in6_addr(&msg->addrto), msg->ifindex, msg->hoplimit);
+
+	if (msg->hoplimit != 255) {
+		warn(ndp, "ignoring packet with bad hop limit (%d)", msg->hoplimit);
+		err = 0;
+		goto free_msg;
+	}
 
 	if (len < sizeof(*msg->icmp6_hdr)) {
 		warn(ndp, "rcvd icmp6 packet too short (%luB)", len);
 		err = 0;
 		goto free_msg;

Vulnerable code: 		       struct in6_addr *addr, uint32_t *ifindex)
	unsigned char cbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
		if (cmsghdr->cmsg_level == IPPROTO_IPV6 &&
		    cmsghdr->cmsg_type == IPV6_PKTINFO &&
		    cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
			struct in6_pktinfo *pktinfo;
			pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsghdr);
			*ifindex = pktinfo->ipi6_ifindex;
			  &msg->addrto, &msg->ifindex);
	dbg(ndp, "rcvd from: %s, ifindex: %u",
		 str_in6_addr(&msg->addrto), msg->ifindex);
Fixed code: 		       struct in6_addr *addr, uint32_t *ifindex, int *hoplimit)
	unsigned char cbuf[2 * CMSG_SPACE(sizeof(struct in6_pktinfo))];
		if (cmsghdr->cmsg_level != IPPROTO_IPV6)
			continue;

		switch(cmsghdr->cmsg_type) {
		case IPV6_PKTINFO:
			if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
				struct in6_pktinfo *pktinfo;

				pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsghdr);
				*ifindex = pktinfo->ipi6_ifindex;
			}
			break;
		case IPV6_HOPLIMIT:
			if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) {
				int *val;
				val = (int *) CMSG_DATA(cmsghdr);
				*hoplimit = *val;
			}
			break;
	val = 1;
	ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
			 &val, sizeof(val));
	if (ret == -1) {
		err(ndp, "Failed to setsockopt IPV6_RECVHOPLIMIT,.");
		err = -errno;
		goto close_sock;
	}

	int				hoplimit;
			  &msg->addrto, &msg->ifindex, &msg->hoplimit);
	dbg(ndp, "rcvd from: %s, ifindex: %u, hoplimit: %d",
		 str_in6_addr(&msg->addrto), msg->ifindex, msg->hoplimit);

	if (msg->hoplimit != 255) {
		warn(ndp, "ignoring packet with bad hop limit (%d)", msg->hoplimit);
		err = 0;
		goto free_msg;
	}