| From 10cf6a541188d8a8dba2827e19090a7c3e4d27b9 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Tue, 3 Dec 2024 21:20:10 +0000 |
| Subject: media: uvcvideo: Remove dangling pointers |
| |
| From: Ricardo Ribalda <ribalda@chromium.org> |
| |
| [ Upstream commit 221cd51efe4565501a3dbf04cc011b537dcce7fb ] |
| |
| When an async control is written, we copy a pointer to the file handle |
| that started the operation. That pointer will be used when the device is |
| done. Which could be anytime in the future. |
| |
| If the user closes that file descriptor, its structure will be freed, |
| and there will be one dangling pointer per pending async control, that |
| the driver will try to use. |
| |
| Clean all the dangling pointers during release(). |
| |
| To avoid adding a performance penalty in the most common case (no async |
| operation), a counter has been introduced with some logic to make sure |
| that it is properly handled. |
| |
| Cc: stable@vger.kernel.org |
| Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives") |
| Reviewed-by: Hans de Goede <hdegoede@redhat.com> |
| Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> |
| Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> |
| Link: https://lore.kernel.org/r/20241203-uvc-fix-async-v6-3-26c867231118@chromium.org |
| Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> |
| Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/media/usb/uvc/uvc_ctrl.c | 59 ++++++++++++++++++++++++++++++-- |
| drivers/media/usb/uvc/uvc_v4l2.c | 2 ++ |
| drivers/media/usb/uvc/uvcvideo.h | 9 ++++- |
| 3 files changed, 67 insertions(+), 3 deletions(-) |
| |
| diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c |
| index 6be1aff23e71c..69f9f451ab400 100644 |
| --- a/drivers/media/usb/uvc/uvc_ctrl.c |
| +++ b/drivers/media/usb/uvc/uvc_ctrl.c |
| @@ -1470,6 +1470,40 @@ static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain, |
| uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes); |
| } |
| |
| +static void uvc_ctrl_set_handle(struct uvc_fh *handle, struct uvc_control *ctrl, |
| + struct uvc_fh *new_handle) |
| +{ |
| + lockdep_assert_held(&handle->chain->ctrl_mutex); |
| + |
| + if (new_handle) { |
| + if (ctrl->handle) |
| + dev_warn_ratelimited(&handle->stream->dev->udev->dev, |
| + "UVC non compliance: Setting an async control with a pending operation."); |
| + |
| + if (new_handle == ctrl->handle) |
| + return; |
| + |
| + if (ctrl->handle) { |
| + WARN_ON(!ctrl->handle->pending_async_ctrls); |
| + if (ctrl->handle->pending_async_ctrls) |
| + ctrl->handle->pending_async_ctrls--; |
| + } |
| + |
| + ctrl->handle = new_handle; |
| + handle->pending_async_ctrls++; |
| + return; |
| + } |
| + |
| + /* Cannot clear the handle for a control not owned by us.*/ |
| + if (WARN_ON(ctrl->handle != handle)) |
| + return; |
| + |
| + ctrl->handle = NULL; |
| + if (WARN_ON(!handle->pending_async_ctrls)) |
| + return; |
| + handle->pending_async_ctrls--; |
| +} |
| + |
| void uvc_ctrl_status_event(struct uvc_video_chain *chain, |
| struct uvc_control *ctrl, const u8 *data) |
| { |
| @@ -1480,7 +1514,8 @@ void uvc_ctrl_status_event(struct uvc_video_chain *chain, |
| mutex_lock(&chain->ctrl_mutex); |
| |
| handle = ctrl->handle; |
| - ctrl->handle = NULL; |
| + if (handle) |
| + uvc_ctrl_set_handle(handle, ctrl, NULL); |
| |
| list_for_each_entry(mapping, &ctrl->info.mappings, list) { |
| s32 value = __uvc_ctrl_get_value(mapping, data); |
| @@ -1754,7 +1789,7 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, |
| |
| if (!rollback && handle && |
| ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) |
| - ctrl->handle = handle; |
| + uvc_ctrl_set_handle(handle, ctrl, handle); |
| } |
| |
| return 0; |
| @@ -2666,6 +2701,26 @@ int uvc_ctrl_init_device(struct uvc_device *dev) |
| return 0; |
| } |
| |
| +void uvc_ctrl_cleanup_fh(struct uvc_fh *handle) |
| +{ |
| + struct uvc_entity *entity; |
| + |
| + guard(mutex)(&handle->chain->ctrl_mutex); |
| + |
| + if (!handle->pending_async_ctrls) |
| + return; |
| + |
| + list_for_each_entry(entity, &handle->chain->dev->entities, list) { |
| + for (unsigned int i = 0; i < entity->ncontrols; ++i) { |
| + if (entity->controls[i].handle != handle) |
| + continue; |
| + uvc_ctrl_set_handle(handle, &entity->controls[i], NULL); |
| + } |
| + } |
| + |
| + WARN_ON(handle->pending_async_ctrls); |
| +} |
| + |
| /* |
| * Cleanup device controls. |
| */ |
| diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c |
| index 950b42d78a107..bd4677a6e653a 100644 |
| --- a/drivers/media/usb/uvc/uvc_v4l2.c |
| +++ b/drivers/media/usb/uvc/uvc_v4l2.c |
| @@ -607,6 +607,8 @@ static int uvc_v4l2_release(struct file *file) |
| |
| uvc_dbg(stream->dev, CALLS, "%s\n", __func__); |
| |
| + uvc_ctrl_cleanup_fh(handle); |
| + |
| /* Only free resources if this is a privileged handle. */ |
| if (uvc_has_privileges(handle)) |
| uvc_queue_release(&stream->queue); |
| diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h |
| index 475bf185be8a8..45caa8523426d 100644 |
| --- a/drivers/media/usb/uvc/uvcvideo.h |
| +++ b/drivers/media/usb/uvc/uvcvideo.h |
| @@ -331,7 +331,11 @@ struct uvc_video_chain { |
| struct uvc_entity *processing; /* Processing unit */ |
| struct uvc_entity *selector; /* Selector unit */ |
| |
| - struct mutex ctrl_mutex; /* Protects ctrl.info */ |
| + struct mutex ctrl_mutex; /* |
| + * Protects ctrl.info, |
| + * ctrl.handle and |
| + * uvc_fh.pending_async_ctrls |
| + */ |
| |
| struct v4l2_prio_state prio; /* V4L2 priority state */ |
| u32 caps; /* V4L2 chain-wide caps */ |
| @@ -585,6 +589,7 @@ struct uvc_fh { |
| struct uvc_video_chain *chain; |
| struct uvc_streaming *stream; |
| enum uvc_handle_state state; |
| + unsigned int pending_async_ctrls; |
| }; |
| |
| struct uvc_driver { |
| @@ -769,6 +774,8 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, |
| int uvc_xu_ctrl_query(struct uvc_video_chain *chain, |
| struct uvc_xu_control_query *xqry); |
| |
| +void uvc_ctrl_cleanup_fh(struct uvc_fh *handle); |
| + |
| /* Utility functions */ |
| struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts, |
| u8 epaddr); |
| -- |
| 2.39.5 |
| |