[PATCH v2 2/2] media: uvcvideo: Do not read beyond the uvc_status_control memory

From: Ricardo Ribalda

Date: Fri Oct 02 2026 - 07:22:31 EST


When we receive an event from the camera we only receive up to
wMaxPacketSize bytes. If a v4l2 control is mapped into a UVC control
beyond those bytes, right now the code is blindly reading them.

Add a check in the event handler to ignore controls that are outside the
data provided by the camera.

Note that this patch now dynamically allocates the size for the status
based on wMaxPacketSize instead of blindly use 16 bytes.

Cc: stable@xxxxxxxxxxxxxxx
Closes: https://sashiko.dev/#/patchset/F0F008459FFA835D%2B20260813074632.2021311-1-raoxu%40uniontech.com
Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives")
Signed-off-by: Ricardo Ribalda <ribalda@xxxxxxxxxxxx>
---
drivers/media/usb/uvc/uvc_ctrl.c | 12 +++++++++---
drivers/media/usb/uvc/uvc_driver.c | 2 +-
drivers/media/usb/uvc/uvc_status.c | 18 +++++++++++++-----
drivers/media/usb/uvc/uvcvideo.h | 9 ++++++---
4 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 6224ecf28ec6..e61235e83e80 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -2142,7 +2142,8 @@ static int uvc_ctrl_clear_handle(struct uvc_control *ctrl)
}

void uvc_ctrl_status_event(struct uvc_video_chain *chain,
- struct uvc_control *ctrl, const u8 *data)
+ struct uvc_control *ctrl, const u8 *data,
+ size_t size)
{
struct uvc_control_mapping *mapping;
struct uvc_fh *handle;
@@ -2162,6 +2163,9 @@ void uvc_ctrl_status_event(struct uvc_video_chain *chain,

if (uvc_ctrl_mapping_is_compound(mapping))
value = 0;
+ else if (DIV_ROUND_UP(mapping->offset + mapping->size, 8) >
+ size)
+ continue;
else
value = uvc_mapping_get_s32(mapping, UVC_GET_CUR, data);

@@ -2191,7 +2195,7 @@ static void uvc_ctrl_status_event_work(struct work_struct *work)
struct uvc_ctrl_work *w = &dev->async_ctrl;
int ret;

- uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data, w->size);

/* The barrier is needed to synchronize with uvc_status_stop(). */
if (smp_load_acquire(&dev->flush_status))
@@ -2206,7 +2210,8 @@ static void uvc_ctrl_status_event_work(struct work_struct *work)
}

bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain,
- struct uvc_control *ctrl, const u8 *data)
+ struct uvc_control *ctrl, const u8 *data,
+ size_t size)
{
struct uvc_device *dev = chain->dev;
struct uvc_ctrl_work *w = &dev->async_ctrl;
@@ -2218,6 +2223,7 @@ bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain,
w->urb = urb;
w->chain = chain;
w->ctrl = ctrl;
+ w->size = size;

schedule_work(&w->work);

diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 468d46dfb9e7..b57825fadea1 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -1305,7 +1305,7 @@ static void uvc_gpio_event(struct uvc_device *dev)

/* GPIO entities are always on the first chain. */
chain = list_first_entry(&dev->chains, struct uvc_video_chain, list);
- uvc_ctrl_status_event(chain, unit->controls, &new_val);
+ uvc_ctrl_status_event(chain, unit->controls, &new_val, sizeof(new_val));
}

static int uvc_gpio_get_cur(struct uvc_device *dev, struct uvc_entity *entity,
diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c
index 97c939206302..a88fa57513fb 100644
--- a/drivers/media/usb/uvc/uvc_status.c
+++ b/drivers/media/usb/uvc/uvc_status.c
@@ -169,6 +169,7 @@ static bool uvc_event_control(struct urb *urb,
struct uvc_device *dev = urb->context;
struct uvc_video_chain *chain;
struct uvc_control *ctrl;
+ size_t ctrl_len;

if (len < 6 || status->bEvent != 0 ||
status->control.bAttribute >= ARRAY_SIZE(attrs)) {
@@ -187,8 +188,10 @@ static bool uvc_event_control(struct urb *urb,

switch (status->control.bAttribute) {
case UVC_CTRL_VALUE_CHANGE:
+ ctrl_len = len - offsetof(struct uvc_status, control.bValue);
return uvc_ctrl_status_event_async(urb, chain, ctrl,
- status->control.bValue);
+ status->control.bValue,
+ ctrl_len);

case UVC_CTRL_INFO_CHANGE:
case UVC_CTRL_FAILURE_CHANGE:
@@ -257,13 +260,18 @@ int uvc_status_init(struct uvc_device *dev)
struct usb_host_endpoint *ep = dev->int_ep;
unsigned int pipe;
int interval;
+ size_t size;

mutex_init(&dev->status_lock);

if (ep == NULL)
return 0;

- dev->status = kzalloc_obj(*dev->status);
+ size = usb_endpoint_maxp(&ep->desc);
+ if (!size)
+ return -EINVAL;
+
+ dev->status = kzalloc(size, GFP_KERNEL);
if (!dev->status)
return -ENOMEM;

@@ -286,7 +294,7 @@ int uvc_status_init(struct uvc_device *dev)
interval = fls(interval) - 1;

usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
- dev->status, sizeof(*dev->status), uvc_status_complete,
+ dev->status, size, uvc_status_complete,
dev, interval);

uvc_input_init(dev);
@@ -360,7 +368,7 @@ static void uvc_status_stop(struct uvc_device *dev)
* process it synchronously.
*/
if (cancel_work_sync(&w->work))
- uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data, w->size);

/* Kill the urb. */
usb_kill_urb(dev->int_urb);
@@ -372,7 +380,7 @@ static void uvc_status_stop(struct uvc_device *dev)
* uvc_status_start() call.
*/
if (cancel_work_sync(&w->work))
- uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data, w->size);
}

int uvc_status_resume(struct uvc_device *dev)
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index 69ee3d0c971d..788cee0c409d 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -567,7 +567,7 @@ struct uvc_status_streaming {
struct uvc_status_control {
u8 bSelector;
u8 bAttribute;
- u8 bValue[11];
+ u8 bValue[];
} __packed;

struct uvc_status {
@@ -629,6 +629,7 @@ struct uvc_device {
struct uvc_video_chain *chain;
struct uvc_control *ctrl;
const void *data;
+ size_t size;
} async_ctrl;

struct uvc_entity *gpio_unit;
@@ -771,9 +772,11 @@ int uvc_ctrl_init_device(struct uvc_device *dev);
void uvc_ctrl_cleanup_device(struct uvc_device *dev);
int uvc_ctrl_restore_values(struct uvc_device *dev);
bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain,
- struct uvc_control *ctrl, const u8 *data);
+ struct uvc_control *ctrl, const u8 *data,
+ size_t size);
void uvc_ctrl_status_event(struct uvc_video_chain *chain,
- struct uvc_control *ctrl, const u8 *data);
+ struct uvc_control *ctrl, const u8 *data,
+ size_t size);

int uvc_ctrl_begin(struct uvc_video_chain *chain);
int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,

--
2.56.0.rc1.315.gc6ed9934b7-goog