Commit message: catc: Use heap buffer for memory size test

Allocating USB buffers on the stack is not portable, and no longer
works on x86_64 (with VMAP_STACK enabled as per default).

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2017-8070
CWE: CWE-119
Vulnerability description: drivers/net/usb/catc.c in the Linux kernel 4.9.x before 4.9.11 interacts incorrectly with the CONFIG_VMAP_STACK option, which allows local users to cause a denial of service (system crash or memory corruption) or possibly have unspecified other impact by leveraging use of more than one virtual page for a DMA scatterlist.

Project: linux
File: catc.c

static int catc_ctrl_async(struct catc *catc, u8 dir, u8 request, u16 value,
	u16 index, void *buf, int len, void (*callback)(struct catc *catc, struct ctrl_queue *q))
{
	struct ctrl_queue *q;
	int retval = 0;
	unsigned long flags;

	spin_lock_irqsave(&catc->ctrl_lock, flags);
	
	q = catc->ctrl_queue + catc->ctrl_head;

	q->dir = dir;
	q->request = request;
	q->value = value;
	q->index = index;
	q->buf = buf;
	q->len = len;
	q->callback = callback;

	catc->ctrl_head = (catc->ctrl_head + 1) & (CTRL_QUEUE - 1);

	if (catc->ctrl_head == catc->ctrl_tail) {
		dev_err(&catc->usbdev->dev, "ctrl queue full\n");
		catc->ctrl_tail = (catc->ctrl_tail + 1) & (CTRL_QUEUE - 1);
		retval = -1;
	}

	if (!test_and_set_bit(CTRL_RUNNING, &catc->flags))
		catc_ctrl_run(catc);

	spin_unlock_irqrestore(&catc->ctrl_lock, flags);

	return retval;
}

Project: linux
File: catc.c

static void catc_ctrl_done(struct urb *urb)
{
	struct catc *catc = urb->context;
	struct ctrl_queue *q;
	unsigned long flags;
	int status = urb->status;

	if (status)
		dev_dbg(&urb->dev->dev, "ctrl_done, status %d, len %d.\n",
			status, urb->actual_length);

	spin_lock_irqsave(&catc->ctrl_lock, flags);

	q = catc->ctrl_queue + catc->ctrl_tail;

	if (q->dir) {
		if (q->buf && q->len)
			memcpy(q->buf, catc->ctrl_buf, q->len);
		else
			q->buf = catc->ctrl_buf;
	}

	if (q->callback)
		q->callback(catc, q);

	catc->ctrl_tail = (catc->ctrl_tail + 1) & (CTRL_QUEUE - 1);

	if (catc->ctrl_head != catc->ctrl_tail)
		catc_ctrl_run(catc);
	else
		clear_bit(CTRL_RUNNING, &catc->flags);

	spin_unlock_irqrestore(&catc->ctrl_lock, flags);
}

Project: linux
File: catc.c

static int catc_ctrl_msg(struct catc *catc, u8 dir, u8 request, u16 value, u16 index, void *buf, int len)
{
        int retval = usb_control_msg(catc->usbdev,
		dir ? usb_rcvctrlpipe(catc->usbdev, 0) : usb_sndctrlpipe(catc->usbdev, 0),
		 request, 0x40 | dir, value, index, buf, len, 1000);
        return retval < 0 ? retval : 0;
}

Project: linux
File: catc.c

static void catc_ctrl_run(struct catc *catc)
{
	struct ctrl_queue *q = catc->ctrl_queue + catc->ctrl_tail;
	struct usb_device *usbdev = catc->usbdev;
	struct urb *urb = catc->ctrl_urb;
	struct usb_ctrlrequest *dr = &catc->ctrl_dr;
	int status;

	dr->bRequest = q->request;
	dr->bRequestType = 0x40 | q->dir;
	dr->wValue = cpu_to_le16(q->value);
	dr->wIndex = cpu_to_le16(q->index);
	dr->wLength = cpu_to_le16(q->len);

        urb->pipe = q->dir ? usb_rcvctrlpipe(usbdev, 0) : usb_sndctrlpipe(usbdev, 0);
	urb->transfer_buffer_length = q->len;
	urb->transfer_buffer = catc->ctrl_buf;
	urb->setup_packet = (void *) dr;
	urb->dev = usbdev;

	if (!q->dir && q->buf && q->len)
		memcpy(catc->ctrl_buf, q->buf, q->len);

	if ((status = usb_submit_urb(catc->ctrl_urb, GFP_ATOMIC)))
		dev_err(&catc->usbdev->dev, "submit(ctrl_urb) status %d\n",
			status);
}

Project: linux
File: catc.c

static void catc_disconnect(struct usb_interface *intf)
{
	struct catc *catc = usb_get_intfdata(intf);

	usb_set_intfdata(intf, NULL);
	if (catc) {
		unregister_netdev(catc->netdev);
		usb_free_urb(catc->ctrl_urb);
		usb_free_urb(catc->tx_urb);
		usb_free_urb(catc->rx_urb);
		usb_free_urb(catc->irq_urb);
		free_netdev(catc->netdev);
	}
}

Project: linux
File: catc.c

static void catc_get_drvinfo(struct net_device *dev,
			     struct ethtool_drvinfo *info)
{
	struct catc *catc = netdev_priv(dev);
	strlcpy(info->driver, driver_name, sizeof(info->driver));
	strlcpy(info->version, DRIVER_VERSION, sizeof(info->version));
	usb_make_path(catc->usbdev, info->bus_info, sizeof(info->bus_info));
}

Project: linux
File: catc.c

static int catc_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
	struct catc *catc = netdev_priv(dev);
	if (!catc->is_f5u011)
		return -EOPNOTSUPP;

	cmd->supported = SUPPORTED_10baseT_Half | SUPPORTED_TP;
	cmd->advertising = ADVERTISED_10baseT_Half | ADVERTISED_TP;
	ethtool_cmd_speed_set(cmd, SPEED_10);
	cmd->duplex = DUPLEX_HALF;
	cmd->port = PORT_TP; 
	cmd->phy_address = 0;
	cmd->transceiver = XCVR_INTERNAL;
	cmd->autoneg = AUTONEG_DISABLE;
	cmd->maxtxpkt = 1;
	cmd->maxrxpkt = 1;
	return 0;
}

Project: linux
File: catc.c

static void catc_multicast(unsigned char *addr, u8 *multicast)
{
	u32 crc;

	crc = ether_crc_le(6, addr);
	multicast[(crc >> 3) & 0x3f] |= 1 << (crc & 7);
}

Project: linux
File: catc.c

static int catc_open(struct net_device *netdev)
{
	struct catc *catc = netdev_priv(netdev);
	int status;

	catc->irq_urb->dev = catc->usbdev;
	if ((status = usb_submit_urb(catc->irq_urb, GFP_KERNEL)) < 0) {
		dev_err(&catc->usbdev->dev, "submit(irq_urb) status %d\n",
			status);
		return -1;
	}

	netif_start_queue(netdev);

	if (!catc->is_f5u011)
		mod_timer(&catc->timer, jiffies + STATS_UPDATE);

	return 0;
}

Project: linux
File: catc.c

static void catc_rx_done(struct urb *urb)
{
	struct catc *catc = urb->context;
	u8 *pkt_start = urb->transfer_buffer;
	struct sk_buff *skb;
	int pkt_len, pkt_offset = 0;
	int status = urb->status;

	if (!catc->is_f5u011) {
		clear_bit(RX_RUNNING, &catc->flags);
		pkt_offset = 2;
	}

	if (status) {
		dev_dbg(&urb->dev->dev, "rx_done, status %d, length %d\n",
			status, urb->actual_length);
		return;
	}

	do {
		if(!catc->is_f5u011) {
			pkt_len = le16_to_cpup((__le16*)pkt_start);
			if (pkt_len > urb->actual_length) {
				catc->netdev->stats.rx_length_errors++;
				catc->netdev->stats.rx_errors++;
				break;
			}
		} else {
			pkt_len = urb->actual_length;
		}

		if (!(skb = dev_alloc_skb(pkt_len)))
			return;

		skb_copy_to_linear_data(skb, pkt_start + pkt_offset, pkt_len);
		skb_put(skb, pkt_len);

		skb->protocol = eth_type_trans(skb, catc->netdev);
		netif_rx(skb);

		catc->netdev->stats.rx_packets++;
		catc->netdev->stats.rx_bytes += pkt_len;

		/* F5U011 only does one packet per RX */
		if (catc->is_f5u011)
			break;
		pkt_start += (((pkt_len + 1) >> 6) + 1) << 6;

	} while (pkt_start - (u8 *) urb->transfer_buffer < urb->actual_length);

	if (catc->is_f5u011) {
		if (atomic_read(&catc->recq_sz)) {
			int state;
			atomic_dec(&catc->recq_sz);
			netdev_dbg(catc->netdev, "getting extra packet\n");
			urb->dev = catc->usbdev;
			if ((state = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
				netdev_dbg(catc->netdev,
					   "submit(rx_urb) status %d\n", state);
			}
		} else {
			clear_bit(RX_RUNNING, &catc->flags);
		}
	}
}

Project: linux
File: catc.c

static void catc_set_multicast_list(struct net_device *netdev)
{
	struct catc *catc = netdev_priv(netdev);
	struct netdev_hw_addr *ha;
	u8 broadcast[ETH_ALEN];
	u8 rx = RxEnable | RxPolarity | RxMultiCast;

	eth_broadcast_addr(broadcast);
	memset(catc->multicast, 0, 64);

	catc_multicast(broadcast, catc->multicast);
	catc_multicast(netdev->dev_addr, catc->multicast);

	if (netdev->flags & IFF_PROMISC) {
		memset(catc->multicast, 0xff, 64);
		rx |= (!catc->is_f5u011) ? RxPromisc : AltRxPromisc;
	} 

	if (netdev->flags & IFF_ALLMULTI) {
		memset(catc->multicast, 0xff, 64);
	} else {
		netdev_for_each_mc_addr(ha, netdev) {
			u32 crc = ether_crc_le(6, ha->addr);
			if (!catc->is_f5u011) {
				catc->multicast[(crc >> 3) & 0x3f] |= 1 << (crc & 7);
			} else {
				catc->multicast[7-(crc >> 29)] |= 1 << ((crc >> 26) & 7);
			}
		}
	}
	if (!catc->is_f5u011) {
		catc_set_reg_async(catc, RxUnit, rx);
		catc_write_mem_async(catc, 0xfa80, catc->multicast, 64);
	} else {
		f5u011_mchash_async(catc, catc->multicast);
		if (catc->rxmode[0] != rx) {
			catc->rxmode[0] = rx;
			netdev_dbg(catc->netdev,
				   "Setting RX mode to %2.2X %2.2X\n",
				   catc->rxmode[0], catc->rxmode[1]);
			f5u011_rxmode_async(catc, catc->rxmode);
		}
	}
}

Project: linux
File: catc.c

static netdev_tx_t catc_start_xmit(struct sk_buff *skb,
					 struct net_device *netdev)
{
	struct catc *catc = netdev_priv(netdev);
	unsigned long flags;
	int r = 0;
	char *tx_buf;

	spin_lock_irqsave(&catc->tx_lock, flags);

	catc->tx_ptr = (((catc->tx_ptr - 1) >> 6) + 1) << 6;
	tx_buf = catc->tx_buf[catc->tx_idx] + catc->tx_ptr;
	if (catc->is_f5u011)
		*(__be16 *)tx_buf = cpu_to_be16(skb->len);
	else
		*(__le16 *)tx_buf = cpu_to_le16(skb->len);
	skb_copy_from_linear_data(skb, tx_buf + 2, skb->len);
	catc->tx_ptr += skb->len + 2;

	if (!test_and_set_bit(TX_RUNNING, &catc->flags)) {
		r = catc_tx_run(catc);
		if (r < 0)
			clear_bit(TX_RUNNING, &catc->flags);
	}

	if ((catc->is_f5u011 && catc->tx_ptr) ||
	    (catc->tx_ptr >= ((TX_MAX_BURST - 1) * (PKT_SZ + 2))))
		netif_stop_queue(netdev);

	spin_unlock_irqrestore(&catc->tx_lock, flags);

	if (r >= 0) {
		catc->netdev->stats.tx_bytes += skb->len;
		catc->netdev->stats.tx_packets++;
	}

	dev_kfree_skb(skb);

	return NETDEV_TX_OK;
}

Project: linux
File: catc.c

static void catc_stats_done(struct catc *catc, struct ctrl_queue *q)
{
	int index = q->index - EthStats;
	u16 data, last;

	catc->stats_buf[index] = *((char *)q->buf);

	if (index & 1)
		return;

	data = ((u16)catc->stats_buf[index] << 8) | catc->stats_buf[index + 1];
	last = catc->stats_vals[index >> 1];

	switch (index) {
		case TxSingleColl:
		case TxMultiColl:
			catc->netdev->stats.collisions += data - last;
			break;
		case TxExcessColl:
			catc->netdev->stats.tx_aborted_errors += data - last;
			catc->netdev->stats.tx_errors += data - last;
			break;
		case RxFramErr:
			catc->netdev->stats.rx_frame_errors += data - last;
			catc->netdev->stats.rx_errors += data - last;
			break;
	}

	catc->stats_vals[index >> 1] = data;
}

Project: linux
File: catc.c

static void catc_stats_timer(unsigned long data)
{
	struct catc *catc = (void *) data;
	int i;

	for (i = 0; i < 8; i++)
		catc_get_reg_async(catc, EthStats + 7 - i, catc_stats_done);

	mod_timer(&catc->timer, jiffies + STATS_UPDATE);
}

Project: linux
File: catc.c

static int catc_stop(struct net_device *netdev)
{
	struct catc *catc = netdev_priv(netdev);

	netif_stop_queue(netdev);

	if (!catc->is_f5u011)
		del_timer_sync(&catc->timer);

	usb_kill_urb(catc->rx_urb);
	usb_kill_urb(catc->tx_urb);
	usb_kill_urb(catc->irq_urb);
	usb_kill_urb(catc->ctrl_urb);

	return 0;
}

Project: linux
File: catc.c

static void catc_tx_done(struct urb *urb)
{
	struct catc *catc = urb->context;
	unsigned long flags;
	int r, status = urb->status;

	if (status == -ECONNRESET) {
		dev_dbg(&urb->dev->dev, "Tx Reset.\n");
		urb->status = 0;
		netif_trans_update(catc->netdev);
		catc->netdev->stats.tx_errors++;
		clear_bit(TX_RUNNING, &catc->flags);
		netif_wake_queue(catc->netdev);
		return;
	}

	if (status) {
		dev_dbg(&urb->dev->dev, "tx_done, status %d, length %d\n",
			status, urb->actual_length);
		return;
	}

	spin_lock_irqsave(&catc->tx_lock, flags);

	if (catc->tx_ptr) {
		r = catc_tx_run(catc);
		if (unlikely(r < 0))
			clear_bit(TX_RUNNING, &catc->flags);
	} else {
		clear_bit(TX_RUNNING, &catc->flags);
	}

	netif_wake_queue(catc->netdev);

	spin_unlock_irqrestore(&catc->tx_lock, flags);
}

Project: linux
File: catc.c

static int catc_tx_run(struct catc *catc)
{
	int status;

	if (catc->is_f5u011)
		catc->tx_ptr = (catc->tx_ptr + 63) & ~63;

	catc->tx_urb->transfer_buffer_length = catc->tx_ptr;
	catc->tx_urb->transfer_buffer = catc->tx_buf[catc->tx_idx];
	catc->tx_urb->dev = catc->usbdev;

	if ((status = usb_submit_urb(catc->tx_urb, GFP_ATOMIC)) < 0)
		dev_err(&catc->usbdev->dev, "submit(tx_urb), status %d\n",
			status);

	catc->tx_idx = !catc->tx_idx;
	catc->tx_ptr = 0;

	netif_trans_update(catc->netdev);
	return status;
}

Project: linux
File: catc.c

static void catc_tx_timeout(struct net_device *netdev)
{
	struct catc *catc = netdev_priv(netdev);

	dev_warn(&netdev->dev, "Transmit timed out.\n");
	usb_unlink_urb(catc->tx_urb);
}