diff options
Diffstat (limited to 'drivers/usb/core/devio.c')
| -rw-r--r-- | drivers/usb/core/devio.c | 189 | 
1 files changed, 125 insertions, 64 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index e3beaf229ee..3af5e2dd1d8 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -86,6 +86,7 @@ struct async {  	void __user *userbuffer;  	void __user *userurb;  	struct urb *urb; +	unsigned int mem_usage;  	int status;  	u32 secid;  	u8 bulk_addr; @@ -108,8 +109,44 @@ enum snoop_when {  #define USB_DEVICE_DEV		MKDEV(USB_DEVICE_MAJOR, 0) -#define	MAX_USBFS_BUFFER_SIZE	16384 +/* Limit on the total amount of memory we can allocate for transfers */ +static unsigned usbfs_memory_mb = 16; +module_param(usbfs_memory_mb, uint, 0644); +MODULE_PARM_DESC(usbfs_memory_mb, +		"maximum MB allowed for usbfs buffers (0 = no limit)"); +/* Hard limit, necessary to avoid aithmetic overflow */ +#define USBFS_XFER_MAX		(UINT_MAX / 2 - 1000000) + +static atomic_t usbfs_memory_usage;	/* Total memory currently allocated */ + +/* Check whether it's okay to allocate more memory for a transfer */ +static int usbfs_increase_memory_usage(unsigned amount) +{ +	unsigned lim; + +	/* +	 * Convert usbfs_memory_mb to bytes, avoiding overflows. +	 * 0 means use the hard limit (effectively unlimited). +	 */ +	lim = ACCESS_ONCE(usbfs_memory_mb); +	if (lim == 0 || lim > (USBFS_XFER_MAX >> 20)) +		lim = USBFS_XFER_MAX; +	else +		lim <<= 20; + +	atomic_add(amount, &usbfs_memory_usage); +	if (atomic_read(&usbfs_memory_usage) <= lim) +		return 0; +	atomic_sub(amount, &usbfs_memory_usage); +	return -ENOMEM; +} + +/* Memory for a transfer is being deallocated */ +static void usbfs_decrease_memory_usage(unsigned amount) +{ +	atomic_sub(amount, &usbfs_memory_usage); +}  static int connected(struct dev_state *ps)  { @@ -249,10 +286,12 @@ static struct async *alloc_async(unsigned int numisoframes)  static void free_async(struct async *as)  {  	put_pid(as->pid); -	put_cred(as->cred); +	if (as->cred) +		put_cred(as->cred);  	kfree(as->urb->transfer_buffer);  	kfree(as->urb->setup_packet);  	usb_free_urb(as->urb); +	usbfs_decrease_memory_usage(as->mem_usage);  	kfree(as);  } @@ -792,9 +831,15 @@ static int proc_control(struct dev_state *ps, void __user *arg)  	wLength = ctrl.wLength;		/* To suppress 64k PAGE_SIZE warning */  	if (wLength > PAGE_SIZE)  		return -EINVAL; +	ret = usbfs_increase_memory_usage(PAGE_SIZE + sizeof(struct urb) + +			sizeof(struct usb_ctrlrequest)); +	if (ret) +		return ret;  	tbuf = (unsigned char *)__get_free_page(GFP_KERNEL); -	if (!tbuf) -		return -ENOMEM; +	if (!tbuf) { +		ret = -ENOMEM; +		goto done; +	}  	tmo = ctrl.timeout;  	snoop(&dev->dev, "control urb: bRequestType=%02x "  		"bRequest=%02x wValue=%04x " @@ -806,8 +851,8 @@ static int proc_control(struct dev_state *ps, void __user *arg)  	if (ctrl.bRequestType & 0x80) {  		if (ctrl.wLength && !access_ok(VERIFY_WRITE, ctrl.data,  					       ctrl.wLength)) { -			free_page((unsigned long)tbuf); -			return -EINVAL; +			ret = -EINVAL; +			goto done;  		}  		pipe = usb_rcvctrlpipe(dev, 0);  		snoop_urb(dev, NULL, pipe, ctrl.wLength, tmo, SUBMIT, NULL, 0); @@ -821,15 +866,15 @@ static int proc_control(struct dev_state *ps, void __user *arg)  			  tbuf, max(i, 0));  		if ((i > 0) && ctrl.wLength) {  			if (copy_to_user(ctrl.data, tbuf, i)) { -				free_page((unsigned long)tbuf); -				return -EFAULT; +				ret = -EFAULT; +				goto done;  			}  		}  	} else {  		if (ctrl.wLength) {  			if (copy_from_user(tbuf, ctrl.data, ctrl.wLength)) { -				free_page((unsigned long)tbuf); -				return -EFAULT; +				ret = -EFAULT; +				goto done;  			}  		}  		pipe = usb_sndctrlpipe(dev, 0); @@ -843,14 +888,18 @@ static int proc_control(struct dev_state *ps, void __user *arg)  		usb_lock_device(dev);  		snoop_urb(dev, NULL, pipe, max(i, 0), min(i, 0), COMPLETE, NULL, 0);  	} -	free_page((unsigned long)tbuf);  	if (i < 0 && i != -EPIPE) {  		dev_printk(KERN_DEBUG, &dev->dev, "usbfs: USBDEVFS_CONTROL "  			   "failed cmd %s rqt %u rq %u len %u ret %d\n",  			   current->comm, ctrl.bRequestType, ctrl.bRequest,  			   ctrl.wLength, i);  	} -	return i; +	ret = i; + done: +	free_page((unsigned long) tbuf); +	usbfs_decrease_memory_usage(PAGE_SIZE + sizeof(struct urb) + +			sizeof(struct usb_ctrlrequest)); +	return ret;  }  static int proc_bulk(struct dev_state *ps, void __user *arg) @@ -877,15 +926,20 @@ static int proc_bulk(struct dev_state *ps, void __user *arg)  	if (!usb_maxpacket(dev, pipe, !(bulk.ep & USB_DIR_IN)))  		return -EINVAL;  	len1 = bulk.len; -	if (len1 > MAX_USBFS_BUFFER_SIZE) +	if (len1 >= USBFS_XFER_MAX)  		return -EINVAL; -	if (!(tbuf = kmalloc(len1, GFP_KERNEL))) -		return -ENOMEM; +	ret = usbfs_increase_memory_usage(len1 + sizeof(struct urb)); +	if (ret) +		return ret; +	if (!(tbuf = kmalloc(len1, GFP_KERNEL))) { +		ret = -ENOMEM; +		goto done; +	}  	tmo = bulk.timeout;  	if (bulk.ep & 0x80) {  		if (len1 && !access_ok(VERIFY_WRITE, bulk.data, len1)) { -			kfree(tbuf); -			return -EINVAL; +			ret = -EINVAL; +			goto done;  		}  		snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, NULL, 0); @@ -896,15 +950,15 @@ static int proc_bulk(struct dev_state *ps, void __user *arg)  		if (!i && len2) {  			if (copy_to_user(bulk.data, tbuf, len2)) { -				kfree(tbuf); -				return -EFAULT; +				ret = -EFAULT; +				goto done;  			}  		}  	} else {  		if (len1) {  			if (copy_from_user(tbuf, bulk.data, len1)) { -				kfree(tbuf); -				return -EFAULT; +				ret = -EFAULT; +				goto done;  			}  		}  		snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, tbuf, len1); @@ -914,10 +968,11 @@ static int proc_bulk(struct dev_state *ps, void __user *arg)  		usb_lock_device(dev);  		snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, NULL, 0);  	} +	ret = (i < 0 ? i : len2); + done:  	kfree(tbuf); -	if (i < 0) -		return i; -	return len2; +	usbfs_decrease_memory_usage(len1 + sizeof(struct urb)); +	return ret;  }  static int proc_resetep(struct dev_state *ps, void __user *arg) @@ -1062,7 +1117,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  {  	struct usbdevfs_iso_packet_desc *isopkt = NULL;  	struct usb_host_endpoint *ep; -	struct async *as; +	struct async *as = NULL;  	struct usb_ctrlrequest *dr = NULL;  	unsigned int u, totlen, isofrmlen;  	int ret, ifnum = -1; @@ -1095,32 +1150,30 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  	}  	if (!ep)  		return -ENOENT; + +	u = 0;  	switch(uurb->type) {  	case USBDEVFS_URB_TYPE_CONTROL:  		if (!usb_endpoint_xfer_control(&ep->desc))  			return -EINVAL; -		/* min 8 byte setup packet, -		 * max 8 byte setup plus an arbitrary data stage */ -		if (uurb->buffer_length < 8 || -		    uurb->buffer_length > (8 + MAX_USBFS_BUFFER_SIZE)) +		/* min 8 byte setup packet */ +		if (uurb->buffer_length < 8)  			return -EINVAL;  		dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);  		if (!dr)  			return -ENOMEM;  		if (copy_from_user(dr, uurb->buffer, 8)) { -			kfree(dr); -			return -EFAULT; +			ret = -EFAULT; +			goto error;  		}  		if (uurb->buffer_length < (le16_to_cpup(&dr->wLength) + 8)) { -			kfree(dr); -			return -EINVAL; +			ret = -EINVAL; +			goto error;  		}  		ret = check_ctrlrecip(ps, dr->bRequestType, dr->bRequest,  				      le16_to_cpup(&dr->wIndex)); -		if (ret) { -			kfree(dr); -			return ret; -		} +		if (ret) +			goto error;  		uurb->number_of_packets = 0;  		uurb->buffer_length = le16_to_cpup(&dr->wLength);  		uurb->buffer += 8; @@ -1138,6 +1191,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  			__le16_to_cpup(&dr->wValue),  			__le16_to_cpup(&dr->wIndex),  			__le16_to_cpup(&dr->wLength)); +		u = sizeof(struct usb_ctrlrequest);  		break;  	case USBDEVFS_URB_TYPE_BULK: @@ -1151,8 +1205,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  			goto interrupt_urb;  		}  		uurb->number_of_packets = 0; -		if (uurb->buffer_length > MAX_USBFS_BUFFER_SIZE) -			return -EINVAL;  		break;  	case USBDEVFS_URB_TYPE_INTERRUPT: @@ -1160,8 +1212,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  			return -EINVAL;   interrupt_urb:  		uurb->number_of_packets = 0; -		if (uurb->buffer_length > MAX_USBFS_BUFFER_SIZE) -			return -EINVAL;  		break;  	case USBDEVFS_URB_TYPE_ISO: @@ -1176,50 +1226,53 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  		if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL)))  			return -ENOMEM;  		if (copy_from_user(isopkt, iso_frame_desc, isofrmlen)) { -			kfree(isopkt); -			return -EFAULT; +			ret = -EFAULT; +			goto error;  		}  		for (totlen = u = 0; u < uurb->number_of_packets; u++) {  			/* arbitrary limit,  			 * sufficient for USB 2.0 high-bandwidth iso */  			if (isopkt[u].length > 8192) { -				kfree(isopkt); -				return -EINVAL; +				ret = -EINVAL; +				goto error;  			}  			totlen += isopkt[u].length;  		} -		/* 3072 * 64 microframes */ -		if (totlen > 196608) { -			kfree(isopkt); -			return -EINVAL; -		} +		u *= sizeof(struct usb_iso_packet_descriptor);  		uurb->buffer_length = totlen;  		break;  	default:  		return -EINVAL;  	} + +	if (uurb->buffer_length >= USBFS_XFER_MAX) { +		ret = -EINVAL; +		goto error; +	}  	if (uurb->buffer_length > 0 &&  			!access_ok(is_in ? VERIFY_WRITE : VERIFY_READ,  				uurb->buffer, uurb->buffer_length)) { -		kfree(isopkt); -		kfree(dr); -		return -EFAULT; +		ret = -EFAULT; +		goto error;  	}  	as = alloc_async(uurb->number_of_packets);  	if (!as) { -		kfree(isopkt); -		kfree(dr); -		return -ENOMEM; +		ret = -ENOMEM; +		goto error;  	} +	u += sizeof(struct async) + sizeof(struct urb) + uurb->buffer_length; +	ret = usbfs_increase_memory_usage(u); +	if (ret) +		goto error; +	as->mem_usage = u; +  	if (uurb->buffer_length > 0) {  		as->urb->transfer_buffer = kmalloc(uurb->buffer_length,  				GFP_KERNEL);  		if (!as->urb->transfer_buffer) { -			kfree(isopkt); -			kfree(dr); -			free_async(as); -			return -ENOMEM; +			ret = -ENOMEM; +			goto error;  		}  		/* Isochronous input data may end up being discontiguous  		 * if some of the packets are short.  Clear the buffer so @@ -1253,6 +1306,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  	as->urb->transfer_buffer_length = uurb->buffer_length;  	as->urb->setup_packet = (unsigned char *)dr; +	dr = NULL;  	as->urb->start_frame = uurb->start_frame;  	as->urb->number_of_packets = uurb->number_of_packets;  	if (uurb->type == USBDEVFS_URB_TYPE_ISO || @@ -1268,6 +1322,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  		totlen += isopkt[u].length;  	}  	kfree(isopkt); +	isopkt = NULL;  	as->ps = ps;  	as->userurb = arg;  	if (is_in && uurb->buffer_length > 0) @@ -1282,8 +1337,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  	if (!is_in && uurb->buffer_length > 0) {  		if (copy_from_user(as->urb->transfer_buffer, uurb->buffer,  				uurb->buffer_length)) { -			free_async(as); -			return -EFAULT; +			ret = -EFAULT; +			goto error;  		}  	}  	snoop_urb(ps->dev, as->userurb, as->urb->pipe, @@ -1329,10 +1384,16 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,  		snoop_urb(ps->dev, as->userurb, as->urb->pipe,  				0, ret, COMPLETE, NULL, 0);  		async_removepending(as); -		free_async(as); -		return ret; +		goto error;  	}  	return 0; + + error: +	kfree(isopkt); +	kfree(dr); +	if (as) +		free_async(as); +	return ret;  }  static int proc_submiturb(struct dev_state *ps, void __user *arg)  |