Skip to content

device: clamp EP0 OUT data copy to the control transfer buffer#3705

Merged
hathach merged 2 commits into
masterfrom
claude/usbd-ep0-out-overrun-clamp
Jun 16, 2026
Merged

device: clamp EP0 OUT data copy to the control transfer buffer#3705
hathach merged 2 commits into
masterfrom
claude/usbd-ep0-out-overrun-clamp

Conversation

@hathach

@hathach hathach commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Problem

usbd_control_xfer_cb() copies the bytes a DCD reports for an EP0 OUT data packet straight into the requester's buffer with no bound:

memcpy(ctrl_xfer->buffer, _ctrl_epbuf.buf, xferred_bytes);
...
ctrl_xfer->total_xferred += (uint16_t) xferred_bytes;
ctrl_xfer->buffer += xferred_bytes;

ctrl_xfer->buffer is the caller's buffer with capacity data_len = min(len, wLength). A non-compliant or hostile host that sends an OUT data packet larger than data_len (e.g. a 64-byte packet answering a SET_LINE_CODING whose buffer is 7 bytes) overflows that buffer and over-counts total_xferred. The per-packet length is bounded by the EP0 max packet size, so the bounce buffer (_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE) is safe — the overflow is the copy into the smaller requester buffer.

This lives in the shared control handler, so it affects every DCD (surfaced while code-reviewing the MUSB EP0 path in #3699).

Fix

Clamp xferred_bytes to the remaining buffer space (data_len - total_xferred) before the copy and accounting. No-op for conforming hosts (they never send more than wLength).

Verification

  • pre-commit run --all-files clean (format, codespell, Ceedling unit tests incl. test_usbd.c).
  • Builds device/cdc_msc for stm32f407disco.

🤖 Generated with Claude Code

usbd_control_xfer_cb() copied xferred_bytes from the EP0 bounce buffer
into the requester's buffer with no bound. A non-compliant host that
sends an OUT data packet larger than the control transfer's data_len
(= min(len, wLength), the buffer capacity) would overflow that buffer
and over-count total_xferred. Clamp xferred_bytes to the remaining
buffer space before the memcpy and accounting.

This is in the shared control handler, so it hardens every DCD. Found
while code-reviewing the MUSB EP0 path in #3699.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 15, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens TinyUSB’s shared device control transfer handler (usbd_control_xfer_cb) against EP0 OUT data-stage buffer overflows by bounding how many received bytes are copied into the requester’s buffer. This mitigates malformed/non-compliant host behavior (or buggy DCD reporting) that could otherwise cause memory corruption across all device controllers.

Changes:

  • Clamp xferred_bytes for EP0 OUT to the remaining requester buffer space (data_len - total_xferred) before copying and accounting.
  • Prevent out-of-bounds memcpy() into the caller-provided control transfer buffer when a host sends an oversized OUT packet.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Size Difference Report

Because TinyUSB code size varies by port and configuration, the metrics below represent the averaged totals across all example builds.

Note: If there is no change, only one value is shown.

Changes >1% in size

file .text .rodata .data .bss size % diff
midi_host.c 1341 ➙ 1339 (-2) 7 7 3635 ➙ 3538 (-97) 4979 ➙ 4880 (-99) -2.0%
TOTAL 1341 ➙ 1339 (-2) 7 7 3635 ➙ 3538 (-97) 4979 ➙ 4880 (-99) -2.0%
Changes <1% in size
file .text .rodata .data .bss size % diff
audio_device.c 2896 ➙ 2890 (-6) 0 1259 1625 ➙ 1623 (-2) 4515 ➙ 4508 (-7) -0.2%
cdc_device.c 1239 ➙ 1236 (-3) 16 1092 735 ➙ 728 (-7) 1972 ➙ 1963 (-9) -0.5%
cdc_host.c 6413 ➙ 6399 (-14) 487 15 994 ➙ 971 (-23) 7619 ➙ 7594 (-25) -0.3%
dcd_stm32_fsdev.c 2558 ➙ 2553 (-5) 0 0 291 2849 ➙ 2844 (-5) -0.2%
dfu_device.c 777 ➙ 776 (-1) 28 712 138 ➙ 136 (-2) 914 ➙ 912 (-2) -0.2%
hcd_stm32_fsdev.c 3257 ➙ 3248 (-9) 0 1 420 3678 ➙ 3670 (-8) -0.2%
hid_device.c 1125 ➙ 1123 (-2) 44 997 119 1244 ➙ 1242 (-2) -0.2%
hid_host.c 1241 0 0 1251 ➙ 1270 (+19) 2492 ➙ 2511 (+19) +0.8%
hub.c 1384 8 8 30 1418 ➙ 1419 (+1) +0.1%
midi2_device.c 2634 ➙ 2628 (-6) 52 1175 566 ➙ 561 (-5) 3222 ➙ 3211 (-11) -0.3%
midi_device.c 1151 ➙ 1149 (-2) 0 1007 624 ➙ 619 (-5) 1773 ➙ 1765 (-8) -0.5%
msc_device.c 2517 ➙ 2513 (-4) 108 2281 806 ➙ 804 (-2) 3323 ➙ 3318 (-5) -0.2%
msc_host.c 1636 ➙ 1633 (-3) 0 0 394 ➙ 395 (+1) 2030 ➙ 2028 (-2) -0.1%
mtp_device.c 1717 ➙ 1713 (-4) 22 743 588 ➙ 589 (+1) 2312 ➙ 2309 (-3) -0.1%
ncm_device.c 1757 ➙ 1754 (-3) 28 815 4354 ➙ 4393 (+39) 6124 ➙ 6160 (+36) +0.6%
printer_device.c 830 ➙ 828 (-2) 0 706 566 ➙ 560 (-6) 1394 ➙ 1387 (-7) -0.5%
tusb_fifo.c 853 ➙ 855 (+2) 0 486 0 848 ➙ 850 (+2) +0.2%
usbd.c 3519 ➙ 3530 (+11) 58 ➙ 57 (-1) 91 ➙ 90 (-1) 355 3936 ➙ 3948 (+12) +0.3%
usbh.c 4967 57 82 1161 ➙ 1165 (+4) 6233 ➙ 6238 (+5) +0.1%
usbtmc_device.c 2196 ➙ 2194 (-2) 24 68 316 ➙ 313 (-3) 2544 ➙ 2539 (-5) -0.2%
vendor_device.c 641 ➙ 639 (-2) 0 534 565 ➙ 559 (-6) 1204 ➙ 1197 (-7) -0.6%
video_device.c 4443 ➙ 4432 (-11) 5 1235 479 ➙ 480 (+1) 4914 ➙ 4904 (-10) -0.2%
TOTAL 49751 ➙ 49685 (-66) 937 ➙ 936 (-1) 13307 ➙ 13306 (-1) 16377 ➙ 16381 (+4) 66558 ➙ 66517 (-41) -0.1%
No changes
file .text .rodata .data .bss size % diff
dcd_ch32_usbfs.c 1582 0 0 2444 4026 +0.0%
dcd_ch32_usbhs.c 1892 0 0 481 2373 +0.0%
dcd_ci_fs.c 1924 0 0 1290 3214 +0.0%
dcd_ci_hs.c 1756 0 0 1344 2534 +0.0%
dcd_da146xx.c 3067 0 0 144 3211 +0.0%
dcd_dwc2.c 4223 19 0 265 4506 +0.0%
dcd_eptri.c 2273 0 0 259 2532 +0.0%
dcd_ft9xx.c 3284 0 0 172 3456 +0.0%
dcd_khci.c 1952 0 0 1290 3242 +0.0%
dcd_lpc17_40.c 1481 0 0 648 1805 +0.0%
dcd_lpc_ip3511.c 1463 0 0 264 1683 +0.0%
dcd_mm32f327x_otg.c 1477 0 0 1290 2767 +0.0%
dcd_msp430x5xx.c 1801 0 0 176 1977 +0.0%
dcd_musb.c 2225 0 0 171 2396 +0.0%
dcd_nrf5x.c 2939 0 0 292 3231 +0.0%
dcd_nuc120.c 1096 0 0 78 1174 +0.0%
dcd_nuc121.c 1170 0 0 101 1270 +0.0%
dcd_nuc505.c 0 0 1533 157 1690 +0.0%
dcd_rp2040.c 840 0 764 653 2257 +0.0%
dcd_rusb2.c 2918 0 0 156 3074 +0.0%
dcd_samd.c 1036 0 0 266 1302 +0.0%
dcd_samg.c 1322 0 0 72 1394 +0.0%
dfu_rt_device.c 157 0 134 0 157 +0.0%
dwc2_common.c 603 22 0 0 615 +0.0%
ecm_rndis_device.c 1059 0 1 2759 3818 +0.0%
ehci.c 2763 0 0 6274 7783 +0.0%
fsdev_common.c 180 0 0 0 180 +0.0%
hcd_ch32_usbfs.c 2491 0 0 502 2993 +0.0%
hcd_ci_hs.c 181 0 0 0 181 +0.0%
hcd_dwc2.c 5070 25 1 545 5640 +0.0%
hcd_khci.c 2443 0 0 454 2897 +0.0%
hcd_musb.c 3071 0 0 157 3228 +0.0%
hcd_pio_usb.c 262 0 240 0 502 +0.0%
hcd_rp2040.c 1996 17 4 321 2338 +0.0%
hcd_rusb2.c 2923 0 0 245 3168 +0.0%
hcd_samd.c 2220 0 0 324 2544 +0.0%
midi2_host.c 1802 0 0 5921 7723 +0.0%
ohci.c 1925 0 0 2503 4428 +0.0%
rp2040_usb.c 386 35 619 11 1051 +0.0%
rusb2_common.c 160 0 16 0 176 +0.0%
tusb.c 455 0 387 3 457 +0.0%
typec_stm32.c 820 8 2 12 842 +0.0%
usbc.c 420 2 20 166 608 +0.0%
TOTAL 73108 128 3721 32210 106443 +0.0%

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Hardware-in-the-loop (HIL) Test Report

hfp-iar

Board cdc_dual_ports dfu cdc_msc cdc_msc_throughput audio_test_freertos dfu_runtime cdc_msc_freertos hid_boot_interface msc_dual_lun hid_generic_inout printer_to_cdc midi_test mtp
stm32l412nucleo ✅ CDC 645k/391k MSC 810k/724k
stm32f746disco ✅ CDC 12.7M/7.1M MSC 18.2M/18.3M
stm32f746disco-DMA ✅ CDC 12.5M/10.6M MSC 15.6M/31.3M
lpcxpresso43s67 ✅ CDC 10.2M/10.7M MSC 20.3M/18.2M

Legend: ✅ pass · ❌ fail · ⚪ skipped · blank not run

hfp.json

Board cdc_dual_ports dfu cdc_msc cdc_msc_throughput audio_test_freertos dfu_runtime cdc_msc_freertos hid_boot_interface msc_dual_lun hid_generic_inout printer_to_cdc midi_test mtp
stm32l412nucleo ✅ CDC 628k/408k MSC 792k/740k
stm32f746disco ✅ CDC 12.5M/8.1M MSC 13.9M/29.4M
stm32f746disco-DMA ✅ CDC 14.5M/9.1M MSC 19.1M/30.4M
lpcxpresso43s67 ✅ CDC 11.6M/11.2M MSC 22.5M/24.7M

Legend: ✅ pass · ❌ fail · ⚪ skipped · blank not run

tinyusb.json

Board cdc_dual_ports dfu cdc_msc cdc_msc_throughput audio_test_freertos dfu_runtime cdc_msc_freertos hid_boot_interface msc_dual_lun hid_generic_inout printer_to_cdc midi_test mtp host_info_to_device_cdc cdc_msc_hid msc_file_explorer msc_file_explorer_freertos device_info hid_composite_freertos
ek_tm4c123gxl ✅ CDC 470k/674k MSC 771k/810k
espressif_p4_function_ev rd 409KB/s
espressif_p4_function_ev-DMA rd 409KB/s
espressif_s3_devkitm rd 409KB/s
espressif_s3_devkitm-DMA rd 409KB/s
feather_nrf52840_express ✅ CDC 413k/245k MSC 495k/476k
max32666fthr ✅ CDC 7.1M/14.3M MSC 5.3M/20.2M
metro_m4_express ✅ CDC 441k/249k MSC 493k/480k
mimxrt1015_evk ✅ CDC 7.4M/6.3M MSC 17.2M/14.1M
mimxrt1064_evk ✅ CDC 10.2M/7.8M MSC 20.2M/10.6M rd 1365KB/s
lpcxpresso11u37 ✅ CDC 358k/238k MSC 489k/473k
ra4m1_ek ✅ CDC 487k/448k MSC 492k/493k
raspberry_pi_pico ✅ CDC 495k/429k MSC 741k/969k
raspberry_pi_pico_w rd 1022KB/s
raspberry_pi_pico2 rd 1110KB/s rd 1022KB/s
adafruit_fruit_jam ✅ CDC 741k/424k MSC 505k/947k rd 62KB/s rd 62KB/s
stm32f072disco ✅ CDC 271k/305k MSC 479k/475k
stm32f407disco ✅ CDC 815k/557k MSC 648k/947k
stm32f723disco ✅ CDC 545k/693k MSC 791k/988k rd 17476KB/s rd 4096KB/s
stm32f723disco-DMA ✅ CDC 912k/742k MSC 1M/987k rd 19418KB/s rd 4096KB/s
stm32h743nucleo ✅ CDC 471k/363k MSC 499k/472k
stm32h743nucleo-DMA ✅ CDC 621k/593k MSC 646k/581k
stm32g0b1nucleo ✅ CDC 484k/453k MSC 496k/486k
stm32l476disco ✅ CDC 523k/517k MSC 572k/547k
stm32u083nucleo ✅ CDC 512k/500k MSC 688k/519k

Legend: ✅ pass · ❌ fail · ⚪ skipped · blank not run

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

Top 10 targets by memory change (%) (out of 2384 targets) View Project Dashboard →

target .text .rodata .data .bss total % diff
xmc4500_relax/hid_boot_interface 13,732 → 13,796 (+64) 27,516 → 27,644 (+128) +0.5%
xmc4500_relax/cdc_dual_ports 14,908 → 14,972 (+64) 29,868 → 29,996 (+128) +0.4%
sipeed_longan_nano/hid_multiple_interface 14,562 → 14,626 (+64) 15,868 → 15,932 (+64) +0.4%
sipeed_longan_nano/hid_boot_interface 14,626 → 14,690 (+64) 15,884 → 15,948 (+64) +0.4%
xmc4500_relax/video_capture 15,988 → 16,052 (+64) 32,056 → 32,184 (+128) +0.4%
xmc4500_relax/uac2_headset 16,344 → 16,408 (+64) 32,744 → 32,872 (+128) +0.4%
sipeed_longan_nano/hid_generic_inout 13,846 → 13,908 (+62) 466 → 462 (-4) 14,980 → 15,038 (+58) +0.4%
xmc4500_relax/uac2_speaker_fb 17,100 → 17,164 (+64) 34,256 → 34,384 (+128) +0.4%
sipeed_longan_nano/printer_to_cdc 16,224 → 16,288 (+64) 17,458 → 17,522 (+64) +0.4%
sipeed_longan_nano/webusb_serial 16,412 → 16,476 (+64) 17,950 → 18,014 (+64) +0.4%

Collapse the hand-rolled remain/if clamp in usbd_control_xfer_cb() into tu_min32(), matching the tu_min16 idiom data_stage_xact() already uses for the same "clamp to remaining EP0 capacity" computation, and shorten the comment to the non-obvious why. No behavior change.

Add test_usbd_control_out_overrun_clamp covering the host-overrun case: a vendor OUT request offering an 8-byte buffer while the DCD reports a full max-size packet must complete via the IN status stage instead of re-arming an OUT data packet. Also fix the stale TEST_SOURCE_FILE("usbd_control.c") reference (merged into usbd.c) to keep the control path compiled from source.

ceedling test:all (61 passed); cdc_msc builds for stm32f407disco.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hathach hathach merged commit 7b79191 into master Jun 16, 2026
328 of 329 checks passed
@hathach hathach deleted the claude/usbd-ep0-out-overrun-clamp branch June 16, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants