mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-23 18:17:49 +07:00
a03ff54460
The syzkaller USB fuzzer found a slab-out-of-bounds write bug in the USB core, caused by a failure to check the actual size of a BOS descriptor. This patch adds a check to make sure the descriptor is at least as large as it is supposed to be, so that the code doesn't inadvertently access memory beyond the end of the allocated region when assigning to dev->bos->desc->bNumDeviceCaps later on. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Reported-and-tested-by: syzbot+71f1e64501a309fcc012@syzkaller.appspotmail.com CC: <stable@vger.kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1030 lines
29 KiB
C
1030 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Released under the GPLv2 only.
|
|
*/
|
|
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/hcd.h>
|
|
#include <linux/usb/quirks.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <asm/byteorder.h>
|
|
#include "usb.h"
|
|
|
|
|
|
#define USB_MAXALTSETTING 128 /* Hard limit */
|
|
|
|
#define USB_MAXCONFIG 8 /* Arbitrary limit */
|
|
|
|
|
|
static inline const char *plural(int n)
|
|
{
|
|
return (n == 1 ? "" : "s");
|
|
}
|
|
|
|
static int find_next_descriptor(unsigned char *buffer, int size,
|
|
int dt1, int dt2, int *num_skipped)
|
|
{
|
|
struct usb_descriptor_header *h;
|
|
int n = 0;
|
|
unsigned char *buffer0 = buffer;
|
|
|
|
/* Find the next descriptor of type dt1 or dt2 */
|
|
while (size > 0) {
|
|
h = (struct usb_descriptor_header *) buffer;
|
|
if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2)
|
|
break;
|
|
buffer += h->bLength;
|
|
size -= h->bLength;
|
|
++n;
|
|
}
|
|
|
|
/* Store the number of descriptors skipped and return the
|
|
* number of bytes skipped */
|
|
if (num_skipped)
|
|
*num_skipped = n;
|
|
return buffer - buffer0;
|
|
}
|
|
|
|
static void usb_parse_ssp_isoc_endpoint_companion(struct device *ddev,
|
|
int cfgno, int inum, int asnum, struct usb_host_endpoint *ep,
|
|
unsigned char *buffer, int size)
|
|
{
|
|
struct usb_ssp_isoc_ep_comp_descriptor *desc;
|
|
|
|
/*
|
|
* The SuperSpeedPlus Isoc endpoint companion descriptor immediately
|
|
* follows the SuperSpeed Endpoint Companion descriptor
|
|
*/
|
|
desc = (struct usb_ssp_isoc_ep_comp_descriptor *) buffer;
|
|
if (desc->bDescriptorType != USB_DT_SSP_ISOC_ENDPOINT_COMP ||
|
|
size < USB_DT_SSP_ISOC_EP_COMP_SIZE) {
|
|
dev_warn(ddev, "Invalid SuperSpeedPlus isoc endpoint companion"
|
|
"for config %d interface %d altsetting %d ep %d.\n",
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
return;
|
|
}
|
|
memcpy(&ep->ssp_isoc_ep_comp, desc, USB_DT_SSP_ISOC_EP_COMP_SIZE);
|
|
}
|
|
|
|
static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno,
|
|
int inum, int asnum, struct usb_host_endpoint *ep,
|
|
unsigned char *buffer, int size)
|
|
{
|
|
struct usb_ss_ep_comp_descriptor *desc;
|
|
int max_tx;
|
|
|
|
/* The SuperSpeed endpoint companion descriptor is supposed to
|
|
* be the first thing immediately following the endpoint descriptor.
|
|
*/
|
|
desc = (struct usb_ss_ep_comp_descriptor *) buffer;
|
|
|
|
if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP ||
|
|
size < USB_DT_SS_EP_COMP_SIZE) {
|
|
dev_warn(ddev, "No SuperSpeed endpoint companion for config %d "
|
|
" interface %d altsetting %d ep %d: "
|
|
"using minimum values\n",
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
|
|
/* Fill in some default values.
|
|
* Leave bmAttributes as zero, which will mean no streams for
|
|
* bulk, and isoc won't support multiple bursts of packets.
|
|
* With bursts of only one packet, and a Mult of 1, the max
|
|
* amount of data moved per endpoint service interval is one
|
|
* packet.
|
|
*/
|
|
ep->ss_ep_comp.bLength = USB_DT_SS_EP_COMP_SIZE;
|
|
ep->ss_ep_comp.bDescriptorType = USB_DT_SS_ENDPOINT_COMP;
|
|
if (usb_endpoint_xfer_isoc(&ep->desc) ||
|
|
usb_endpoint_xfer_int(&ep->desc))
|
|
ep->ss_ep_comp.wBytesPerInterval =
|
|
ep->desc.wMaxPacketSize;
|
|
return;
|
|
}
|
|
buffer += desc->bLength;
|
|
size -= desc->bLength;
|
|
memcpy(&ep->ss_ep_comp, desc, USB_DT_SS_EP_COMP_SIZE);
|
|
|
|
/* Check the various values */
|
|
if (usb_endpoint_xfer_control(&ep->desc) && desc->bMaxBurst != 0) {
|
|
dev_warn(ddev, "Control endpoint with bMaxBurst = %d in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to zero\n", desc->bMaxBurst,
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
ep->ss_ep_comp.bMaxBurst = 0;
|
|
} else if (desc->bMaxBurst > 15) {
|
|
dev_warn(ddev, "Endpoint with bMaxBurst = %d in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to 15\n", desc->bMaxBurst,
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
ep->ss_ep_comp.bMaxBurst = 15;
|
|
}
|
|
|
|
if ((usb_endpoint_xfer_control(&ep->desc) ||
|
|
usb_endpoint_xfer_int(&ep->desc)) &&
|
|
desc->bmAttributes != 0) {
|
|
dev_warn(ddev, "%s endpoint with bmAttributes = %d in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to zero\n",
|
|
usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk",
|
|
desc->bmAttributes,
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
ep->ss_ep_comp.bmAttributes = 0;
|
|
} else if (usb_endpoint_xfer_bulk(&ep->desc) &&
|
|
desc->bmAttributes > 16) {
|
|
dev_warn(ddev, "Bulk endpoint with more than 65536 streams in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to max\n",
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
ep->ss_ep_comp.bmAttributes = 16;
|
|
} else if (usb_endpoint_xfer_isoc(&ep->desc) &&
|
|
!USB_SS_SSP_ISOC_COMP(desc->bmAttributes) &&
|
|
USB_SS_MULT(desc->bmAttributes) > 3) {
|
|
dev_warn(ddev, "Isoc endpoint has Mult of %d in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to 3\n",
|
|
USB_SS_MULT(desc->bmAttributes),
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
|
ep->ss_ep_comp.bmAttributes = 2;
|
|
}
|
|
|
|
if (usb_endpoint_xfer_isoc(&ep->desc))
|
|
max_tx = (desc->bMaxBurst + 1) *
|
|
(USB_SS_MULT(desc->bmAttributes)) *
|
|
usb_endpoint_maxp(&ep->desc);
|
|
else if (usb_endpoint_xfer_int(&ep->desc))
|
|
max_tx = usb_endpoint_maxp(&ep->desc) *
|
|
(desc->bMaxBurst + 1);
|
|
else
|
|
max_tx = 999999;
|
|
if (le16_to_cpu(desc->wBytesPerInterval) > max_tx) {
|
|
dev_warn(ddev, "%s endpoint with wBytesPerInterval of %d in "
|
|
"config %d interface %d altsetting %d ep %d: "
|
|
"setting to %d\n",
|
|
usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int",
|
|
le16_to_cpu(desc->wBytesPerInterval),
|
|
cfgno, inum, asnum, ep->desc.bEndpointAddress,
|
|
max_tx);
|
|
ep->ss_ep_comp.wBytesPerInterval = cpu_to_le16(max_tx);
|
|
}
|
|
/* Parse a possible SuperSpeedPlus isoc ep companion descriptor */
|
|
if (usb_endpoint_xfer_isoc(&ep->desc) &&
|
|
USB_SS_SSP_ISOC_COMP(desc->bmAttributes))
|
|
usb_parse_ssp_isoc_endpoint_companion(ddev, cfgno, inum, asnum,
|
|
ep, buffer, size);
|
|
}
|
|
|
|
static const unsigned short low_speed_maxpacket_maxes[4] = {
|
|
[USB_ENDPOINT_XFER_CONTROL] = 8,
|
|
[USB_ENDPOINT_XFER_ISOC] = 0,
|
|
[USB_ENDPOINT_XFER_BULK] = 0,
|
|
[USB_ENDPOINT_XFER_INT] = 8,
|
|
};
|
|
static const unsigned short full_speed_maxpacket_maxes[4] = {
|
|
[USB_ENDPOINT_XFER_CONTROL] = 64,
|
|
[USB_ENDPOINT_XFER_ISOC] = 1023,
|
|
[USB_ENDPOINT_XFER_BULK] = 64,
|
|
[USB_ENDPOINT_XFER_INT] = 64,
|
|
};
|
|
static const unsigned short high_speed_maxpacket_maxes[4] = {
|
|
[USB_ENDPOINT_XFER_CONTROL] = 64,
|
|
[USB_ENDPOINT_XFER_ISOC] = 1024,
|
|
|
|
/* Bulk should be 512, but some devices use 1024: we will warn below */
|
|
[USB_ENDPOINT_XFER_BULK] = 1024,
|
|
[USB_ENDPOINT_XFER_INT] = 1024,
|
|
};
|
|
static const unsigned short super_speed_maxpacket_maxes[4] = {
|
|
[USB_ENDPOINT_XFER_CONTROL] = 512,
|
|
[USB_ENDPOINT_XFER_ISOC] = 1024,
|
|
[USB_ENDPOINT_XFER_BULK] = 1024,
|
|
[USB_ENDPOINT_XFER_INT] = 1024,
|
|
};
|
|
|
|
static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
|
|
int asnum, struct usb_host_interface *ifp, int num_ep,
|
|
unsigned char *buffer, int size)
|
|
{
|
|
unsigned char *buffer0 = buffer;
|
|
struct usb_endpoint_descriptor *d;
|
|
struct usb_host_endpoint *endpoint;
|
|
int n, i, j, retval;
|
|
unsigned int maxp;
|
|
const unsigned short *maxpacket_maxes;
|
|
|
|
d = (struct usb_endpoint_descriptor *) buffer;
|
|
buffer += d->bLength;
|
|
size -= d->bLength;
|
|
|
|
if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)
|
|
n = USB_DT_ENDPOINT_AUDIO_SIZE;
|
|
else if (d->bLength >= USB_DT_ENDPOINT_SIZE)
|
|
n = USB_DT_ENDPOINT_SIZE;
|
|
else {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d has an "
|
|
"invalid endpoint descriptor of length %d, skipping\n",
|
|
cfgno, inum, asnum, d->bLength);
|
|
goto skip_to_next_endpoint_or_interface_descriptor;
|
|
}
|
|
|
|
i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK;
|
|
if (i >= 16 || i == 0) {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d has an "
|
|
"invalid endpoint with address 0x%X, skipping\n",
|
|
cfgno, inum, asnum, d->bEndpointAddress);
|
|
goto skip_to_next_endpoint_or_interface_descriptor;
|
|
}
|
|
|
|
/* Only store as many endpoints as we have room for */
|
|
if (ifp->desc.bNumEndpoints >= num_ep)
|
|
goto skip_to_next_endpoint_or_interface_descriptor;
|
|
|
|
/* Check for duplicate endpoint addresses */
|
|
for (i = 0; i < ifp->desc.bNumEndpoints; ++i) {
|
|
if (ifp->endpoint[i].desc.bEndpointAddress ==
|
|
d->bEndpointAddress) {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d has a duplicate endpoint with address 0x%X, skipping\n",
|
|
cfgno, inum, asnum, d->bEndpointAddress);
|
|
goto skip_to_next_endpoint_or_interface_descriptor;
|
|
}
|
|
}
|
|
|
|
endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints];
|
|
++ifp->desc.bNumEndpoints;
|
|
|
|
memcpy(&endpoint->desc, d, n);
|
|
INIT_LIST_HEAD(&endpoint->urb_list);
|
|
|
|
/*
|
|
* Fix up bInterval values outside the legal range.
|
|
* Use 10 or 8 ms if no proper value can be guessed.
|
|
*/
|
|
i = 0; /* i = min, j = max, n = default */
|
|
j = 255;
|
|
if (usb_endpoint_xfer_int(d)) {
|
|
i = 1;
|
|
switch (to_usb_device(ddev)->speed) {
|
|
case USB_SPEED_SUPER_PLUS:
|
|
case USB_SPEED_SUPER:
|
|
case USB_SPEED_HIGH:
|
|
/*
|
|
* Many device manufacturers are using full-speed
|
|
* bInterval values in high-speed interrupt endpoint
|
|
* descriptors. Try to fix those and fall back to an
|
|
* 8-ms default value otherwise.
|
|
*/
|
|
n = fls(d->bInterval*8);
|
|
if (n == 0)
|
|
n = 7; /* 8 ms = 2^(7-1) uframes */
|
|
j = 16;
|
|
|
|
/*
|
|
* Adjust bInterval for quirked devices.
|
|
*/
|
|
/*
|
|
* This quirk fixes bIntervals reported in ms.
|
|
*/
|
|
if (to_usb_device(ddev)->quirks &
|
|
USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL) {
|
|
n = clamp(fls(d->bInterval) + 3, i, j);
|
|
i = j = n;
|
|
}
|
|
/*
|
|
* This quirk fixes bIntervals reported in
|
|
* linear microframes.
|
|
*/
|
|
if (to_usb_device(ddev)->quirks &
|
|
USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL) {
|
|
n = clamp(fls(d->bInterval), i, j);
|
|
i = j = n;
|
|
}
|
|
break;
|
|
default: /* USB_SPEED_FULL or _LOW */
|
|
/*
|
|
* For low-speed, 10 ms is the official minimum.
|
|
* But some "overclocked" devices might want faster
|
|
* polling so we'll allow it.
|
|
*/
|
|
n = 10;
|
|
break;
|
|
}
|
|
} else if (usb_endpoint_xfer_isoc(d)) {
|
|
i = 1;
|
|
j = 16;
|
|
switch (to_usb_device(ddev)->speed) {
|
|
case USB_SPEED_HIGH:
|
|
n = 7; /* 8 ms = 2^(7-1) uframes */
|
|
break;
|
|
default: /* USB_SPEED_FULL */
|
|
n = 4; /* 8 ms = 2^(4-1) frames */
|
|
break;
|
|
}
|
|
}
|
|
if (d->bInterval < i || d->bInterval > j) {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d "
|
|
"endpoint 0x%X has an invalid bInterval %d, "
|
|
"changing to %d\n",
|
|
cfgno, inum, asnum,
|
|
d->bEndpointAddress, d->bInterval, n);
|
|
endpoint->desc.bInterval = n;
|
|
}
|
|
|
|
/* Some buggy low-speed devices have Bulk endpoints, which is
|
|
* explicitly forbidden by the USB spec. In an attempt to make
|
|
* them usable, we will try treating them as Interrupt endpoints.
|
|
*/
|
|
if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&
|
|
usb_endpoint_xfer_bulk(d)) {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d "
|
|
"endpoint 0x%X is Bulk; changing to Interrupt\n",
|
|
cfgno, inum, asnum, d->bEndpointAddress);
|
|
endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;
|
|
endpoint->desc.bInterval = 1;
|
|
if (usb_endpoint_maxp(&endpoint->desc) > 8)
|
|
endpoint->desc.wMaxPacketSize = cpu_to_le16(8);
|
|
}
|
|
|
|
/* Validate the wMaxPacketSize field */
|
|
maxp = usb_endpoint_maxp(&endpoint->desc);
|
|
|
|
/* Find the highest legal maxpacket size for this endpoint */
|
|
i = 0; /* additional transactions per microframe */
|
|
switch (to_usb_device(ddev)->speed) {
|
|
case USB_SPEED_LOW:
|
|
maxpacket_maxes = low_speed_maxpacket_maxes;
|
|
break;
|
|
case USB_SPEED_FULL:
|
|
maxpacket_maxes = full_speed_maxpacket_maxes;
|
|
break;
|
|
case USB_SPEED_HIGH:
|
|
/* Bits 12..11 are allowed only for HS periodic endpoints */
|
|
if (usb_endpoint_xfer_int(d) || usb_endpoint_xfer_isoc(d)) {
|
|
i = maxp & (BIT(12) | BIT(11));
|
|
maxp &= ~i;
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
maxpacket_maxes = high_speed_maxpacket_maxes;
|
|
break;
|
|
case USB_SPEED_SUPER:
|
|
case USB_SPEED_SUPER_PLUS:
|
|
maxpacket_maxes = super_speed_maxpacket_maxes;
|
|
break;
|
|
}
|
|
j = maxpacket_maxes[usb_endpoint_type(&endpoint->desc)];
|
|
|
|
if (maxp > j) {
|
|
dev_warn(ddev, "config %d interface %d altsetting %d endpoint 0x%X has invalid maxpacket %d, setting to %d\n",
|
|
cfgno, inum, asnum, d->bEndpointAddress, maxp, j);
|
|
maxp = j;
|
|
endpoint->desc.wMaxPacketSize = cpu_to_le16(i | maxp);
|
|
}
|
|
|
|
/*
|
|
* Some buggy high speed devices have bulk endpoints using
|
|
* maxpacket sizes other than 512. High speed HCDs may not
|
|
* be able to handle that particular bug, so let's warn...
|
|
*/
|
|
if (to_usb_device(ddev)->speed == USB_SPEED_HIGH
|
|
&& usb_endpoint_xfer_bulk(d)) {
|
|
if (maxp != 512)
|
|
dev_warn(ddev, "config %d interface %d altsetting %d "
|
|
"bulk endpoint 0x%X has invalid maxpacket %d\n",
|
|
cfgno, inum, asnum, d->bEndpointAddress,
|
|
maxp);
|
|
}
|
|
|
|
/* Parse a possible SuperSpeed endpoint companion descriptor */
|
|
if (to_usb_device(ddev)->speed >= USB_SPEED_SUPER)
|
|
usb_parse_ss_endpoint_companion(ddev, cfgno,
|
|
inum, asnum, endpoint, buffer, size);
|
|
|
|
/* Skip over any Class Specific or Vendor Specific descriptors;
|
|
* find the next endpoint or interface descriptor */
|
|
endpoint->extra = buffer;
|
|
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
|
USB_DT_INTERFACE, &n);
|
|
endpoint->extralen = i;
|
|
retval = buffer - buffer0 + i;
|
|
if (n > 0)
|
|
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
|
n, plural(n), "endpoint");
|
|
return retval;
|
|
|
|
skip_to_next_endpoint_or_interface_descriptor:
|
|
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
|
USB_DT_INTERFACE, NULL);
|
|
return buffer - buffer0 + i;
|
|
}
|
|
|
|
void usb_release_interface_cache(struct kref *ref)
|
|
{
|
|
struct usb_interface_cache *intfc = ref_to_usb_interface_cache(ref);
|
|
int j;
|
|
|
|
for (j = 0; j < intfc->num_altsetting; j++) {
|
|
struct usb_host_interface *alt = &intfc->altsetting[j];
|
|
|
|
kfree(alt->endpoint);
|
|
kfree(alt->string);
|
|
}
|
|
kfree(intfc);
|
|
}
|
|
|
|
static int usb_parse_interface(struct device *ddev, int cfgno,
|
|
struct usb_host_config *config, unsigned char *buffer, int size,
|
|
u8 inums[], u8 nalts[])
|
|
{
|
|
unsigned char *buffer0 = buffer;
|
|
struct usb_interface_descriptor *d;
|
|
int inum, asnum;
|
|
struct usb_interface_cache *intfc;
|
|
struct usb_host_interface *alt;
|
|
int i, n;
|
|
int len, retval;
|
|
int num_ep, num_ep_orig;
|
|
|
|
d = (struct usb_interface_descriptor *) buffer;
|
|
buffer += d->bLength;
|
|
size -= d->bLength;
|
|
|
|
if (d->bLength < USB_DT_INTERFACE_SIZE)
|
|
goto skip_to_next_interface_descriptor;
|
|
|
|
/* Which interface entry is this? */
|
|
intfc = NULL;
|
|
inum = d->bInterfaceNumber;
|
|
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
|
|
if (inums[i] == inum) {
|
|
intfc = config->intf_cache[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!intfc || intfc->num_altsetting >= nalts[i])
|
|
goto skip_to_next_interface_descriptor;
|
|
|
|
/* Check for duplicate altsetting entries */
|
|
asnum = d->bAlternateSetting;
|
|
for ((i = 0, alt = &intfc->altsetting[0]);
|
|
i < intfc->num_altsetting;
|
|
(++i, ++alt)) {
|
|
if (alt->desc.bAlternateSetting == asnum) {
|
|
dev_warn(ddev, "Duplicate descriptor for config %d "
|
|
"interface %d altsetting %d, skipping\n",
|
|
cfgno, inum, asnum);
|
|
goto skip_to_next_interface_descriptor;
|
|
}
|
|
}
|
|
|
|
++intfc->num_altsetting;
|
|
memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE);
|
|
|
|
/* Skip over any Class Specific or Vendor Specific descriptors;
|
|
* find the first endpoint or interface descriptor */
|
|
alt->extra = buffer;
|
|
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
|
|
USB_DT_INTERFACE, &n);
|
|
alt->extralen = i;
|
|
if (n > 0)
|
|
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
|
n, plural(n), "interface");
|
|
buffer += i;
|
|
size -= i;
|
|
|
|
/* Allocate space for the right(?) number of endpoints */
|
|
num_ep = num_ep_orig = alt->desc.bNumEndpoints;
|
|
alt->desc.bNumEndpoints = 0; /* Use as a counter */
|
|
if (num_ep > USB_MAXENDPOINTS) {
|
|
dev_warn(ddev, "too many endpoints for config %d interface %d "
|
|
"altsetting %d: %d, using maximum allowed: %d\n",
|
|
cfgno, inum, asnum, num_ep, USB_MAXENDPOINTS);
|
|
num_ep = USB_MAXENDPOINTS;
|
|
}
|
|
|
|
if (num_ep > 0) {
|
|
/* Can't allocate 0 bytes */
|
|
len = sizeof(struct usb_host_endpoint) * num_ep;
|
|
alt->endpoint = kzalloc(len, GFP_KERNEL);
|
|
if (!alt->endpoint)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Parse all the endpoint descriptors */
|
|
n = 0;
|
|
while (size > 0) {
|
|
if (((struct usb_descriptor_header *) buffer)->bDescriptorType
|
|
== USB_DT_INTERFACE)
|
|
break;
|
|
retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt,
|
|
num_ep, buffer, size);
|
|
if (retval < 0)
|
|
return retval;
|
|
++n;
|
|
|
|
buffer += retval;
|
|
size -= retval;
|
|
}
|
|
|
|
if (n != num_ep_orig)
|
|
dev_warn(ddev, "config %d interface %d altsetting %d has %d "
|
|
"endpoint descriptor%s, different from the interface "
|
|
"descriptor's value: %d\n",
|
|
cfgno, inum, asnum, n, plural(n), num_ep_orig);
|
|
return buffer - buffer0;
|
|
|
|
skip_to_next_interface_descriptor:
|
|
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
|
|
USB_DT_INTERFACE, NULL);
|
|
return buffer - buffer0 + i;
|
|
}
|
|
|
|
static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
|
|
struct usb_host_config *config, unsigned char *buffer, int size)
|
|
{
|
|
struct device *ddev = &dev->dev;
|
|
unsigned char *buffer0 = buffer;
|
|
int cfgno;
|
|
int nintf, nintf_orig;
|
|
int i, j, n;
|
|
struct usb_interface_cache *intfc;
|
|
unsigned char *buffer2;
|
|
int size2;
|
|
struct usb_descriptor_header *header;
|
|
int retval;
|
|
u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
|
|
unsigned iad_num = 0;
|
|
|
|
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
|
|
nintf = nintf_orig = config->desc.bNumInterfaces;
|
|
config->desc.bNumInterfaces = 0; // Adjusted later
|
|
|
|
if (config->desc.bDescriptorType != USB_DT_CONFIG ||
|
|
config->desc.bLength < USB_DT_CONFIG_SIZE ||
|
|
config->desc.bLength > size) {
|
|
dev_err(ddev, "invalid descriptor for config index %d: "
|
|
"type = 0x%X, length = %d\n", cfgidx,
|
|
config->desc.bDescriptorType, config->desc.bLength);
|
|
return -EINVAL;
|
|
}
|
|
cfgno = config->desc.bConfigurationValue;
|
|
|
|
buffer += config->desc.bLength;
|
|
size -= config->desc.bLength;
|
|
|
|
if (nintf > USB_MAXINTERFACES) {
|
|
dev_warn(ddev, "config %d has too many interfaces: %d, "
|
|
"using maximum allowed: %d\n",
|
|
cfgno, nintf, USB_MAXINTERFACES);
|
|
nintf = USB_MAXINTERFACES;
|
|
}
|
|
|
|
/* Go through the descriptors, checking their length and counting the
|
|
* number of altsettings for each interface */
|
|
n = 0;
|
|
for ((buffer2 = buffer, size2 = size);
|
|
size2 > 0;
|
|
(buffer2 += header->bLength, size2 -= header->bLength)) {
|
|
|
|
if (size2 < sizeof(struct usb_descriptor_header)) {
|
|
dev_warn(ddev, "config %d descriptor has %d excess "
|
|
"byte%s, ignoring\n",
|
|
cfgno, size2, plural(size2));
|
|
break;
|
|
}
|
|
|
|
header = (struct usb_descriptor_header *) buffer2;
|
|
if ((header->bLength > size2) || (header->bLength < 2)) {
|
|
dev_warn(ddev, "config %d has an invalid descriptor "
|
|
"of length %d, skipping remainder of the config\n",
|
|
cfgno, header->bLength);
|
|
break;
|
|
}
|
|
|
|
if (header->bDescriptorType == USB_DT_INTERFACE) {
|
|
struct usb_interface_descriptor *d;
|
|
int inum;
|
|
|
|
d = (struct usb_interface_descriptor *) header;
|
|
if (d->bLength < USB_DT_INTERFACE_SIZE) {
|
|
dev_warn(ddev, "config %d has an invalid "
|
|
"interface descriptor of length %d, "
|
|
"skipping\n", cfgno, d->bLength);
|
|
continue;
|
|
}
|
|
|
|
inum = d->bInterfaceNumber;
|
|
|
|
if ((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) &&
|
|
n >= nintf_orig) {
|
|
dev_warn(ddev, "config %d has more interface "
|
|
"descriptors, than it declares in "
|
|
"bNumInterfaces, ignoring interface "
|
|
"number: %d\n", cfgno, inum);
|
|
continue;
|
|
}
|
|
|
|
if (inum >= nintf_orig)
|
|
dev_warn(ddev, "config %d has an invalid "
|
|
"interface number: %d but max is %d\n",
|
|
cfgno, inum, nintf_orig - 1);
|
|
|
|
/* Have we already encountered this interface?
|
|
* Count its altsettings */
|
|
for (i = 0; i < n; ++i) {
|
|
if (inums[i] == inum)
|
|
break;
|
|
}
|
|
if (i < n) {
|
|
if (nalts[i] < 255)
|
|
++nalts[i];
|
|
} else if (n < USB_MAXINTERFACES) {
|
|
inums[n] = inum;
|
|
nalts[n] = 1;
|
|
++n;
|
|
}
|
|
|
|
} else if (header->bDescriptorType ==
|
|
USB_DT_INTERFACE_ASSOCIATION) {
|
|
struct usb_interface_assoc_descriptor *d;
|
|
|
|
d = (struct usb_interface_assoc_descriptor *)header;
|
|
if (d->bLength < USB_DT_INTERFACE_ASSOCIATION_SIZE) {
|
|
dev_warn(ddev,
|
|
"config %d has an invalid interface association descriptor of length %d, skipping\n",
|
|
cfgno, d->bLength);
|
|
continue;
|
|
}
|
|
|
|
if (iad_num == USB_MAXIADS) {
|
|
dev_warn(ddev, "found more Interface "
|
|
"Association Descriptors "
|
|
"than allocated for in "
|
|
"configuration %d\n", cfgno);
|
|
} else {
|
|
config->intf_assoc[iad_num] = d;
|
|
iad_num++;
|
|
}
|
|
|
|
} else if (header->bDescriptorType == USB_DT_DEVICE ||
|
|
header->bDescriptorType == USB_DT_CONFIG)
|
|
dev_warn(ddev, "config %d contains an unexpected "
|
|
"descriptor of type 0x%X, skipping\n",
|
|
cfgno, header->bDescriptorType);
|
|
|
|
} /* for ((buffer2 = buffer, size2 = size); ...) */
|
|
size = buffer2 - buffer;
|
|
config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);
|
|
|
|
if (n != nintf)
|
|
dev_warn(ddev, "config %d has %d interface%s, different from "
|
|
"the descriptor's value: %d\n",
|
|
cfgno, n, plural(n), nintf_orig);
|
|
else if (n == 0)
|
|
dev_warn(ddev, "config %d has no interfaces?\n", cfgno);
|
|
config->desc.bNumInterfaces = nintf = n;
|
|
|
|
/* Check for missing interface numbers */
|
|
for (i = 0; i < nintf; ++i) {
|
|
for (j = 0; j < nintf; ++j) {
|
|
if (inums[j] == i)
|
|
break;
|
|
}
|
|
if (j >= nintf)
|
|
dev_warn(ddev, "config %d has no interface number "
|
|
"%d\n", cfgno, i);
|
|
}
|
|
|
|
/* Allocate the usb_interface_caches and altsetting arrays */
|
|
for (i = 0; i < nintf; ++i) {
|
|
j = nalts[i];
|
|
if (j > USB_MAXALTSETTING) {
|
|
dev_warn(ddev, "too many alternate settings for "
|
|
"config %d interface %d: %d, "
|
|
"using maximum allowed: %d\n",
|
|
cfgno, inums[i], j, USB_MAXALTSETTING);
|
|
nalts[i] = j = USB_MAXALTSETTING;
|
|
}
|
|
|
|
intfc = kzalloc(struct_size(intfc, altsetting, j), GFP_KERNEL);
|
|
config->intf_cache[i] = intfc;
|
|
if (!intfc)
|
|
return -ENOMEM;
|
|
kref_init(&intfc->ref);
|
|
}
|
|
|
|
/* FIXME: parse the BOS descriptor */
|
|
|
|
/* Skip over any Class Specific or Vendor Specific descriptors;
|
|
* find the first interface descriptor */
|
|
config->extra = buffer;
|
|
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
|
|
USB_DT_INTERFACE, &n);
|
|
config->extralen = i;
|
|
if (n > 0)
|
|
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
|
|
n, plural(n), "configuration");
|
|
buffer += i;
|
|
size -= i;
|
|
|
|
/* Parse all the interface/altsetting descriptors */
|
|
while (size > 0) {
|
|
retval = usb_parse_interface(ddev, cfgno, config,
|
|
buffer, size, inums, nalts);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
buffer += retval;
|
|
size -= retval;
|
|
}
|
|
|
|
/* Check for missing altsettings */
|
|
for (i = 0; i < nintf; ++i) {
|
|
intfc = config->intf_cache[i];
|
|
for (j = 0; j < intfc->num_altsetting; ++j) {
|
|
for (n = 0; n < intfc->num_altsetting; ++n) {
|
|
if (intfc->altsetting[n].desc.
|
|
bAlternateSetting == j)
|
|
break;
|
|
}
|
|
if (n >= intfc->num_altsetting)
|
|
dev_warn(ddev, "config %d interface %d has no "
|
|
"altsetting %d\n", cfgno, inums[i], j);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* hub-only!! ... and only exported for reset/reinit path.
|
|
* otherwise used internally on disconnect/destroy path
|
|
*/
|
|
void usb_destroy_configuration(struct usb_device *dev)
|
|
{
|
|
int c, i;
|
|
|
|
if (!dev->config)
|
|
return;
|
|
|
|
if (dev->rawdescriptors) {
|
|
for (i = 0; i < dev->descriptor.bNumConfigurations; i++)
|
|
kfree(dev->rawdescriptors[i]);
|
|
|
|
kfree(dev->rawdescriptors);
|
|
dev->rawdescriptors = NULL;
|
|
}
|
|
|
|
for (c = 0; c < dev->descriptor.bNumConfigurations; c++) {
|
|
struct usb_host_config *cf = &dev->config[c];
|
|
|
|
kfree(cf->string);
|
|
for (i = 0; i < cf->desc.bNumInterfaces; i++) {
|
|
if (cf->intf_cache[i])
|
|
kref_put(&cf->intf_cache[i]->ref,
|
|
usb_release_interface_cache);
|
|
}
|
|
}
|
|
kfree(dev->config);
|
|
dev->config = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the USB config descriptors, cache and parse'em
|
|
*
|
|
* hub-only!! ... and only in reset path, or usb_new_device()
|
|
* (used by real hubs and virtual root hubs)
|
|
*/
|
|
int usb_get_configuration(struct usb_device *dev)
|
|
{
|
|
struct device *ddev = &dev->dev;
|
|
int ncfg = dev->descriptor.bNumConfigurations;
|
|
int result = -ENOMEM;
|
|
unsigned int cfgno, length;
|
|
unsigned char *bigbuffer;
|
|
struct usb_config_descriptor *desc;
|
|
|
|
if (ncfg > USB_MAXCONFIG) {
|
|
dev_warn(ddev, "too many configurations: %d, "
|
|
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
|
|
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
|
|
}
|
|
|
|
if (ncfg < 1) {
|
|
dev_err(ddev, "no configurations\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
length = ncfg * sizeof(struct usb_host_config);
|
|
dev->config = kzalloc(length, GFP_KERNEL);
|
|
if (!dev->config)
|
|
goto err2;
|
|
|
|
length = ncfg * sizeof(char *);
|
|
dev->rawdescriptors = kzalloc(length, GFP_KERNEL);
|
|
if (!dev->rawdescriptors)
|
|
goto err2;
|
|
|
|
desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
|
|
if (!desc)
|
|
goto err2;
|
|
|
|
for (cfgno = 0; cfgno < ncfg; cfgno++) {
|
|
/* We grab just the first descriptor so we know how long
|
|
* the whole configuration is */
|
|
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
|
|
desc, USB_DT_CONFIG_SIZE);
|
|
if (result < 0) {
|
|
dev_err(ddev, "unable to read config index %d "
|
|
"descriptor/%s: %d\n", cfgno, "start", result);
|
|
if (result != -EPIPE)
|
|
goto err;
|
|
dev_err(ddev, "chopping to %d config(s)\n", cfgno);
|
|
dev->descriptor.bNumConfigurations = cfgno;
|
|
break;
|
|
} else if (result < 4) {
|
|
dev_err(ddev, "config index %d descriptor too short "
|
|
"(expected %i, got %i)\n", cfgno,
|
|
USB_DT_CONFIG_SIZE, result);
|
|
result = -EINVAL;
|
|
goto err;
|
|
}
|
|
length = max((int) le16_to_cpu(desc->wTotalLength),
|
|
USB_DT_CONFIG_SIZE);
|
|
|
|
/* Now that we know the length, get the whole thing */
|
|
bigbuffer = kmalloc(length, GFP_KERNEL);
|
|
if (!bigbuffer) {
|
|
result = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
if (dev->quirks & USB_QUIRK_DELAY_INIT)
|
|
msleep(200);
|
|
|
|
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
|
|
bigbuffer, length);
|
|
if (result < 0) {
|
|
dev_err(ddev, "unable to read config index %d "
|
|
"descriptor/%s\n", cfgno, "all");
|
|
kfree(bigbuffer);
|
|
goto err;
|
|
}
|
|
if (result < length) {
|
|
dev_warn(ddev, "config index %d descriptor too short "
|
|
"(expected %i, got %i)\n", cfgno, length, result);
|
|
length = result;
|
|
}
|
|
|
|
dev->rawdescriptors[cfgno] = bigbuffer;
|
|
|
|
result = usb_parse_configuration(dev, cfgno,
|
|
&dev->config[cfgno], bigbuffer, length);
|
|
if (result < 0) {
|
|
++cfgno;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
err:
|
|
kfree(desc);
|
|
dev->descriptor.bNumConfigurations = cfgno;
|
|
err2:
|
|
if (result == -ENOMEM)
|
|
dev_err(ddev, "out of memory\n");
|
|
return result;
|
|
}
|
|
|
|
void usb_release_bos_descriptor(struct usb_device *dev)
|
|
{
|
|
if (dev->bos) {
|
|
kfree(dev->bos->desc);
|
|
kfree(dev->bos);
|
|
dev->bos = NULL;
|
|
}
|
|
}
|
|
|
|
static const __u8 bos_desc_len[256] = {
|
|
[USB_CAP_TYPE_WIRELESS_USB] = USB_DT_USB_WIRELESS_CAP_SIZE,
|
|
[USB_CAP_TYPE_EXT] = USB_DT_USB_EXT_CAP_SIZE,
|
|
[USB_SS_CAP_TYPE] = USB_DT_USB_SS_CAP_SIZE,
|
|
[USB_SSP_CAP_TYPE] = USB_DT_USB_SSP_CAP_SIZE(1),
|
|
[CONTAINER_ID_TYPE] = USB_DT_USB_SS_CONTN_ID_SIZE,
|
|
[USB_PTM_CAP_TYPE] = USB_DT_USB_PTM_ID_SIZE,
|
|
};
|
|
|
|
/* Get BOS descriptor set */
|
|
int usb_get_bos_descriptor(struct usb_device *dev)
|
|
{
|
|
struct device *ddev = &dev->dev;
|
|
struct usb_bos_descriptor *bos;
|
|
struct usb_dev_cap_header *cap;
|
|
struct usb_ssp_cap_descriptor *ssp_cap;
|
|
unsigned char *buffer;
|
|
int length, total_len, num, i, ssac;
|
|
__u8 cap_type;
|
|
int ret;
|
|
|
|
bos = kzalloc(sizeof(struct usb_bos_descriptor), GFP_KERNEL);
|
|
if (!bos)
|
|
return -ENOMEM;
|
|
|
|
/* Get BOS descriptor */
|
|
ret = usb_get_descriptor(dev, USB_DT_BOS, 0, bos, USB_DT_BOS_SIZE);
|
|
if (ret < USB_DT_BOS_SIZE || bos->bLength < USB_DT_BOS_SIZE) {
|
|
dev_err(ddev, "unable to get BOS descriptor or descriptor too short\n");
|
|
if (ret >= 0)
|
|
ret = -ENOMSG;
|
|
kfree(bos);
|
|
return ret;
|
|
}
|
|
|
|
length = bos->bLength;
|
|
total_len = le16_to_cpu(bos->wTotalLength);
|
|
num = bos->bNumDeviceCaps;
|
|
kfree(bos);
|
|
if (total_len < length)
|
|
return -EINVAL;
|
|
|
|
dev->bos = kzalloc(sizeof(struct usb_host_bos), GFP_KERNEL);
|
|
if (!dev->bos)
|
|
return -ENOMEM;
|
|
|
|
/* Now let's get the whole BOS descriptor set */
|
|
buffer = kzalloc(total_len, GFP_KERNEL);
|
|
if (!buffer) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
dev->bos->desc = (struct usb_bos_descriptor *)buffer;
|
|
|
|
ret = usb_get_descriptor(dev, USB_DT_BOS, 0, buffer, total_len);
|
|
if (ret < total_len) {
|
|
dev_err(ddev, "unable to get BOS descriptor set\n");
|
|
if (ret >= 0)
|
|
ret = -ENOMSG;
|
|
goto err;
|
|
}
|
|
total_len -= length;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
buffer += length;
|
|
cap = (struct usb_dev_cap_header *)buffer;
|
|
|
|
if (total_len < sizeof(*cap) || total_len < cap->bLength) {
|
|
dev->bos->desc->bNumDeviceCaps = i;
|
|
break;
|
|
}
|
|
cap_type = cap->bDevCapabilityType;
|
|
length = cap->bLength;
|
|
if (bos_desc_len[cap_type] && length < bos_desc_len[cap_type]) {
|
|
dev->bos->desc->bNumDeviceCaps = i;
|
|
break;
|
|
}
|
|
|
|
total_len -= length;
|
|
|
|
if (cap->bDescriptorType != USB_DT_DEVICE_CAPABILITY) {
|
|
dev_warn(ddev, "descriptor type invalid, skip\n");
|
|
continue;
|
|
}
|
|
|
|
switch (cap_type) {
|
|
case USB_CAP_TYPE_WIRELESS_USB:
|
|
/* Wireless USB cap descriptor is handled by wusb */
|
|
break;
|
|
case USB_CAP_TYPE_EXT:
|
|
dev->bos->ext_cap =
|
|
(struct usb_ext_cap_descriptor *)buffer;
|
|
break;
|
|
case USB_SS_CAP_TYPE:
|
|
dev->bos->ss_cap =
|
|
(struct usb_ss_cap_descriptor *)buffer;
|
|
break;
|
|
case USB_SSP_CAP_TYPE:
|
|
ssp_cap = (struct usb_ssp_cap_descriptor *)buffer;
|
|
ssac = (le32_to_cpu(ssp_cap->bmAttributes) &
|
|
USB_SSP_SUBLINK_SPEED_ATTRIBS);
|
|
if (length >= USB_DT_USB_SSP_CAP_SIZE(ssac))
|
|
dev->bos->ssp_cap = ssp_cap;
|
|
break;
|
|
case CONTAINER_ID_TYPE:
|
|
dev->bos->ss_id =
|
|
(struct usb_ss_container_id_descriptor *)buffer;
|
|
break;
|
|
case USB_PTM_CAP_TYPE:
|
|
dev->bos->ptm_cap =
|
|
(struct usb_ptm_cap_descriptor *)buffer;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
usb_release_bos_descriptor(dev);
|
|
return ret;
|
|
}
|