MAX3420 UDC: out-of-bounds read/write via unvalidated wIndex in USB SETUP packet

Pavitra Jha
2026-04-11T17:58:21Z

Hello,

I am reporting an out-of-bounds read and write in the MAX3420 USB
Device Controller driver. The issue arises from using a
host-controlled wIndex field from a USB SETUP packet as a direct index
into a fixed-size endpoint array without validating that the index is
within bounds.

The driver handles USB control requests originating from an external
USB host and therefore must treat all request fields as untrusted.

1. VERSION AND ENVIRONMENT

Research kernel: v7.0.0-rc7
Architecture: x86_64

Observed unpatched as of April 11, 2026

2. VULNERABILITY PATH

USB host (attacker-controlled SETUP packet)
->
MAX3420 hardware (SUDFIFO register)
->
max3420_handle_irqs()
->
max3420_handle_setup()
->
spi_rd_buf(..., MAX3420_REG_SUDFIFO, ...)
->
udc->setup = setup
udc->setup.wIndex = cpu_to_le16(setup.wIndex)
->
dispatch:
-> max3420_getstatus()
-> max3420_set_clear_feature()
->
wIndex masked and used as endpoint index
->
out-of-bounds access into udc->ep[] when the derived index exceeds the
array bounds

No validation of the derived index is observed along this path.

3. VULNERABILITY DESCRIPTION

The endpoint index is derived as:

id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;

USB_ENDPOINT_NUMBER_MASK is 0x0f, producing values in the range 0–15.

The backing storage is:

struct max3420_ep ep[MAX3420_MAX_EPS];

where:

MAX3420_MAX_EPS = 4

Valid indices are therefore 0–3. Values >= 4 will index beyond the
bounds of the array.

4. SOURCE CODE ANALYSIS

4.1 Untrusted input propagation

spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8);
udc->setup = setup;
udc->setup.wIndex = cpu_to_le16(setup.wIndex);

The SETUP packet contents are populated by the USB host and copied
into driver state without validation. The wIndex field is later used
in control flow and memory access decisions.

4.2 Direct indexing into fixed-size array

ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK];

and:

id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;
ep = &udc->ep[id];

There is no check ensuring the derived index is less than
MAX3420_MAX_EPS before dereferencing.

4.3 Affected call sites

The pattern appears in:

- max3420_getstatus()
- max3420_set_clear_feature()

Both paths derive an index from wIndex and use it to access udc->ep[].

4.4 Existing checks are not sufficient

if (udc->setup.wIndex & USB_DIR_IN)

This condition only evaluates the direction bit and does not constrain
the endpoint number. It does not prevent out-of-bounds indexing.

4.5 Invariant violation

Regardless of protocol expectations, memory safety requires that any
externally influenced index used for array access be validated against
the array bounds.

Here, the code assumes the masked value is valid without enforcing
that constraint, which can lead to out-of-bounds access if unexpected
values are received.

5. IMPACT

Out-of-bounds read:

In max3420_getstatus(), fields are read from ep. If the index is
out-of-bounds, this may result in reading adjacent memory.

Out-of-bounds write:

In max3420_set_clear_feature(), fields within ep are modified
(including locking and state flags). If ep is out-of-bounds, this may
result in writes to unrelated memory.

Memory corruption characteristics:

Because ep[] is embedded within struct max3420_udc as a fixed-size array:

- out-of-bounds indexing accesses memory beyond the array within the same struct
- this may overlap adjacent struct members depending on layout
- further out-of-bounds accesses may extend beyond the struct into
surrounding memory depending on allocation context

Trigger:

A malformed USB control request can supply an out-of-range wIndex
value, for example:

bmRequestType = 0x02 (TYPE_STANDARD | RECIP_ENDPOINT)
bRequest = SET_FEATURE
wValue = ENDPOINT_HALT
wIndex = 0x0007
wLength = 0

This produces an index of 7, which exceeds the valid range [0–3].

6. VALIDATION EXPECTATION

The driver processes externally supplied USB control requests and
should validate fields before using them in memory access operations.

Other UDC drivers typically constrain endpoint indices or validate
request parameters before indexing fixed-size arrays. The absence of
such validation here suggests a missing bounds check.

7. SUGGESTED FIX

Validate the derived endpoint index before accessing the array:

id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;

if (id >= MAX3420_MAX_EPS)
goto stall;

This check should be applied consistently in both:

- max3420_getstatus()
- max3420_set_clear_feature()

8. AFFECTED VERSIONS

Affected: all kernels including the MAX3420 UDC driver
Fixed in: not yet patched

9. REACHABILITY ANALYSIS (CODE-LEVEL)

The vulnerable access is reachable through the standard USB control
request handling path.

9.1 Interrupt entry point

USB SETUP packet handling is triggered from the IRQ path:

max3420_handle_irqs()

Relevant code:

if (epirq & SUDAVIRQ) {
spi_wr8(udc, MAX3420_REG_EPIRQ, SUDAVIRQ);
max3420_handle_setup(udc);
return true;
}

Receipt of a SETUP packet (SUDAVIRQ) directly invokes max3420_handle_setup().

9.2 SETUP packet ingestion from hardware

The SETUP packet is read directly from hardware:

spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8);

udc->setup = setup;
udc->setup.wValue = cpu_to_le16(setup.wValue);
udc->setup.wIndex = cpu_to_le16(setup.wIndex);
udc->setup.wLength = cpu_to_le16(setup.wLength);

No validation or bounds checking is performed on wIndex before storing it.

9.3 Dispatch into request handlers

Standard USB requests are dispatched as follows:

case USB_REQ_GET_STATUS:
return max3420_getstatus(udc);

case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
return max3420_set_clear_feature(udc);

This dispatch occurs without validating the endpoint index derived from wIndex.

9.4 Out-of-bounds access in max3420_getstatus()

ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK];

No bounds check is performed before dereferencing ep.

9.5 Out-of-bounds access in max3420_set_clear_feature()

id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;
ep = &udc->ep[id];

No validation ensures id < MAX3420_MAX_EPS.

9.6 Array bounds vs index range

Array definition:

struct max3420_ep ep[MAX3420_MAX_EPS];
#define MAX3420_MAX_EPS 4

Valid indices: 0–3. Index derivation produces values 0–15. Values >= 4
result in out-of-bounds access.

9.7 End-to-end reachability

USB host → SETUP packet
→ MAX3420_REG_SUDFIFO
→ spi_rd_buf(...)
→ udc->setup.wIndex (unvalidated)
→ max3420_getstatus() / max3420_set_clear_feature()
→ ep = &udc->ep[id]
→ out-of-bounds access when id >= 4

This path requires only a valid USB control request targeting an
endpoint and does not rely on undefined behavior or malformed packet
structure.

10. RUNTIME EVIDENCE (USERSPACE HARNESS)

Hardware emulation for the MAX3420 SPI controller is unavailable in
QEMU. A userspace harness was constructed that reconstructs the
vulnerable struct layout and control flow from max3420_udc.c verbatim,
feeding attacker-controlled SETUP packet fields directly into the
dispatch path. AddressSanitizer confirms the out-of-bounds write at
the exact offset predicted by the struct layout analysis.

Build:

gcc -fsanitize=address,undefined -g -O0 -o max3420_poc max3420_oob_harness.c

Trigger packet (wIndex=0x0007, derived id=7):

bmRequestType = 0x02
bRequest = SET_FEATURE
wValue = ENDPOINT_HALT
wIndex = 0x0007
wLength = 0x0000

ASan output:

max3420_oob_harness.c:154:26: runtime error: index 7 out of bounds
for type 'max3420_ep [4]'
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c78019e0680
WRITE of size 1 at 0x7c78019e0680 thread T0
#0 max3420_set_clear_feature max3420_oob_harness.c:163
#1 run_test max3420_oob_harness.c:223
#2 main max3420_oob_harness.c:282
0x7c78019e0680 is located 168 bytes after 280-byte region
SUMMARY: AddressSanitizer: heap-buffer-overflow in max3420_set_clear_feature

Struct layout confirms ep[4] overlaps irq_registered at offset 256.
ep[7] lands 168 bytes past the struct boundary. The harness source is
available on request.

Regards,
Pavitra Jha
jhapavitra98@gmail.com