CVE-2016-8633: Improper Access Control (CWE-284)
value: drivers/firewire/net.c in the Linux kernel before 4.8.7, in certain unusual hardware configurations, allows remote attackers to execute arbitrary code via crafted fragmented packets.
CWE description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVSS v3 base score: 6.8
Published: 2016-11-28T03:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: drivers/firewire/net.c
Commit date: 2016-11-03 14:46:39 +0100
Commit message: firewire: net: guard against rx buffer overflows

The IP-over-1394 driver firewire-net lacked input validation when
handling incoming fragmented datagrams.  A maliciously formed fragment
with a respectively large datagram_offset would cause a memcpy past the
datagram buffer.

So, drop any packets carrying a fragment with offset + length larger
than datagram_size.

In addition, ensure that
  - GASP header, unfragmented encapsulation header, or fragment
    encapsulation header actually exists before we access it,
  - the encapsulated datagram or fragment is of nonzero size.

Reported-by: Eyal Itkin <eyal.itkin@gmail.com>
Reviewed-by: Eyal Itkin <eyal.itkin@gmail.com>
Fixes: CVE 2016-8633
Cc: stable@vger.kernel.org
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>

Diff: diff --git a/drivers/firewire/net.c b/drivers/firewire/net.c
index 309311b1faae18..cbaa8d776d8350 100644
--- a/drivers/firewire/net.c
+++ b/drivers/firewire/net.c
@@ -576,10 +576,13 @@ static int fwnet_incoming_packet(struct fwnet_device *dev, __be32 *buf, int len,
 	int dg_size;
 	u16 datagram_label;
 	int retval;
 	u16 ether_type;
 
+	if (len <= RFC2374_UNFRAG_HDR_SIZE)
+		return 0;
+
 	hdr.w0 = be32_to_cpu(buf[0]);
 	lf = fwnet_get_hdr_lf(&hdr);
 	if (lf == RFC2374_HDR_UNFRAG) {
 		/*
 		 * An unfragmented datagram has been received by the ieee1394
@@ -600,11 +603,16 @@ static int fwnet_incoming_packet(struct fwnet_device *dev, __be32 *buf, int len,
 		memcpy(skb_put(skb, len), buf, len);
 
 		return fwnet_finish_incoming_packet(net, skb, source_node_id,
 						    is_broadcast, ether_type);
 	}
+
 	/* A datagram fragment has been received, now the fun begins. */
+
+	if (len <= RFC2374_FRAG_HDR_SIZE)
+		return 0;
+
 	hdr.w1 = ntohl(buf[1]);
 	buf += 2;
 	len -= RFC2374_FRAG_HDR_SIZE;
 	if (lf == RFC2374_HDR_FIRSTFRAG) {
 		ether_type = fwnet_get_hdr_ether_type(&hdr);
@@ -614,10 +622,13 @@ static int fwnet_incoming_packet(struct fwnet_device *dev, __be32 *buf, int len,
 		fg_off = fwnet_get_hdr_fg_off(&hdr);
 	}
 	datagram_label = fwnet_get_hdr_dgl(&hdr);
 	dg_size = fwnet_get_hdr_dg_size(&hdr); /* ??? + 1 */
 
+	if (fg_off + len > dg_size)
+		return 0;
+
 	spin_lock_irqsave(&dev->lock, flags);
 
 	peer = fwnet_peer_find_by_node_id(dev, source_node_id, generation);
 	if (!peer) {
 		retval = -ENOENT;
@@ -720,22 +731,35 @@ static void fwnet_receive_packet(struct fw_card *card, struct fw_request *r,
 		rcode = RCODE_COMPLETE;
 
 	fw_send_response(card, r, rcode);
 }
 
+static int gasp_source_id(__be32 *p)
+{
+	return be32_to_cpu(p[0]) >> 16;
+}
+
+static u32 gasp_specifier_id(__be32 *p)
+{
+	return (be32_to_cpu(p[0]) & 0xffff) << 8 |
+	       (be32_to_cpu(p[1]) & 0xff000000) >> 24;
+}
+
+static u32 gasp_version(__be32 *p)
+{
+	return be32_to_cpu(p[1]) & 0xffffff;
+}
+
 static void fwnet_receive_broadcast(struct fw_iso_context *context,
 		u32 cycle, size_t header_length, void *header, void *data)
 {
 	struct fwnet_device *dev;
 	struct fw_iso_packet packet;
 	__be16 *hdr_ptr;
 	__be32 *buf_ptr;
 	int retval;
 	u32 length;
-	u16 source_node_id;
-	u32 specifier_id;
-	u32 ver;
 	unsigned long offset;
 	unsigned long flags;
 
 	dev = data;
 	hdr_ptr = header;
@@ -748,26 +772,21 @@ static void fwnet_receive_broadcast(struct fw_iso_context *context,
 	if (dev->broadcast_rcv_next_ptr == dev->num_broadcast_rcv_ptrs)
 		dev->broadcast_rcv_next_ptr = 0;
 
 	spin_unlock_irqrestore(&dev->lock, flags);
 
-	specifier_id =    (be32_to_cpu(buf_ptr[0]) & 0xffff) << 8
-			| (be32_to_cpu(buf_ptr[1]) & 0xff000000) >> 24;
-	ver = be32_to_cpu(buf_ptr[1]) & 0xffffff;
-	source_node_id = be32_to_cpu(buf_ptr[0]) >> 16;
-
-	if (specifier_id == IANA_SPECIFIER_ID &&
-	    (ver == RFC2734_SW_VERSION
+	if (length > IEEE1394_GASP_HDR_SIZE &&
+	    gasp_specifier_id(buf_ptr) == IANA_SPECIFIER_ID &&
+	    (gasp_version(buf_ptr) == RFC2734_SW_VERSION
 #if IS_ENABLED(CONFIG_IPV6)
-	     || ver == RFC3146_SW_VERSION
+	     || gasp_version(buf_ptr) == RFC3146_SW_VERSION
 #endif
-	    )) {
-		buf_ptr += 2;
-		length -= IEEE1394_GASP_HDR_SIZE;
-		fwnet_incoming_packet(dev, buf_ptr, length, source_node_id,
+	    ))
+		fwnet_incoming_packet(dev, buf_ptr + 2,
+				      length - IEEE1394_GASP_HDR_SIZE,
+				      gasp_source_id(buf_ptr),
 				      context->card->generation, true);
-	}
 
 	packet.payload_length = dev->rcv_buffer_size;
 	packet.interrupt = 1;
 	packet.skip = 0;
 	packet.tag = 3;

Vulnerable code: 	u16 source_node_id;
	u32 specifier_id;
	u32 ver;
	specifier_id =    (be32_to_cpu(buf_ptr[0]) & 0xffff) << 8
			| (be32_to_cpu(buf_ptr[1]) & 0xff000000) >> 24;
	ver = be32_to_cpu(buf_ptr[1]) & 0xffffff;
	source_node_id = be32_to_cpu(buf_ptr[0]) >> 16;

	if (specifier_id == IANA_SPECIFIER_ID &&
	    (ver == RFC2734_SW_VERSION
	     || ver == RFC3146_SW_VERSION
	    )) {
		buf_ptr += 2;
		length -= IEEE1394_GASP_HDR_SIZE;
		fwnet_incoming_packet(dev, buf_ptr, length, source_node_id,
	}
Fixed code: 	if (len <= RFC2374_UNFRAG_HDR_SIZE)
		return 0;



	if (len <= RFC2374_FRAG_HDR_SIZE)
		return 0;

	if (fg_off + len > dg_size)
		return 0;

static int gasp_source_id(__be32 *p)
{
	return be32_to_cpu(p[0]) >> 16;
}

static u32 gasp_specifier_id(__be32 *p)
{
	return (be32_to_cpu(p[0]) & 0xffff) << 8 |
	       (be32_to_cpu(p[1]) & 0xff000000) >> 24;
}

static u32 gasp_version(__be32 *p)
{
	return be32_to_cpu(p[1]) & 0xffffff;
}

	if (length > IEEE1394_GASP_HDR_SIZE &&
	    gasp_specifier_id(buf_ptr) == IANA_SPECIFIER_ID &&
	    (gasp_version(buf_ptr) == RFC2734_SW_VERSION
	     || gasp_version(buf_ptr) == RFC3146_SW_VERSION
	    ))
		fwnet_incoming_packet(dev, buf_ptr + 2,
				      length - IEEE1394_GASP_HDR_SIZE,
				      gasp_source_id(buf_ptr),