From: Ana Bobillo <anabobillo3@gmail.com>
To: Marek Vasut <marek.vasut+usb@mailbox.org>
Cc: u-boot@lists.u-boot-project.org, Tom Rini <trini@konsulko.com>,
Simon Glass <sjg@chromium.org>
Subject: [PATCH 4/6] usb: keep interface and endpoint counts within what was parsed
Date: Sat, 31 Oct 2026 06:17:10 +0000 [thread overview]
Message-ID: <88b3752e-2712-4e1d-9a31-b95c7dfdb699@gmail.com> (raw)
In-Reply-To: <57123e9b-629d-49d3-b500-095503129ed2@gmail.com>
From: Ana Bobillo <anabobillo3@gmail.com>
usb_parse_config() stores at most USB_MAXINTERFACES interfaces, with
the endpoint descriptors it finds for each, but leaves bNumInterfaces
and each interface's bNumEndpoints as the device sent them. These counts
are what usb_set_maxpacket(), usb_set_interface() and the class drivers
(usb_storage, usb_kbd, usb_ether) walk if_desc[] and ep_desc[] by. A
device whose counts claim more than it describes, or more than there is
room for, makes them read past what was stored, and past the end of
those arrays.
Lower the counts to what was stored once the configuration is parsed.
For a device with more interfaces or endpoints than there is room for,
keep the ones that fit, as the comment there already intends, rather
than return an error that nothing checks: stop at the first interface
too many, and skip each endpoint too many. Don't take the companion
descriptor of a skipped endpoint for that of the endpoint before it.
Add tests for both cases.
Signed-off-by: Ana Bobillo <anabobillo3@gmail.com>
---
common/usb.c | 24 ++++++--
test/dm/usb.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 174 insertions(+), 4 deletions(-)
diff --git a/common/usb.c b/common/usb.c
index 6a4ad346f4b..05502da6b30 100644
--- a/common/usb.c
+++ b/common/usb.c
@@ -385,6 +385,7 @@ static int usb_parse_config(struct usb_device *dev,
int index, ifno, epno, curr_if_num;
u16 ep_wMaxPacketSize;
struct usb_interface *if_desc = NULL;
+ int i;
ifno = -1;
epno = -1;
@@ -428,7 +429,7 @@ static int usb_parse_config(struct usb_device *dev,
if (ifno >= USB_MAXINTERFACES) {
puts("Too many USB interfaces!\n");
/* try to go on with what we have */
- return -EINVAL;
+ goto out;
}
if_desc = &dev->config.if_desc[ifno];
dev->config.no_of_if++;
@@ -447,6 +448,8 @@ static int usb_parse_config(struct usb_device *dev,
}
break;
case USB_DT_ENDPOINT:
+ /* no companion descriptor goes with an endpoint we skip */
+ epno = -1;
if (head->bLength != USB_DT_ENDPOINT_SIZE &&
head->bLength != USB_DT_ENDPOINT_AUDIO_SIZE) {
printf("ERROR: Invalid USB EP length (%d)\n",
@@ -462,14 +465,14 @@ static int usb_parse_config(struct usb_device *dev,
puts("Endpoint descriptor out of order!\n");
break;
}
- epno = dev->config.if_desc[ifno].no_of_ep;
if_desc = &dev->config.if_desc[ifno];
- if (epno >= USB_MAXENDPOINTS) {
+ if (if_desc->no_of_ep >= USB_MAXENDPOINTS) {
printf("Interface %d has too many endpoints!\n",
if_desc->desc.bInterfaceNumber);
- return -EINVAL;
+ break;
}
/* found an endpoint */
+ epno = if_desc->no_of_ep;
if_desc->no_of_ep++;
memcpy(&if_desc->ep_desc[epno], head,
USB_DT_ENDPOINT_SIZE);
@@ -525,6 +528,19 @@ static int usb_parse_config(struct usb_device *dev,
index += head->bLength;
head = (struct usb_descriptor_header *)&buffer[index];
}
+out:
+ /*
+ * Interfaces and endpoints are walked by the counts in their
+ * descriptors later on, so don't let these claim more than we stored.
+ */
+ if (dev->config.desc.bNumInterfaces > dev->config.no_of_if)
+ dev->config.desc.bNumInterfaces = dev->config.no_of_if;
+ for (i = 0; i < dev->config.no_of_if; i++) {
+ if_desc = &dev->config.if_desc[i];
+ if (if_desc->desc.bNumEndpoints > if_desc->no_of_ep)
+ if_desc->desc.bNumEndpoints = if_desc->no_of_ep;
+ }
+
return 0;
}
diff --git a/test/dm/usb.c b/test/dm/usb.c
index 7f803f9fe01..145ce79dfec 100644
--- a/test/dm/usb.c
+++ b/test/dm/usb.c
@@ -500,3 +500,157 @@ static int dm_test_usb_hub_ports(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_usb_hub_ports, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+/*
+ * Descriptors of a device with a vendor-specific interface, which is bound
+ * to the generic driver whatever its endpoints. The tests below give them,
+ * or copies with errors in them, to the emulator on port 1 in place of its
+ * own, to check how a configuration that a device gets wrong is parsed.
+ */
+static struct usb_device_descriptor vendor_device_desc = {
+ .bLength = sizeof(vendor_device_desc),
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = cpu_to_le16(0x0200),
+ .idVendor = cpu_to_le16(0x1234),
+ .idProduct = cpu_to_le16(0x5678),
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+};
+
+static struct usb_config_descriptor vendor_config0 = {
+ .bLength = sizeof(vendor_config0),
+ .bDescriptorType = USB_DT_CONFIG,
+ /* wTotalLength is set up by usb_emul_setup_device() */
+ .bNumInterfaces = 1,
+ .bmAttributes = 1 << 7,
+ .bMaxPower = 50,
+};
+
+static struct usb_interface_descriptor vendor_interface0 = {
+ .bLength = sizeof(vendor_interface0),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+};
+
+static struct usb_endpoint_descriptor vendor_endpoint1_in = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+/**
+ * scan_port1() - Scan the bus with a device on port 1 that has the given
+ * descriptors
+ *
+ * @uts: Test state
+ * @desc_list: Descriptors of the device, terminated by NULL
+ * @udevp: Returns the device found on port 1, or NULL if none was set up
+ * Return: 0 if OK, -ve on error
+ */
+static int scan_port1(struct unit_test_state *uts, void **desc_list,
+ struct usb_device **udevp)
+{
+ struct udevice *emul, *hub, *dev;
+
+ ut_assertok(uclass_find_device_by_name(UCLASS_USB_EMUL,
+ "flash-stick@0", &emul));
+ ut_assertok(usb_emul_setup_device(emul, NULL, desc_list));
+
+ state_set_skip_delays(true);
+ ut_assertok(usb_init());
+
+ *udevp = NULL;
+ ut_assertok(uclass_find_device_by_name(UCLASS_USB_HUB, "hub", &hub));
+ device_foreach_child(dev, hub) {
+ struct usb_device *udev = dev_get_parent_priv(dev);
+
+ if (device_active(dev) && udev->portnr == 1)
+ *udevp = udev;
+ }
+
+ return 0;
+}
+
+/* test that interface and endpoint counts are limited to those described */
+static int dm_test_usb_config_counts(struct unit_test_state *uts)
+{
+ static struct usb_config_descriptor config0;
+ static struct usb_interface_descriptor interface0;
+ static void *desc_list[] = {
+ &vendor_device_desc,
+ &config0,
+ &interface0,
+ &vendor_endpoint1_in,
+ NULL,
+ };
+ struct usb_device *udev;
+
+ config0 = vendor_config0;
+ config0.bNumInterfaces = USB_MAXINTERFACES + 1;
+ interface0 = vendor_interface0;
+ interface0.bNumEndpoints = USB_MAXENDPOINTS + 1;
+ ut_assertok(scan_port1(uts, desc_list, &udev));
+ ut_assertnonnull(udev);
+ ut_asserteq(1, udev->config.no_of_if);
+ ut_asserteq(1, udev->config.desc.bNumInterfaces);
+ ut_asserteq(1, udev->config.if_desc[0].no_of_ep);
+ ut_asserteq(1, udev->config.if_desc[0].desc.bNumEndpoints);
+ ut_assertok(usb_stop());
+
+ return 0;
+}
+DM_TEST(dm_test_usb_config_counts, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+/* test that a device with more interfaces and endpoints than we keep works */
+static int dm_test_usb_config_too_many(struct unit_test_state *uts)
+{
+ static struct usb_config_descriptor config0;
+ static struct usb_interface_descriptor interface[USB_MAXINTERFACES + 1];
+ static struct usb_endpoint_descriptor endpoint[USB_MAXENDPOINTS + 1];
+ static void *desc_list[3 + ARRAY_SIZE(interface) + ARRAY_SIZE(endpoint)];
+ struct usb_device *udev;
+ int i, n = 0;
+
+ config0 = vendor_config0;
+ config0.bNumInterfaces = ARRAY_SIZE(interface);
+ for (i = 0; i < ARRAY_SIZE(interface); i++) {
+ interface[i] = vendor_interface0;
+ interface[i].bInterfaceNumber = i;
+ interface[i].bNumEndpoints = 0;
+ }
+ desc_list[n++] = &vendor_device_desc;
+ desc_list[n++] = &config0;
+
+ /* The first interface has one endpoint too many */
+ interface[0].bNumEndpoints = ARRAY_SIZE(endpoint);
+ desc_list[n++] = &interface[0];
+ for (i = 0; i < ARRAY_SIZE(endpoint); i++) {
+ endpoint[i] = vendor_endpoint1_in;
+ endpoint[i].bEndpointAddress = 1 + i % 15;
+ if (i < 15)
+ endpoint[i].bEndpointAddress |= USB_DIR_IN;
+ desc_list[n++] = &endpoint[i];
+ }
+
+ /* and there is one interface too many */
+ for (i = 1; i < ARRAY_SIZE(interface); i++)
+ desc_list[n++] = &interface[i];
+ desc_list[n] = NULL;
+
+ ut_assertok(scan_port1(uts, desc_list, &udev));
+ ut_assertnonnull(udev);
+ ut_asserteq(USB_MAXINTERFACES, udev->config.no_of_if);
+ ut_asserteq(USB_MAXINTERFACES, udev->config.desc.bNumInterfaces);
+ for (i = 0; i < USB_MAXINTERFACES; i++)
+ ut_asserteq(i, udev->config.if_desc[i].desc.bInterfaceNumber);
+ ut_asserteq(USB_MAXENDPOINTS, udev->config.if_desc[0].no_of_ep);
+ ut_asserteq(USB_MAXENDPOINTS,
+ udev->config.if_desc[0].desc.bNumEndpoints);
+ ut_assertok(usb_stop());
+
+ return 0;
+}
+DM_TEST(dm_test_usb_config_too_many, UTF_SCAN_PDATA | UTF_SCAN_FDT);
next prev parent reply other threads:[~2026-10-31 6:17 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 6:16 [PATCH 0/6] usb: check hub port and descriptor counts during enumeration Ana Bobillo
2026-10-31 6:16 ` [PATCH 1/6] usb: raise USB_MAXCHILDREN to 31 Ana Bobillo
2026-10-31 6:16 ` [PATCH 2/6] usb: hub: don't use more ports than USB_MAXCHILDREN Ana Bobillo
2026-10-31 6:17 ` [PATCH 3/6] cmd: usb: don't look past the last port in usb tree Ana Bobillo
2026-10-31 6:17 ` Ana Bobillo [this message]
2026-10-31 6:17 ` [PATCH 5/6] usb: skip endpoint descriptors for endpoint zero Ana Bobillo
2026-10-31 6:17 ` [PATCH 6/6] usb: don't use a device whose configuration can't be parsed Ana Bobillo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=88b3752e-2712-4e1d-9a31-b95c7dfdb699@gmail.com \
--to=anabobillo3@gmail.com \
--cc=marek.vasut+usb@mailbox.org \
--cc=sjg@chromium.org \
--cc=trini@konsulko.com \
--cc=u-boot@lists.u-boot-project.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox