Skip to content

media: uvcvideo: reject zero-length extension unit control queries - #153

Merged
chriskearney merged 1 commit into
omacom:integration/aurorafrom
briantoll:uvc-xu-zero-length
Oct 30, 2026
Merged

chriskearney merged 1 commit into
omacom:integration/aurorafrom
briantoll:uvc-xu-zero-length

Conversation

@briantoll

Copy link
Copy Markdown

uvcvideo: an extension unit control with length 0 oopses the kernel (Studio Display XDR camera)

A control query from user space on the Studio Display XDR's camera crashes the kernel, and the aftermath takes the Thunderbolt port with it: the crashed task keeps the camera's control mutex, the camera's video node can never be released, the next unplug blocks the USB hub worker, and the port does not enumerate anything until a reboot (which itself hangs in device_shutdown() and needs the power button). Any process in the video group can trigger it; no special privileges beyond the camera node.

Mechanism, from the commit message: selector 1 of the camera's vendor extension unit answers UVC_GET_LEN with 0 (seen with the device in its third USB configuration, the one macOS uses). uvc_xu_ctrl_query() accepts a value query with size 0, kmalloc(0) returns ZERO_SIZE_PTR, the USB core rejects the zero-length IN control transfer with -EPIPE, and uvc_query_ctrl() treats that as a STALL and reads data[0] from the bogus pointer.

The fix is two guards: value queries on a zero-length control return -ENODATA (UVC_GET_LEN and UVC_GET_INFO still work), and uvc_query_ctrl() never borrows a zero-length buffer for the error-code request. The same code is in mainline as of 7.3-rc; a submission to linux-media is planned with this as the basis.

Tested

MacBook Pro 14" M1 Max (j314c), Omarchy, Aurora release 20 (7.1.12), Studio Display XDR on an Apple Thunderbolt 5 cable:

  • The exact UVCIOC_CTRL_QUERY sequence that oopsed (GET_LEN, GET_INFO, then GET_CUR/MIN/MAX/RES/DEF on selector 1 of XU 4) now completes: selector 1 returns -ENODATA for every value request, the process exits, no kernel message.
  • The other extension-unit selectors are queried normally afterwards.
  • Display hotplug on all three Thunderbolt ports, 5K60 tiled mode, USB audio and the default MJPEG camera stream are unchanged.
  • Verified first as a swapped-in module on the release 9 kernel, then as part of a full rebuild (local release 10) through a reboot and replug.

Oops (before the fix)

usb 1-1.3: BOGUS control dir, pipe 80000380 doesn't match bRequestType a1
WARNING: drivers/usb/core/urb.c:411 at usb_submit_urb+0x560/0x660
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010
Internal error: Oops: 0000000096000004 [#1]  SMP
pc : uvc_query_ctrl+0xe8/0x28c [uvcvideo]
lr : uvc_query_ctrl+0x94/0x28c [uvcvideo]
Call trace:
 uvc_query_ctrl+0xe8/0x28c [uvcvideo] (P)
 uvc_xu_ctrl_query+0x20c/0x420 [uvcvideo]
 uvc_ioctl_default+0xe8/0x3e0 [uvcvideo]
 __video_do_ioctl+0x154/0x3e0 [videodev]
 video_usercopy+0x220/0x74c [videodev]
 video_ioctl2+0x18/0x74 [videodev]
 uvc_v4l2_unlocked_ioctl+0xd0/0x1e4 [uvcvideo]
 v4l2_ioctl+0x40/0x60 [videodev]
 __arm64_sys_ioctl+0x2ec/0xad4

Stuck hub worker after the unplug:

vb2_video_unregister_device+0x54/0xa8 [videobuf2_v4l2]
uvc_unregister_video+0x50/0xe0 [uvcvideo]
uvc_disconnect+0x30/0xb4 [uvcvideo]
usb_unbind_interface+0x70/0x2ac
...
hub_event+0xc44/0x1a30

🤖 Generated with Claude Code

https://claude.ai/code/session_01QhYiD43tUYf8SqrkTnG5LQ

An extension unit control may answer UVC_GET_LEN with 0. The camera of
the Apple Studio Display XDR (05ac:1116, bcdDevice 18.01) does so for
selector 1 of its vendor extension unit when the device is switched to
its third USB configuration.

uvc_ctrl_fill_xu_info() stores the zero as ctrl->info.size, and
uvc_xu_ctrl_query() then accepts a UVCIOC_CTRL_QUERY for UVC_GET_CUR,
UVC_GET_MIN, UVC_GET_MAX, UVC_GET_RES, UVC_GET_DEF or UVC_SET_CUR with
xqry->size == 0: kmalloc(0) returns ZERO_SIZE_PTR and uvc_query_ctrl()
is called with a zero-length buffer. usb_submit_urb() rejects the
zero-length IN control transfer ("BOGUS control dir, pipe 80000380
doesn't match bRequestType a1") with -EPIPE, which uvc_query_ctrl()
takes for a protocol STALL, and it dereferences the buffer to request
the error code:

  Unable to handle kernel NULL pointer dereference at virtual address 0x10
  pc : uvc_query_ctrl+0xe8/0x28c [uvcvideo]
  lr : uvc_query_ctrl+0x94/0x28c [uvcvideo]
  Call trace:
   uvc_query_ctrl+0xe8/0x28c [uvcvideo] (P)
   uvc_xu_ctrl_query+0x20c/0x420 [uvcvideo]
   uvc_ioctl_default+0xe8/0x3e0 [uvcvideo]
   __video_do_ioctl+0x154/0x3e0 [videodev]
   video_usercopy+0x220/0x74c [videodev]
   video_ioctl2+0x18/0x74 [videodev]
   uvc_v4l2_unlocked_ioctl+0xd0/0x1e4 [uvcvideo]
   v4l2_ioctl+0x40/0x60 [videodev]
   __arm64_sys_ioctl+0x2ec/0xad4

The oops kills the calling task while it holds chain->ctrl_mutex. Its
exit path then blocks forever in uvc_ctrl_cleanup_fh(), every later
release of the video node blocks on the same mutex, and when the
display is unplugged the hub worker blocks in
vb2_video_unregister_device(), so the Thunderbolt port cannot enumerate
anything until a reboot. Any process with access to the video node can
trigger this.

Refuse value queries for a control whose length is 0 in
uvc_xu_ctrl_query(); UVC_GET_LEN and UVC_GET_INFO keep working so that
user space can still see the reported length. Also make uvc_query_ctrl()
return -EPIPE instead of borrowing the first byte of a zero-length
buffer for the error code request, so that no other caller can reach
the dereference.

Tested on a MacBook Pro 14" M1 Max (j314c) with the Studio Display XDR
on the Aurora release 20 kernel: the query that oopsed returns -ENODATA
and the process exits normally; the display's hotplug, 5K60 tiled mode,
audio and MJPEG camera are unaffected.

Same bug in mainline (checked 2026-10-19, 7.3-rc); the error code
request dates from 8e7a1db ("media: uvcvideo: Handle control pipe
protocol STALLs").

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QhYiD43tUYf8SqrkTnG5LQ
Signed-off-by: Brian Toll <[email protected]>

@chriskearney chriskearney left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Source approval for 31f6cbb, including the effective merge onto integration/aurora 12be047. Both changed files and the relevant query/cleanup/error paths were reviewed; the three-way merge is clean.

The value-query guard returns -ENODATA for a zero-size control through the existing cleanup path, releasing the mutex before any zero-size allocation/transfer. GET_LEN and GET_INFO remain available. The lower-level guard avoids borrowing data[0] when the buffer size is zero. Nonzero transfers and the 7.2.11 privacy check are preserved.

Independent host validation extracted the exact functions with mocked USB/allocator/kernel interfaces: the effective merge and PR-head variants each passed 16 cases. Before/one-guard controls exposed the missing guards, including seven zero-size crash witnesses in the pre-change fixture. This is host control-flow evidence, not a kernel/device run. The author's hardware evidence is on 7.1.12; no 7.2.11 hardware run or new kernel compile is claimed. Zero-size controls mapped through CTRL_MAP can still cause a zero-length transfer warning; that preexisting path does not invalidate these guards.

Required CI has never run on this head because this PR predates the workflow. I am reopening the unchanged PR solely to trigger the required checks. This approval is not a merge-ready claim: ci / result and the normal combined-tree merge queue must pass. No contributor branch changes or CI bypass.

@chriskearney

Copy link
Copy Markdown
Collaborator

Required CI run 41093190437 now passes on unchanged head 31f6cbb. #166 fixed the compile checkout; the fresh run was verified against test-merge fad2bbe and the corrected workflow before approval. Its compile log confirms the named event-ref fetch and compilation of both the 7.1.12 base and this commit (two objects each). The original missing-tool failures remain failures; no contributor rebase or source change was needed.

The pull-request run is checks-only: package, KUnit and boot were skipped, not passed. Normal merge-queue CI must validate the combined current 7.2.11 tree with its full tier. The independent effective-merge source/host review and its hardware limitations remain as recorded above.

@chriskearney
chriskearney added this pull request to the merge queue Oct 30, 2026
Merged via the queue into omacom:integration/aurora with commit a86ce7a Oct 30, 2026
27 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants