[PATCH 2/3] i2c: usbio: reject bridges with undersized transfer buffers

From: HE WEI (ギカク)

Date: Sun Jul 26 2026 - 04:05:23 EST


usbio_i2c_read() and usbio_i2c_write() derive their per-transfer chunk
size from the bridge buffer sizes:

u16 rxchunk = i2c->rxbuf_len - I2C_RW_OVERHEAD;
u16 txchunk = i2c->txbuf_len - I2C_RW_OVERHEAD;

I2C_RW_OVERHEAD is sizeof(struct usbio_bulk_packet) +
sizeof(struct usbio_i2c_rw), i.e. a size_t of value 10, while the two
lengths are u16. The subtraction is therefore done in size_t and, for a
bridge reporting less than 10, wraps before being truncated back into a
u16. rxbuf_len = 8, a legal full speed bulk wMaxPacketSize, gives
rxchunk = 65534.

The chunk size decides whether a transfer has to be split:

if (msg->len > rxchunk) {
/* Need to split the input buffer */

The adapter quirks cap msg->len at 4096, so a wrapped chunk size makes
that condition permanently false, the splitting path becomes dead code,
and the single-shot path asks usbio_bulk_msg() for more than the bridge
buffer can hold.

The boundary value is worse. rxbuf_len of exactly 10 gives rxchunk 0,
and I2C_AQ_NO_ZERO_LEN guarantees msg->len is at least 1, so the split
loop is entered and never advances:

do {
if (msg->len - len < rxchunk)
rxchunk = msg->len - len;
ret = usbio_bulk_msg(...);
if (ret < 0)
return ret;
memcpy(&msg->buf[len], rbuf->data, rxchunk);
len += rxchunk;
} while (msg->len > len);

"msg->len - len < rxchunk" is 1 < 0, so rxchunk stays 0 and len never
grows. The only exit is an error return, so a device that keeps
answering keeps the loop running indefinitely and uninterruptibly, while
holding both usbio->bulk_mutex and the client mutex, which blocks every
other user of the bridge. txbuf_len is independent and can be left at a
normal value, so usbio_i2c_init() succeeds and the read is reached.

Check both lengths once at probe time.

This is a behaviour change: a bridge reporting a bulk wMaxPacketSize
below 11 no longer gets an I2C adapter at all. Such an adapter could
never have completed a transfer. usbio_i2c_xfer() runs usbio_i2c_init()
first and bails out on failure, and that needs obuf_len = 7 to fit in
txbuf_len - sizeof(struct usbio_bulk_packet), so anything below 12
already returned -EMSGSIZE for every single transfer. Failing to probe
is better than registering an adapter that cannot work. No supported
bridge is affected: they use 64, or 63 via USBIO_QUIRK_BULK_MAXP_63.

This additionally covers the case where usbio_get_txrxbuf_len() returns
without writing its output parameters because the bridge is already
gone; the lengths then stay 0 and the bridge is rejected instead of
being used.

Found by code review. The non-terminating loop was reproduced with a
userspace model of usbio_i2c_read()'s split path; it has not been
exercised on hardware or on dummy_hcd.

Fixes: 121a0f839dbb ("usb: misc: Add Intel USBIO bridge driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: HE WEI (ギカク) <skyexpoc@xxxxxxxxx>
---
--- a/drivers/i2c/busses/i2c-usbio.c
+++ b/drivers/i2c/busses/i2c-usbio.c
@@ -245,6 +245,20 @@

usbio_acpi_bind(i2c->adev, usbio_i2c_acpi_hids);
usbio_get_txrxbuf_len(i2c->adev, &i2c->txbuf_len, &i2c->rxbuf_len);
+
+ /*
+ * usbio_i2c_read() and usbio_i2c_write() compute their chunk size as
+ * "<buf>_len - I2C_RW_OVERHEAD". Those lengths are u16 and the
+ * overhead is a size_t, so a bridge reporting less than the overhead
+ * makes the subtraction wrap and the truncated u16 chunk size then
+ * defeats the splitting logic, while reporting exactly the overhead
+ * makes the chunk 0 and the split loop in usbio_i2c_read() never
+ * advance. Refuse to attach to such a bridge.
+ */
+ if (i2c->txbuf_len <= I2C_RW_OVERHEAD || i2c->rxbuf_len <= I2C_RW_OVERHEAD)
+ return dev_err_probe(dev, -EINVAL,
+ "Bridge buffers too small: tx %u rx %u\n",
+ i2c->txbuf_len, i2c->rxbuf_len);

i2c->rwbuf = devm_kzalloc(dev, max(i2c->txbuf_len, i2c->rxbuf_len), GFP_KERNEL);
if (!i2c->rwbuf)
--
2.51.0