linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
@ 2023-09-30 18:48 Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                   ` (7 more replies)
  0 siblings, 8 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-09-30 18:48 UTC (permalink / raw)
  To: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, Michael Grzeschik
  Cc: jchowdhary, etalvala, linux-usb, linux-kernel, Avichal Rakesh

We have been seeing two main stability issues that uvc gadget driver
runs into when stopping streams:
 1. Attempting to queue usb_requests to a disabled usb_ep
 2. use-after-free issue for inflight usb_requests

The three patches below fix the two issues above. Patch 1/3 fixes the
first issue, and Patch 2/3 and 3/3 fix the second issue.

Avichal Rakesh (3):
  usb: gadget: uvc: prevent use of disabled endpoint
  usb: gadget: uvc: Allocate uvc_requests one at a time
  usb: gadget: uvc: Fix use-after-free for inflight usb_requests

 drivers/usb/gadget/function/f_uvc.c     |  11 +-
 drivers/usb/gadget/function/f_uvc.h     |   2 +-
 drivers/usb/gadget/function/uvc.h       |   6 +-
 drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
 drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
 5 files changed, 164 insertions(+), 65 deletions(-)

--
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
@ 2023-09-30 18:48 ` Avichal Rakesh
  2023-10-03 23:18   ` [PATCH v2 " Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-09-30 18:48 UTC (permalink / raw)
  To: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, Michael Grzeschik
  Cc: jchowdhary, etalvala, linux-usb, linux-kernel, Avichal Rakesh

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 21 ++++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..75c9f9a3f884 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }
 
-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;
 
+	if (disable_ep && uvc->video.ep) {
+		usb_ep_disable(uvc->video.ep);
+	}
 	usb_composite_setup_continue(cdev);
 }
 
@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;
 
-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);
 
-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;
 
 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@
 
 struct uvc_device;
 
-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);
 
 void uvc_function_connect(struct uvc_device *uvc);
 
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */
 
-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);
 
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..3d3469883ed0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
 
 	return 0;
@@ -463,11 +463,19 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;
 
 	if (type != video->queue.queue.type)
 		return -EINVAL;
 
-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0) {
+		return ret;
+	}
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }
 
 static int
@@ -500,6 +508,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	if (uvc->state == UVC_STATE_STREAMING) {
+		/*
+		 * Drop uvc->state to CONNECTED if it was streaming before.
+		 * This ensures that the usb_requests are no longer queued
+		 * to the controller.
+		 */
+		uvc->state = UVC_STATE_CONNECTED;
+	}
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +663,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..70ff88854539 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -382,6 +382,7 @@ static void uvcg_video_pump(struct work_struct *work)
 {
 	struct uvc_video *video = container_of(work, struct uvc_video, pump);
 	struct uvc_video_queue *queue = &video->queue;
+	struct uvc_device *uvc = video->uvc;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
 	struct usb_request *req = NULL;
@@ -390,7 +391,7 @@ static void uvcg_video_pump(struct work_struct *work)
 	bool buf_done;
 	int ret;
 
-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
-- 
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v1 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-09-30 18:48 ` Avichal Rakesh
  2023-10-03 23:19   ` [PATCH v2 " Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-09-30 18:48 UTC (permalink / raw)
  To: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, Michael Grzeschik
  Cc: jchowdhary, etalvala, linux-usb, linux-kernel, Avichal Rakesh,
	Michael Grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 90 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };
 
 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {
 
 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;
 
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 70ff88854539..ffecd7a140dc 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,23 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */
 
+static void uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +310,13 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
-
-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
+	struct uvc_request *ureq, *temp;
 
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		uvc_video_free_request(ureq, video->ep);
 	}
 
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,31 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);
 
-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
-
-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
 			goto error;
+		INIT_LIST_HEAD(&ureq->list);
+		list_add_tail(&ureq->list, &video->ureqs);
+	}
 
-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;
-
-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
-
-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
+			goto error;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +495,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;
 
 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +508,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);
 
-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}
 
 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +543,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
-- 
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v1 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-09-30 18:48 ` [PATCH v1 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-09-30 18:48 ` Avichal Rakesh
  2023-10-03 23:21   ` [PATCH v2 " Avichal Rakesh
  2023-10-03 11:09 ` [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Michael Grzeschik
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-09-30 18:48 UTC (permalink / raw)
  To: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, Michael Grzeschik
  Cc: jchowdhary, etalvala, linux-usb, linux-kernel, Avichal Rakesh,
	Michael Grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_request to mark it as 'abandoned'. When disabling the video
stream, instead of de-allocating all uvc_requests and usb_requests, the
gadget driver only de-allocates those usb_requests that are currently
owned by the gadget driver (as present in req_free). Other usb_requests
have their corresponding 'is_abandoned' flag tripped, and the
usb_requests complete handler takes care of freeing the usb_request and
its corresponding uvc_request.

This should ensure that uvc gadget driver never accidentally de-allocates
a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_video.c | 106 ++++++++++++++++++++----
 2 files changed, 91 insertions(+), 16 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..e69cfb7cced1 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -82,6 +82,7 @@ struct uvc_request {
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
 	struct list_head list;
+	bool is_abandoned;
 };
 
 struct uvc_video {
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index ffecd7a140dc..aad7dcba46ee 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -271,7 +271,21 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
 	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf;
 	unsigned long flags;
+	bool is_abandoned;
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	is_abandoned = ureq->is_abandoned;
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func, "Freeing abandoned usb_request\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}
 
 	switch (req->status) {
 	case 0:
@@ -294,15 +308,29 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}
 
-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
 	}
 
+	/*
+	 * request might have been abandoned while being processed.
+	 * do a last minute check before queueing the request back.
+	 */
 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	is_abandoned = ureq->is_abandoned;
+	if (!is_abandoned)
+		list_add_tail(&req->list, &video->req_free);
 	spin_unlock_irqrestore(&video->req_lock, flags);
 
+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func,
+			 "usb_request abandoned mid-processing - freeing.\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}
+
 	if (uvc->state == UVC_STATE_STREAMING)
 		queue_work(video->async_wq, &video->pump);
 }
@@ -366,7 +394,6 @@ uvc_video_alloc_requests(struct uvc_video *video)
 	}
 
 	video->req_size = req_size;
-
 	return 0;
 
 error:
@@ -490,13 +517,69 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }
 
+/*
+ * Disable video stream. This ensures that any inflight usb requests are marked
+ * for clean up and all video buffers are dropped before returning.
+ */
+static void uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_buffer *buf, *tmp_buf;
+	struct uvc_request *ureq, *temp;
+	struct list_head buf_list; /* track in-flight video buffers */
+	struct usb_request *req;
+	unsigned long flags;
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	INIT_LIST_HEAD(&buf_list);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/* abandon all usb requests */
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		ureq->is_abandoned = true;
+		if (ureq->last_buf) {
+			list_add(&ureq->last_buf->queue, &buf_list);
+			ureq->last_buf = NULL;
+		}
+		list_del_init(&ureq->list);
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+	/*
+	 * re-add uvc_requests currently owned by the gadget to
+	 * video->ureqs to be deallocated
+	 */
+	list_for_each_entry(req, &video->req_free, list) {
+		ureq = req->context;
+		list_add_tail(&ureq->list, &video->ureqs);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * drop abandoned uvc_buffers, as the completion handler
+	 * no longer will
+	 */
+	if (!list_empty(&buf_list)) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		list_for_each_entry_safe(buf, tmp_buf,
+						&buf_list, queue) {
+			video->queue.flags |= UVC_QUEUE_DROP_INCOMPLETE;
+			uvcg_complete_buffer(&video->queue, buf);
+			list_del(&buf->queue);
+		}
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;
 
 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -505,16 +588,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 	}
 
 	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
+		uvcg_video_disable(video);
 		return 0;
 	}
 
-- 
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                   ` (2 preceding siblings ...)
  2023-09-30 18:48 ` [PATCH v1 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-03 11:09 ` Michael Grzeschik
  2023-10-03 23:16   ` Avichal Rakesh
  2023-10-05  8:23   ` Laurent Pinchart
  2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-03 11:09 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2710 bytes --]

Hi

On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>We have been seeing two main stability issues that uvc gadget driver
>runs into when stopping streams:
> 1. Attempting to queue usb_requests to a disabled usb_ep
> 2. use-after-free issue for inflight usb_requests
>
>The three patches below fix the two issues above. Patch 1/3 fixes the
>first issue, and Patch 2/3 and 3/3 fix the second issue.
>
>Avichal Rakesh (3):
>  usb: gadget: uvc: prevent use of disabled endpoint
>  usb: gadget: uvc: Allocate uvc_requests one at a time
>  usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>
> drivers/usb/gadget/function/f_uvc.c     |  11 +-
> drivers/usb/gadget/function/f_uvc.h     |   2 +-
> drivers/usb/gadget/function/uvc.h       |   6 +-
> drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
> drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
> 5 files changed, 164 insertions(+), 65 deletions(-)

These patches are not applying on gregkh/usb-testing since
Greg did take my patches first. I have already rebased them.

In the updated version I the stack runs into the
following error, when enabling lockdep. Could you
try your version with lockdep enabled?

[   41.278520] configfs-gadget.vz gadget.0: uvc: reset UVC
[   47.156261] configfs-gadget.vz gadget.0: uvc: uvc_function_set_alt(2, 0)
[   47.169177]
[   47.170903] ============================================
[   47.176857] WARNING: possible recursive locking detected
[   47.182798] 6.5.0-20230919-1+ #19 Tainted: G         C
[   47.189323] --------------------------------------------
[   47.195256] vzuvcd/412 is trying to acquire lock:
[   47.200511] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvc_video_complete+0x44/0x2e0
[   47.210172]
[   47.210172] but task is already holding lock:
[   47.216687] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvcg_video_enable+0x2d0/0x5c0
[   47.226333]
[   47.226333] other info that might help us debug this:
[   47.233625]  Possible unsafe locking scenario:
[   47.233625]
[   47.240242]        CPU0
[   47.242974]        ----
[   47.245709]   lock(&video->req_lock);
[   47.249802]   lock(&video->req_lock);
[   47.253897]
[   47.253897]  *** DEADLOCK ***
[   47.253897]
[   47.260511]  May be due to missing lock nesting notation
[   47.260511]


Regards,
Michael

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-03 11:09 ` [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Michael Grzeschik
@ 2023-10-03 23:16   ` Avichal Rakesh
  2023-10-05  7:11     ` Greg Kroah-Hartman
  2023-10-05  8:23   ` Laurent Pinchart
  1 sibling, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-03 23:16 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

Thank you for testing the patch, Michael!

On 10/3/23 04:09, Michael Grzeschik wrote:
> Hi
> 
> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>> We have been seeing two main stability issues that uvc gadget driver
>> runs into when stopping streams:
>> 1. Attempting to queue usb_requests to a disabled usb_ep
>> 2. use-after-free issue for inflight usb_requests
>>
>> The three patches below fix the two issues above. Patch 1/3 fixes the
>> first issue, and Patch 2/3 and 3/3 fix the second issue.
>>
>> Avichal Rakesh (3):
>>  usb: gadget: uvc: prevent use of disabled endpoint
>>  usb: gadget: uvc: Allocate uvc_requests one at a time
>>  usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>
>> drivers/usb/gadget/function/f_uvc.c     |  11 +-
>> drivers/usb/gadget/function/f_uvc.h     |   2 +-
>> drivers/usb/gadget/function/uvc.h       |   6 +-
>> drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>> drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>> 5 files changed, 164 insertions(+), 65 deletions(-)
> 
> These patches are not applying on gregkh/usb-testing since
> Greg did take my patches first. I have already rebased them.

Ah, I didn't realize Greg had picked up your changes in his tree.
Rebased the patches in V2.

Also want to point out that 
https://lore.kernel.org/20230911002451.2860049-2-m.grzeschik@pengutronix.de/
may have introduced an issue when setting the uvc->state to 
UVC_STATE_CONNECTED in uvcg_video_enable. 

Effectively, uvc_video_enable can be called 
  1. because the host asks to halt the stream, or 
  2. if the gadget was disabled. 

Setting uvc->state to CONNECTED is fine for (1). In (2), uvc_v4l2 
sets uvc->state to DISCONNECTED before calling uvcg_video_enable. 
In this case, your change would overwrite the value to CONNECTED 
without any further checks.

> 
> In the updated version I the stack runs into the
> following error, when enabling lockdep. Could you
> try your version with lockdep enabled?
> 
> [   41.278520] configfs-gadget.vz gadget.0: uvc: reset UVC
> [   47.156261] configfs-gadget.vz gadget.0: uvc: uvc_function_set_alt(2, 0)
> [   47.169177]
> [   47.170903] ============================================
> [   47.176857] WARNING: possible recursive locking detected
> [   47.182798] 6.5.0-20230919-1+ #19 Tainted: G         C
> [   47.189323] --------------------------------------------
> [   47.195256] vzuvcd/412 is trying to acquire lock:
> [   47.200511] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvc_video_complete+0x44/0x2e0
> [   47.210172]
> [   47.210172] but task is already holding lock:
> [   47.216687] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvcg_video_enable+0x2d0/0x5c0
> [   47.226333]
> [   47.226333] other info that might help us debug this:
> [   47.233625]  Possible unsafe locking scenario:
> [   47.233625]
> [   47.240242]        CPU0
> [   47.242974]        ----
> [   47.245709]   lock(&video->req_lock);
> [   47.249802]   lock(&video->req_lock);
> [   47.253897]
> [   47.253897]  *** DEADLOCK ***
> [   47.253897]
> [   47.260511]  May be due to missing lock nesting notation
> [   47.260511]
> 

Thank you for catching this. I couldn't repro the deadlock, but it looks
like DWC3 controller can call the complete callback directly from 
usb_ep_dequeue for requests in its pending list. I am not sure if that 
is up to spec, considering calling usb_ep_queue explicitly forbids calling
the complete callback from within. Regardless, it is easy enough to fix
by moving the dequeue calls to before we acquire req_lock. The semantics
of the rest of the patch is unchanged.

Uploading the fixed patches shortly.

Regards,
Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v2 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-09-30 18:48 ` [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-03 23:18   ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-03 23:18 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, mgr
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. To be consistent with the actual streaming state, uvc->state
is now toggled between CONNECTED and STREAMING from the v4l2 event
callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.

 drivers/usb/gadget/function/f_uvc.c    | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h    |  2 +-
 drivers/usb/gadget/function/uvc.h      |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c | 21 ++++++++++++++++++---
 4 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..75c9f9a3f884 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep) {
+		usb_ep_disable(uvc->video.ep);
+	}
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..3d3469883ed0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,19 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0) {
+		return ret;
+	}
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +508,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	if (uvc->state == UVC_STATE_STREAMING) {
+		/*
+		 * Drop uvc->state to CONNECTED if it was streaming before.
+		 * This ensures that the usb_requests are no longer queued
+		 * to the controller.
+		 */
+		uvc->state = UVC_STATE_CONNECTED;
+	}
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +663,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
--
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v2 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-09-30 18:48 ` [PATCH v1 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-03 23:19   ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-03 23:19 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik, mgr
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 90 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 97d875c27dcf..3c4d286d81c0 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,23 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -299,27 +316,13 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
-
-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		uvc_video_free_request(ureq, video->ep);
 	}

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -328,6 +331,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -338,29 +342,31 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
-
-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
 			goto error;
+		INIT_LIST_HEAD(&ureq->list);
+		list_add_tail(&ureq->list, &video->ureqs);
+	}

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;
-
-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
-
-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
+			goto error;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -504,7 +510,7 @@ static void uvcg_video_pump(struct work_struct *work)
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	struct uvc_device *uvc = video->uvc;
-	unsigned int i;
+	struct uvc_request *ureq;
 	int ret;

 	if (video->ep == NULL) {
@@ -519,9 +525,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -555,6 +562,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v2 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-09-30 18:48 ` [PATCH v1 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-03 23:21   ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-03 23:21 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik, mgr
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_request to mark it as 'abandoned'. When disabling the video
stream, instead of de-allocating all uvc_requests and usb_requests, the
gadget driver only de-allocates those usb_requests that are currently
owned by the gadget driver (as present in req_free). Other usb_requests
have their corresponding 'is_abandoned' flag tripped, and the
usb_requests complete handler takes care of freeing the usb_request and
its corresponding uvc_request.

This should ensure that uvc gadget driver never accidentally de-allocates
a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_video.c | 118 ++++++++++++++++++++----
 2 files changed, 102 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..e69cfb7cced1 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -82,6 +82,7 @@ struct uvc_request {
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
 	struct list_head list;
+	bool is_abandoned;
 };

 struct uvc_video {
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 3c4d286d81c0..69521886d599 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -271,7 +271,21 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
 	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf;
 	unsigned long flags;
+	bool is_abandoned;
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	is_abandoned = ureq->is_abandoned;
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func, "Freeing abandoned usb_request\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}

 	if (uvc->state == UVC_STATE_CONNECTED) {
 		usb_ep_free_request(video->ep, ureq->req);
@@ -300,15 +314,29 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
 	}

+	/*
+	 * request might have been abandoned while being processed.
+	 * do a last minute check before queueing the request back.
+	 */
 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	is_abandoned = ureq->is_abandoned;
+	if (!is_abandoned)
+		list_add_tail(&req->list, &video->req_free);
 	spin_unlock_irqrestore(&video->req_lock, flags);

+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func,
+			 "usb_request abandoned mid-processing - freeing.\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}
+
 	if (uvc->state == UVC_STATE_STREAMING)
 		queue_work(video->async_wq, &video->pump);
 }
@@ -372,7 +400,6 @@ uvc_video_alloc_requests(struct uvc_video *video)
 	}

 	video->req_size = req_size;
-
 	return 0;

 error:
@@ -504,13 +531,80 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }

+/*
+ * Disable video stream. This ensures that any inflight usb requests are marked
+ * for clean up and all video buffers are dropped before returning.
+ */
+static void uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_buffer *buf, *tmp_buf;
+	struct uvc_request *ureq, *temp;
+	struct list_head buf_list; /* track in-flight video buffers */
+	struct usb_request *req;
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&buf_list);
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+
+	/**
+	 * acquiring req_lock here should prevent any more complete
+	 * callbacks from processing until we've abandoned the
+	 * requests that are still in-flight and let the complete
+	 * callback handle cleanup
+	 */
+	spin_lock_irqsave(&video->req_lock, flags);
+	/* abandon all usb requests */
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		ureq->is_abandoned = true;
+		if (ureq->last_buf) {
+			list_add(&ureq->last_buf->queue, &buf_list);
+			ureq->last_buf = NULL;
+		}
+		list_del_init(&ureq->list);
+	}
+	/*
+	 * re-add uvc_requests currently owned by the gadget to
+	 * video->ureqs to be deallocated. This effectively leaves
+	 * video->ureqs with the requests that we currently own.
+	 */
+	list_for_each_entry(req, &video->req_free, list) {
+		ureq = req->context;
+		list_add_tail(&ureq->list, &video->ureqs);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * drop abandoned uvc_buffers, as the completion handler
+	 * no longer will
+	 */
+	if (!list_empty(&buf_list)) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		list_for_each_entry_safe(buf, tmp_buf,
+						&buf_list, queue) {
+			video->queue.flags |= UVC_QUEUE_DROP_INCOMPLETE;
+			uvcg_complete_buffer(&video->queue, buf);
+			list_del(&buf->queue);
+		}
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	struct uvc_device *uvc = video->uvc;
-	struct uvc_request *ureq;
 	int ret;

 	if (video->ep == NULL) {
@@ -521,17 +615,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)

 	if (!enable) {
 		uvc->state = UVC_STATE_CONNECTED;
-
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
+		uvcg_video_disable(video);
 		return 0;
 	}

--
2.42.0.582.g8ccd20d70d-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-03 23:16   ` Avichal Rakesh
@ 2023-10-05  7:11     ` Greg Kroah-Hartman
  2023-10-05 18:09       ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Greg Kroah-Hartman @ 2023-10-05  7:11 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Michael Grzeschik, Laurent Pinchart, Daniel Scally, jchowdhary,
	etalvala, linux-usb, linux-kernel

On Tue, Oct 03, 2023 at 04:16:00PM -0700, Avichal Rakesh wrote:
> Thank you for testing the patch, Michael!
> 
> On 10/3/23 04:09, Michael Grzeschik wrote:
> > Hi
> > 
> > On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
> >> We have been seeing two main stability issues that uvc gadget driver
> >> runs into when stopping streams:
> >> 1. Attempting to queue usb_requests to a disabled usb_ep
> >> 2. use-after-free issue for inflight usb_requests
> >>
> >> The three patches below fix the two issues above. Patch 1/3 fixes the
> >> first issue, and Patch 2/3 and 3/3 fix the second issue.
> >>
> >> Avichal Rakesh (3):
> >>  usb: gadget: uvc: prevent use of disabled endpoint
> >>  usb: gadget: uvc: Allocate uvc_requests one at a time
> >>  usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> >>
> >> drivers/usb/gadget/function/f_uvc.c     |  11 +-
> >> drivers/usb/gadget/function/f_uvc.h     |   2 +-
> >> drivers/usb/gadget/function/uvc.h       |   6 +-
> >> drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
> >> drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
> >> 5 files changed, 164 insertions(+), 65 deletions(-)
> > 
> > These patches are not applying on gregkh/usb-testing since
> > Greg did take my patches first. I have already rebased them.
> 
> Ah, I didn't realize Greg had picked up your changes in his tree.
> Rebased the patches in V2.

The "v2" series here is almost impossible to follow, sorry.

Please send it as a new thread, not as responses to the individual
commits, how am I supposed to pick them up that way?

And make it v3 please.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-03 11:09 ` [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Michael Grzeschik
  2023-10-03 23:16   ` Avichal Rakesh
@ 2023-10-05  8:23   ` Laurent Pinchart
  2023-10-05 10:14     ` Michael Grzeschik
  1 sibling, 1 reply; 94+ messages in thread
From: Laurent Pinchart @ 2023-10-05  8:23 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: Avichal Rakesh, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
> > We have been seeing two main stability issues that uvc gadget driver
> > runs into when stopping streams:
> >  1. Attempting to queue usb_requests to a disabled usb_ep
> >  2. use-after-free issue for inflight usb_requests
> >
> > The three patches below fix the two issues above. Patch 1/3 fixes the
> > first issue, and Patch 2/3 and 3/3 fix the second issue.
> >
> > Avichal Rakesh (3):
> >   usb: gadget: uvc: prevent use of disabled endpoint
> >   usb: gadget: uvc: Allocate uvc_requests one at a time
> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> >
> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
> > drivers/usb/gadget/function/uvc.h       |   6 +-
> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
> > 5 files changed, 164 insertions(+), 65 deletions(-)
> 
> These patches are not applying on gregkh/usb-testing since
> Greg did take my patches first. I have already rebased them.

I think they got merged too soon :-( We could fix things on top, but
there's very little time to do so for v6.7.

> In the updated version I the stack runs into the
> following error, when enabling lockdep. Could you
> try your version with lockdep enabled?
> 
> [   41.278520] configfs-gadget.vz gadget.0: uvc: reset UVC
> [   47.156261] configfs-gadget.vz gadget.0: uvc: uvc_function_set_alt(2, 0)
> [   47.169177]
> [   47.170903] ============================================
> [   47.176857] WARNING: possible recursive locking detected
> [   47.182798] 6.5.0-20230919-1+ #19 Tainted: G         C
> [   47.189323] --------------------------------------------
> [   47.195256] vzuvcd/412 is trying to acquire lock:
> [   47.200511] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvc_video_complete+0x44/0x2e0
> [   47.210172]
> [   47.210172] but task is already holding lock:
> [   47.216687] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvcg_video_enable+0x2d0/0x5c0
> [   47.226333]
> [   47.226333] other info that might help us debug this:
> [   47.233625]  Possible unsafe locking scenario:
> [   47.233625]
> [   47.240242]        CPU0
> [   47.242974]        ----
> [   47.245709]   lock(&video->req_lock);
> [   47.249802]   lock(&video->req_lock);
> [   47.253897]
> [   47.253897]  *** DEADLOCK ***
> [   47.253897]
> [   47.260511]  May be due to missing lock nesting notation
> [   47.260511]

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-05  8:23   ` Laurent Pinchart
@ 2023-10-05 10:14     ` Michael Grzeschik
  2023-10-05 18:30       ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-05 10:14 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Avichal Rakesh, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 4074 bytes --]

Hi Laurent

On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>> > We have been seeing two main stability issues that uvc gadget driver
>> > runs into when stopping streams:
>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>> >  2. use-after-free issue for inflight usb_requests
>> >
>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>> >
>> > Avichal Rakesh (3):
>> >   usb: gadget: uvc: prevent use of disabled endpoint
>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>> >
>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>
>> These patches are not applying on gregkh/usb-testing since
>> Greg did take my patches first. I have already rebased them.
>
>I think they got merged too soon :-( We could fix things on top, but
>there's very little time to do so for v6.7.

Agreed. I was jumping from one workaround to another one, since this
is not easy to fix in a proper way. And still after this long discussion
with Avichal I don't think we are there yet.


So far the first two patches from Avichal look legit. But the overall
Use-After-Free fix is yet to be done properly.

The "abondoned" method he suggested is really bad to follow and will
add too much complexity and will be hard to debug.

IMHO it should be possible to introduce two cleanup pathes.

One path would be in the uvc_cleanup_requests that will cleanup the
requests that are actually not used in the controller and are registered
in the req_free list.

The second path would be the complete functions that are being run
from the controller and will ensure that the cleanup will really free
the requests from the controller after they were consumed.

What do you think?

Regards,
Michael

>> In the updated version I the stack runs into the
>> following error, when enabling lockdep. Could you
>> try your version with lockdep enabled?
>>
>> [   41.278520] configfs-gadget.vz gadget.0: uvc: reset UVC
>> [   47.156261] configfs-gadget.vz gadget.0: uvc: uvc_function_set_alt(2, 0)
>> [   47.169177]
>> [   47.170903] ============================================
>> [   47.176857] WARNING: possible recursive locking detected
>> [   47.182798] 6.5.0-20230919-1+ #19 Tainted: G         C
>> [   47.189323] --------------------------------------------
>> [   47.195256] vzuvcd/412 is trying to acquire lock:
>> [   47.200511] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvc_video_complete+0x44/0x2e0
>> [   47.210172]
>> [   47.210172] but task is already holding lock:
>> [   47.216687] ffffff8009560928 (&video->req_lock){....}-{3:3}, at: uvcg_video_enable+0x2d0/0x5c0
>> [   47.226333]
>> [   47.226333] other info that might help us debug this:
>> [   47.233625]  Possible unsafe locking scenario:
>> [   47.233625]
>> [   47.240242]        CPU0
>> [   47.242974]        ----
>> [   47.245709]   lock(&video->req_lock);
>> [   47.249802]   lock(&video->req_lock);
>> [   47.253897]
>> [   47.253897]  *** DEADLOCK ***
>> [   47.253897]
>> [   47.260511]  May be due to missing lock nesting notation
>> [   47.260511]
>
>-- 
>Regards,
>
>Laurent Pinchart
>

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                   ` (3 preceding siblings ...)
  2023-10-03 11:09 ` [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Michael Grzeschik
@ 2023-10-05 18:08 ` Avichal Rakesh
  2023-10-05 18:08   ` [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                     ` (2 more replies)
  2023-10-12  0:24 ` [PATCH v4 " Avichal Rakesh
                   ` (2 subsequent siblings)
  7 siblings, 3 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-05 18:08 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. To be consistent with the actual streaming state, uvc->state
is now toggled between CONNECTED and STREAMING from the v4l2 event
callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up

 drivers/usb/gadget/function/f_uvc.c    | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h    |  2 +-
 drivers/usb/gadget/function/uvc.h      |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c | 21 ++++++++++++++++++---
 4 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..75c9f9a3f884 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep) {
+		usb_ep_disable(uvc->video.ep);
+	}
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..3d3469883ed0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,19 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0) {
+		return ret;
+	}
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +508,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	if (uvc->state == UVC_STATE_STREAMING) {
+		/*
+		 * Drop uvc->state to CONNECTED if it was streaming before.
+		 * This ensures that the usb_requests are no longer queued
+		 * to the controller.
+		 */
+		uvc->state = UVC_STATE_CONNECTED;
+	}
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +663,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
--
2.42.0.609.gbb76f46606-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-05 18:08   ` Avichal Rakesh
  2023-10-06 22:11     ` Michael Grzeschik
  2023-10-05 18:08   ` [PATCH v3 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-10-06 22:04   ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Michael Grzeschik
  2 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-05 18:08 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, Michael Grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
V2 -> V3: Fix email threading goof-up

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 90 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 97d875c27dcf..3c4d286d81c0 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,23 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -299,27 +316,13 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
-
-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		uvc_video_free_request(ureq, video->ep);
 	}

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -328,6 +331,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -338,29 +342,31 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
-
-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
 			goto error;
+		INIT_LIST_HEAD(&ureq->list);
+		list_add_tail(&ureq->list, &video->ureqs);
+	}

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;
-
-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
-
-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
+			goto error;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -504,7 +510,7 @@ static void uvcg_video_pump(struct work_struct *work)
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	struct uvc_device *uvc = video->uvc;
-	unsigned int i;
+	struct uvc_request *ureq;
 	int ret;

 	if (video->ep == NULL) {
@@ -519,9 +525,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -555,6 +562,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.609.gbb76f46606-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v3 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-05 18:08   ` [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-05 18:08   ` Avichal Rakesh
  2023-10-06 22:04   ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Michael Grzeschik
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-05 18:08 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, Michael Grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_request to mark it as 'abandoned'. When disabling the video
stream, instead of de-allocating all uvc_requests and usb_requests, the
gadget driver only de-allocates those usb_requests that are currently
owned by the gadget driver (as present in req_free). Other usb_requests
have their corresponding 'is_abandoned' flag tripped, and the
usb_requests complete handler takes care of freeing the usb_request and
its corresponding uvc_request.

This should ensure that uvc gadget driver never accidentally de-allocates
a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <mgr@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_video.c | 118 ++++++++++++++++++++----
 2 files changed, 102 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..e69cfb7cced1 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -82,6 +82,7 @@ struct uvc_request {
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
 	struct list_head list;
+	bool is_abandoned;
 };

 struct uvc_video {
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 3c4d286d81c0..69521886d599 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -271,7 +271,21 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
 	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf;
 	unsigned long flags;
+	bool is_abandoned;
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	is_abandoned = ureq->is_abandoned;
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func, "Freeing abandoned usb_request\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}

 	if (uvc->state == UVC_STATE_CONNECTED) {
 		usb_ep_free_request(video->ep, ureq->req);
@@ -300,15 +314,29 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
 	}

+	/*
+	 * request might have been abandoned while being processed.
+	 * do a last minute check before queueing the request back.
+	 */
 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	is_abandoned = ureq->is_abandoned;
+	if (!is_abandoned)
+		list_add_tail(&req->list, &video->req_free);
 	spin_unlock_irqrestore(&video->req_lock, flags);

+	if (is_abandoned) {
+		uvcg_dbg(&video->uvc->func,
+			 "usb_request abandoned mid-processing - freeing.\n");
+		uvc_video_free_request(ureq, ep);
+		return;
+	}
+
 	if (uvc->state == UVC_STATE_STREAMING)
 		queue_work(video->async_wq, &video->pump);
 }
@@ -372,7 +400,6 @@ uvc_video_alloc_requests(struct uvc_video *video)
 	}

 	video->req_size = req_size;
-
 	return 0;

 error:
@@ -504,13 +531,80 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }

+/*
+ * Disable video stream. This ensures that any inflight usb requests are marked
+ * for clean up and all video buffers are dropped before returning.
+ */
+static void uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_buffer *buf, *tmp_buf;
+	struct uvc_request *ureq, *temp;
+	struct list_head buf_list; /* track in-flight video buffers */
+	struct usb_request *req;
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&buf_list);
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+
+	/**
+	 * acquiring req_lock here should prevent any more complete
+	 * callbacks from processing until we've abandoned the
+	 * requests that are still in-flight and let the complete
+	 * callback handle cleanup
+	 */
+	spin_lock_irqsave(&video->req_lock, flags);
+	/* abandon all usb requests */
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
+		ureq->is_abandoned = true;
+		if (ureq->last_buf) {
+			list_add(&ureq->last_buf->queue, &buf_list);
+			ureq->last_buf = NULL;
+		}
+		list_del_init(&ureq->list);
+	}
+	/*
+	 * re-add uvc_requests currently owned by the gadget to
+	 * video->ureqs to be deallocated. This effectively leaves
+	 * video->ureqs with the requests that we currently own.
+	 */
+	list_for_each_entry(req, &video->req_free, list) {
+		ureq = req->context;
+		list_add_tail(&ureq->list, &video->ureqs);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * drop abandoned uvc_buffers, as the completion handler
+	 * no longer will
+	 */
+	if (!list_empty(&buf_list)) {
+		spin_lock_irqsave(&video->queue.irqlock, flags);
+		list_for_each_entry_safe(buf, tmp_buf,
+						&buf_list, queue) {
+			video->queue.flags |= UVC_QUEUE_DROP_INCOMPLETE;
+			uvcg_complete_buffer(&video->queue, buf);
+			list_del(&buf->queue);
+		}
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	struct uvc_device *uvc = video->uvc;
-	struct uvc_request *ureq;
 	int ret;

 	if (video->ep == NULL) {
@@ -521,17 +615,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)

 	if (!enable) {
 		uvc->state = UVC_STATE_CONNECTED;
-
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
+		uvcg_video_disable(video);
 		return 0;
 	}

--
2.42.0.609.gbb76f46606-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-05  7:11     ` Greg Kroah-Hartman
@ 2023-10-05 18:09       ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-05 18:09 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael Grzeschik, Laurent Pinchart, Daniel Scally, jchowdhary,
	etalvala, linux-usb, linux-kernel



On 10/5/23 00:11, Greg Kroah-Hartman wrote:
> On Tue, Oct 03, 2023 at 04:16:00PM -0700, Avichal Rakesh wrote:
>> Thank you for testing the patch, Michael!
>>
>> On 10/3/23 04:09, Michael Grzeschik wrote:
>>> Hi
>>>
>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>> We have been seeing two main stability issues that uvc gadget driver
>>>> runs into when stopping streams:
>>>> 1. Attempting to queue usb_requests to a disabled usb_ep
>>>> 2. use-after-free issue for inflight usb_requests
>>>>
>>>> The three patches below fix the two issues above. Patch 1/3 fixes the
>>>> first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>>
>>>> Avichal Rakesh (3):
>>>>  usb: gadget: uvc: prevent use of disabled endpoint
>>>>  usb: gadget: uvc: Allocate uvc_requests one at a time
>>>>  usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>>
>>>> drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>> drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>> drivers/usb/gadget/function/uvc.h       |   6 +-
>>>> drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>> drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>> 5 files changed, 164 insertions(+), 65 deletions(-)
>>>
>>> These patches are not applying on gregkh/usb-testing since
>>> Greg did take my patches first. I have already rebased them.
>>
>> Ah, I didn't realize Greg had picked up your changes in his tree.
>> Rebased the patches in V2.
> 
> The "v2" series here is almost impossible to follow, sorry.
> 
> Please send it as a new thread, not as responses to the individual
> commits, how am I supposed to pick them up that way?
> 
> And make it v3 please.

Sent out v3 as a new thread. Sorry about that!

- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-05 10:14     ` Michael Grzeschik
@ 2023-10-05 18:30       ` Avichal Rakesh
  2023-10-05 22:05         ` Michael Grzeschik
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-05 18:30 UTC (permalink / raw)
  To: Michael Grzeschik, Laurent Pinchart
  Cc: Daniel Scally, Greg Kroah-Hartman, jchowdhary, etalvala,
	linux-usb, linux-kernel



On 10/5/23 03:14, Michael Grzeschik wrote:
> Hi Laurent
> 
> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>> > We have been seeing two main stability issues that uvc gadget driver
>>> > runs into when stopping streams:
>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>> >  2. use-after-free issue for inflight usb_requests
>>> >
>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>> >
>>> > Avichal Rakesh (3):
>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>> >
>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>
>>> These patches are not applying on gregkh/usb-testing since
>>> Greg did take my patches first. I have already rebased them.
>>
>> I think they got merged too soon :-( We could fix things on top, but
>> there's very little time to do so for v6.7.
> 
> Agreed. I was jumping from one workaround to another one, since this
> is not easy to fix in a proper way. And still after this long discussion
> with Avichal I don't think we are there yet.
> 
> 
> So far the first two patches from Avichal look legit. But the overall
> Use-After-Free fix is yet to be done properly.
> 
> The "abondoned" method he suggested is really bad to follow and will
> add too much complexity and will be hard to debug.
> 
> IMHO it should be possible to introduce two cleanup pathes.
> 
> One path would be in the uvc_cleanup_requests that will cleanup the
> requests that are actually not used in the controller and are registered
> in the req_free list.
> 
> The second path would be the complete functions that are being run
> from the controller and will ensure that the cleanup will really free
> the requests from the controller after they were consumed.
> 
> What do you think?

I am not sure I follow. Patch 3/3 does exactly what you say here.
There are two cleanup paths:
  1. uvcg_video_disable cleans up only the requests in req_free, and
  2. complete handler cleans up the in-flight requests.

The "abandoned" flag is simply to let the completion handler know
which requests to clean up and which ones to re-queue back to
the gadget driver.

The other "complications" are around making sure we can trust
the values in an inherently racey situation. The reasoning
can admittedly be difficult to follow at a glance, which incidentally
is why I went with a simple to prove timed wait in the past 
(https://lore.kernel.org/20230912041910.726442-3-arakesh@google.com).

I am not suggesting we go back to a timed wait, but please do look
at the patch and let me know which parts don't make sense, or are
difficult to understand. We can add more documentation about our
assumptions there, or if you have a way to do this that you
think is simpler to reason about, then please let me know and I'll
be more than happy to use that!

Regards,
Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-05 18:30       ` Avichal Rakesh
@ 2023-10-05 22:05         ` Michael Grzeschik
  2023-10-06 17:00           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-05 22:05 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5822 bytes --]

Hi Avichal,

On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>On 10/5/23 03:14, Michael Grzeschik wrote:
>> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>> > We have been seeing two main stability issues that uvc gadget driver
>>>> > runs into when stopping streams:
>>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>>> >  2. use-after-free issue for inflight usb_requests
>>>> >
>>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>> >
>>>> > Avichal Rakesh (3):
>>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>> >
>>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>>
>>>> These patches are not applying on gregkh/usb-testing since
>>>> Greg did take my patches first. I have already rebased them.
>>>
>>> I think they got merged too soon :-( We could fix things on top, but
>>> there's very little time to do so for v6.7.
>>
>> Agreed. I was jumping from one workaround to another one, since this
>> is not easy to fix in a proper way. And still after this long discussion
>> with Avichal I don't think we are there yet.
>>
>>
>> So far the first two patches from Avichal look legit. But the overall
>> Use-After-Free fix is yet to be done properly.
>>
>> The "abondoned" method he suggested is really bad to follow and will
>> add too much complexity and will be hard to debug.
>>
>> IMHO it should be possible to introduce two cleanup pathes.
>>
>> One path would be in the uvc_cleanup_requests that will cleanup the
>> requests that are actually not used in the controller and are registered
>> in the req_free list.
>>
>> The second path would be the complete functions that are being run
>> from the controller and will ensure that the cleanup will really free
>> the requests from the controller after they were consumed.
>>
>> What do you think?
>
>I am not sure I follow. Patch 3/3 does exactly what you say here.

Yes, it was just to summ up what the latest state of the idea was,
so Laurent does not read the whole thread in detail. Sorry for not
being clear enough about that.

>There are two cleanup paths:
>  1. uvcg_video_disable cleans up only the requests in req_free, and
>  2. complete handler cleans up the in-flight requests.
>
>The "abandoned" flag is simply to let the completion handler know
>which requests to clean up and which ones to re-queue back to
>the gadget driver.

What I don't get is, why in the case of shutdown there needs to
be something re-queued back to the gadget driver. There should not
need to be any sort of barrier flag for the requests. Just the
complete handler running past a barrier where it knows that the
whole device is stopped. So every call on complete should then clean
that exact request it is touching currently.

I don't know where the extra complexity comes from.

>The other "complications" are around making sure we can trust
>the values in an inherently racey situation. The reasoning
>can admittedly be difficult to follow at a glance, which incidentally
>is why I went with a simple to prove timed wait in the past
>(https://lore.kernel.org/20230912041910.726442-3-arakesh@google.com).
>
>I am not suggesting we go back to a timed wait, but please do look
>at the patch and let me know which parts don't make sense, or are
>difficult to understand. We can add more documentation about our
>assumptions there, or if you have a way to do this that you
>think is simpler to reason about, then please let me know and I'll
>be more than happy to use that!

I really try to spin my head around the idea of the is_abondoned flag
you are using. Unfortunatly for now I am out to debug the issues I see
with your series.

So I did try these patches you send. Yes the deadlock error is gone with
v3. But the linked list is still running into cases where
dwc3_gadget_giveback(complete) is touching requests that are already
freed.

[   61.408715] ------------[ cut here ]------------
[   61.413897] kernel BUG at lib/list_debug.c:56!
...
[   61.590762] Call trace:
[   61.596890]  __list_del_entry_valid+0xb8/0xe8
[   61.603408]  dwc3_gadget_giveback+0x3c/0x1b0
[   61.607594]  dwc3_remove_requests.part.0+0xcc/0x100
[   61.612948]  __dwc3_gadget_ep_disable+0xbc/0x1b8
[   61.621019]  dwc3_gadget_ep_disable+0x48/0x100
[   61.627925]  usb_ep_disable+0x3c/0x138
[   61.638230]  uvc_function_setup_continue+0x3c/0x60
[   61.645040]  uvc_v4l2_streamoff+0x5c/0x80
[   61.659812]  v4l_streamoff+0x40/0x60
[   61.668950]  __video_do_ioctl+0x344/0x420
[   61.679548]  video_usercopy+0x1d0/0x788
[   61.685677]  video_ioctl2+0x40/0x70
[   61.697439]  v4l2_ioctl+0x68/0xa0
[   61.709200]  __arm64_sys_ioctl+0x304/0xda0
[   61.720768]  invoke_syscall.constprop.0+0x70/0x130

Regards,
Michael

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-05 22:05         ` Michael Grzeschik
@ 2023-10-06 17:00           ` Avichal Rakesh
  2023-10-06 22:53             ` Michael Grzeschik
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-06 17:00 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel



On 10/5/23 15:05, Michael Grzeschik wrote:
> Hi Avichal,
> 
> On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>> On 10/5/23 03:14, Michael Grzeschik wrote:
>>> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>>>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>>> > We have been seeing two main stability issues that uvc gadget driver
>>>>> > runs into when stopping streams:
>>>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>>>> >  2. use-after-free issue for inflight usb_requests
>>>>> >
>>>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>>> >
>>>>> > Avichal Rakesh (3):
>>>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>>> >
>>>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>>>
>>>>> These patches are not applying on gregkh/usb-testing since
>>>>> Greg did take my patches first. I have already rebased them.
>>>>
>>>> I think they got merged too soon :-( We could fix things on top, but
>>>> there's very little time to do so for v6.7.
>>>
>>> Agreed. I was jumping from one workaround to another one, since this
>>> is not easy to fix in a proper way. And still after this long discussion
>>> with Avichal I don't think we are there yet.
>>>
>>>
>>> So far the first two patches from Avichal look legit. But the overall
>>> Use-After-Free fix is yet to be done properly.
>>>
>>> The "abondoned" method he suggested is really bad to follow and will
>>> add too much complexity and will be hard to debug.
>>>
>>> IMHO it should be possible to introduce two cleanup pathes.
>>>
>>> One path would be in the uvc_cleanup_requests that will cleanup the
>>> requests that are actually not used in the controller and are registered
>>> in the req_free list.
>>>
>>> The second path would be the complete functions that are being run
>>> from the controller and will ensure that the cleanup will really free
>>> the requests from the controller after they were consumed.
>>>
>>> What do you think?
>>
>> I am not sure I follow. Patch 3/3 does exactly what you say here.
> 
> Yes, it was just to summ up what the latest state of the idea was,
> so Laurent does not read the whole thread in detail. Sorry for not
> being clear enough about that.

Whoops! Sorry about the misunderstanding!

> 
>> There are two cleanup paths:
>>  1. uvcg_video_disable cleans up only the requests in req_free, and
>>  2. complete handler cleans up the in-flight requests.
>>
>> The "abandoned" flag is simply to let the completion handler know
>> which requests to clean up and which ones to re-queue back to
>> the gadget driver.
> 
> What I don't get is, why in the case of shutdown there needs to
> be something re-queued back to the gadget driver. There should not
> need to be any sort of barrier flag for the requests. Just the
> complete handler running past a barrier where it knows that the
> whole device is stopped. So every call on complete should then clean
> that exact request it is touching currently.
> 
> I don't know where the extra complexity comes from.

A lot of this complexity comes from assuming a back to back
STREAMOFF -> STREAMON sequence is possible where the gadget driver
doesn't have the time to clean up all in-flight usb_requests.
However, looking through the usb gadget APIs again, and it 
looks like  usb_ep_disable enforces that all requests will 
be sent back to the gadget driver before it returns. 

So you're right:
With Patch 1/3 in place, I think we can just guard on uvc->state 
alone, because control requests are blocked until usb_ep_disable
is finished anyway. I'll upload v4 with the "is_abandoned"
flag removed and the checks simplified once I've verified the
fix locally.

That should also remove any bookkeeping issues that may have 
triggered the stack below.

Regards,
Avi.

> 
>> The other "complications" are around making sure we can trust
>> the values in an inherently racey situation. The reasoning
>> can admittedly be difficult to follow at a glance, which incidentally
>> is why I went with a simple to prove timed wait in the past
>> (https://lore.kernel.org/20230912041910.726442-3-arakesh@google.com).
>>
>> I am not suggesting we go back to a timed wait, but please do look
>> at the patch and let me know which parts don't make sense, or are
>> difficult to understand. We can add more documentation about our
>> assumptions there, or if you have a way to do this that you
>> think is simpler to reason about, then please let me know and I'll
>> be more than happy to use that!
> 
> I really try to spin my head around the idea of the is_abondoned flag
> you are using. Unfortunatly for now I am out to debug the issues I see
> with your series.
> 
> So I did try these patches you send. Yes the deadlock error is gone with
> v3. But the linked list is still running into cases where
> dwc3_gadget_giveback(complete) is touching requests that are already
> freed.
> 
> [   61.408715] ------------[ cut here ]------------
> [   61.413897] kernel BUG at lib/list_debug.c:56!
> ...
> [   61.590762] Call trace:
> [   61.596890]  __list_del_entry_valid+0xb8/0xe8
> [   61.603408]  dwc3_gadget_giveback+0x3c/0x1b0
> [   61.607594]  dwc3_remove_requests.part.0+0xcc/0x100
> [   61.612948]  __dwc3_gadget_ep_disable+0xbc/0x1b8
> [   61.621019]  dwc3_gadget_ep_disable+0x48/0x100
> [   61.627925]  usb_ep_disable+0x3c/0x138
> [   61.638230]  uvc_function_setup_continue+0x3c/0x60
> [   61.645040]  uvc_v4l2_streamoff+0x5c/0x80
> [   61.659812]  v4l_streamoff+0x40/0x60
> [   61.668950]  __video_do_ioctl+0x344/0x420
> [   61.679548]  video_usercopy+0x1d0/0x788
> [   61.685677]  video_ioctl2+0x40/0x70
> [   61.697439]  v4l2_ioctl+0x68/0xa0
> [   61.709200]  __arm64_sys_ioctl+0x304/0xda0
> [   61.720768]  invoke_syscall.constprop.0+0x70/0x130
> 


^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-05 18:08   ` [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-05 18:08   ` [PATCH v3 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-06 22:04   ` Michael Grzeschik
  2 siblings, 0 replies; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-06 22:04 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, gregkh, laurent.pinchart, etalvala, jchowdhary,
	linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 5884 bytes --]

On Thu, Oct 05, 2023 at 11:08:12AM -0700, Avichal Rakesh wrote:
>Currently the set_alt callback immediately disables the endpoint and queues
>the v4l2 streamoff event. However, as the streamoff event is processed
>asynchronously, it is possible that the video_pump thread attempts to queue
>requests to an already disabled endpoint.
>
>This change moves disabling usb endpoint to the end of streamoff event
>callback. To be consistent with the actual streaming state, uvc->state
>is now toggled between CONNECTED and STREAMING from the v4l2 event
>callback only.
>
>Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
>Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
>Signed-off-by: Avichal Rakesh <arakesh@google.com>
>---
>v1 -> v2: Rebased to ToT and reworded commit message.
>v2 -> v3: Fix email threading goof-up
>
> drivers/usb/gadget/function/f_uvc.c    | 11 +++++------
> drivers/usb/gadget/function/f_uvc.h    |  2 +-
> drivers/usb/gadget/function/uvc.h      |  2 +-
> drivers/usb/gadget/function/uvc_v4l2.c | 21 ++++++++++++++++++---
> 4 files changed, 25 insertions(+), 11 deletions(-)
>
>diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
>index faa398109431..75c9f9a3f884 100644
>--- a/drivers/usb/gadget/function/f_uvc.c
>+++ b/drivers/usb/gadget/function/f_uvc.c
>@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
> 	return 0;
> }
>
>-void uvc_function_setup_continue(struct uvc_device *uvc)
>+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
> {
> 	struct usb_composite_dev *cdev = uvc->func.config->cdev;
>
>+	if (disable_ep && uvc->video.ep) {
>+		usb_ep_disable(uvc->video.ep);
>+	}

Could you drop the extra braces and add one spare line here.

> 	usb_composite_setup_continue(cdev);
> }
>
>@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
> 		if (uvc->state != UVC_STATE_STREAMING)
> 			return 0;
>
>-		if (uvc->video.ep)
>-			usb_ep_disable(uvc->video.ep);
>-
> 		memset(&v4l2_event, 0, sizeof(v4l2_event));
> 		v4l2_event.type = UVC_EVENT_STREAMOFF;
> 		v4l2_event_queue(&uvc->vdev, &v4l2_event);
>
>-		uvc->state = UVC_STATE_CONNECTED;
>-		return 0;
>+		return USB_GADGET_DELAYED_STATUS;
>
> 	case 1:
> 		if (uvc->state != UVC_STATE_CONNECTED)
>diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
>index 1db972d4beeb..e7f9f13f14dc 100644
>--- a/drivers/usb/gadget/function/f_uvc.h
>+++ b/drivers/usb/gadget/function/f_uvc.h
>@@ -11,7 +11,7 @@
>
> struct uvc_device;
>
>-void uvc_function_setup_continue(struct uvc_device *uvc);
>+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);
>
> void uvc_function_connect(struct uvc_device *uvc);
>
>diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>index 6751de8b63ad..989bc6b4e93d 100644
>--- a/drivers/usb/gadget/function/uvc.h
>+++ b/drivers/usb/gadget/function/uvc.h
>@@ -177,7 +177,7 @@ struct uvc_file_handle {
>  * Functions
>  */
>
>-extern void uvc_function_setup_continue(struct uvc_device *uvc);
>+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
> extern void uvc_function_connect(struct uvc_device *uvc);
> extern void uvc_function_disconnect(struct uvc_device *uvc);
>
>diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>index 3f0a9795c0d4..3d3469883ed0 100644
>--- a/drivers/usb/gadget/function/uvc_v4l2.c
>+++ b/drivers/usb/gadget/function/uvc_v4l2.c
>@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
> 	 * Complete the alternate setting selection setup phase now that
> 	 * userspace is ready to provide video frames.
> 	 */
>-	uvc_function_setup_continue(uvc);
>+	uvc_function_setup_continue(uvc, 0);
> 	uvc->state = UVC_STATE_STREAMING;
>
> 	return 0;
>@@ -463,11 +463,19 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
> 	struct video_device *vdev = video_devdata(file);
> 	struct uvc_device *uvc = video_get_drvdata(vdev);
> 	struct uvc_video *video = &uvc->video;
>+	int ret = 0;
>
> 	if (type != video->queue.queue.type)
> 		return -EINVAL;
>
>-	return uvcg_video_enable(video, 0);
>+	uvc->state = UVC_STATE_CONNECTED;
>+	ret = uvcg_video_enable(video, 0);
>+	if (ret < 0) {
>+		return ret;
>+	}

Please drop those extra braces.

>+
>+	uvc_function_setup_continue(uvc, 1);
>+	return 0;
> }
>
> static int
>@@ -500,6 +508,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
> static void uvc_v4l2_disable(struct uvc_device *uvc)
> {
> 	uvc_function_disconnect(uvc);
>+	if (uvc->state == UVC_STATE_STREAMING) {
>+		/*
>+		 * Drop uvc->state to CONNECTED if it was streaming before.
>+		 * This ensures that the usb_requests are no longer queued
>+		 * to the controller.
>+		 */
>+		uvc->state = UVC_STATE_CONNECTED;
>+	}

Could you write the comment above the check
and also remove the extra braces.

> 	uvcg_video_enable(&uvc->video, 0);
> 	uvcg_free_buffers(&uvc->video.queue);
> 	uvc->func_connected = false;
>@@ -647,4 +663,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
> 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
> #endif
> };
>-
>--
>2.42.0.609.gbb76f46606-goog


With this you can add my:

Reviewed-by: <m.grzeschik@pengutronix.de>

Thanks

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-05 18:08   ` [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-06 22:11     ` Michael Grzeschik
  0 siblings, 0 replies; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-06 22:11 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, gregkh, laurent.pinchart, etalvala, jchowdhary,
	linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 7628 bytes --]

On Thu, Oct 05, 2023 at 11:08:13AM -0700, Avichal Rakesh wrote:
>Currently, the uvc gadget driver allocates all uvc_requests as one array
>and deallocates them all when the video stream stops. This includes
>de-allocating all the usb_requests associated with those uvc_requests.
>This can lead to use-after-free issues if any of those de-allocated
>usb_requests were still owned by the usb controller.
>
>This patch is 1 of 2 patches addressing the use-after-free issue.
>Instead of bulk allocating all uvc_requests as an array, this patch
>allocates uvc_requests one at a time, which should allows for similar
>granularity when deallocating the uvc_requests. This patch has no
>functional changes other than allocating each uvc_request separately,
>and similarly freeing each of them separately.
>
>Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>Suggested-by: Michael Grzeschik <mgr@pengutronix.de>

Please use my full address here. m.grzeschik@pengutronix.de

>Signed-off-by: Avichal Rakesh <arakesh@google.com>
>---
>v1 -> v2: Rebased to ToT
>V2 -> V3: Fix email threading goof-up
>
> drivers/usb/gadget/function/uvc.h       |  3 +-
> drivers/usb/gadget/function/uvc_video.c | 90 ++++++++++++++-----------
> 2 files changed, 51 insertions(+), 42 deletions(-)
>
>diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>index 989bc6b4e93d..993694da0bbc 100644
>--- a/drivers/usb/gadget/function/uvc.h
>+++ b/drivers/usb/gadget/function/uvc.h
>@@ -81,6 +81,7 @@ struct uvc_request {
> 	struct sg_table sgt;
> 	u8 header[UVCG_REQUEST_HEADER_LEN];
> 	struct uvc_buffer *last_buf;
>+	struct list_head list;
> };
>
> struct uvc_video {
>@@ -102,7 +103,7 @@ struct uvc_video {
>
> 	/* Requests */
> 	unsigned int req_size;
>-	struct uvc_request *ureq;
>+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
> 	struct list_head req_free;
> 	spinlock_t req_lock;
>
>diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>index 97d875c27dcf..3c4d286d81c0 100644
>--- a/drivers/usb/gadget/function/uvc_video.c
>+++ b/drivers/usb/gadget/function/uvc_video.c
>@@ -227,6 +227,23 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>  * Request handling
>  */
>
>+static void uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>+{
>+	sg_free_table(&ureq->sgt);
>+	if (ureq->req && ep) {
>+		usb_ep_free_request(ep, ureq->req);
>+		ureq->req = NULL;
>+	}
>+
>+	kfree(ureq->req_buffer);
>+	ureq->req_buffer = NULL;
>+
>+	if (!list_empty(&ureq->list))
>+		list_del_init(&ureq->list);
>+
>+	kfree(ureq);
>+}
>+
> static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
> {
> 	int ret;
>@@ -299,27 +316,13 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
> static int
> uvc_video_free_requests(struct uvc_video *video)
> {
>-	unsigned int i;
>-
>-	if (video->ureq) {
>-		for (i = 0; i < video->uvc_num_requests; ++i) {
>-			sg_free_table(&video->ureq[i].sgt);
>-
>-			if (video->ureq[i].req) {
>-				usb_ep_free_request(video->ep, video->ureq[i].req);
>-				video->ureq[i].req = NULL;
>-			}
>+	struct uvc_request *ureq, *temp;
>
>-			if (video->ureq[i].req_buffer) {
>-				kfree(video->ureq[i].req_buffer);
>-				video->ureq[i].req_buffer = NULL;
>-			}
>-		}
>-
>-		kfree(video->ureq);
>-		video->ureq = NULL;
>+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list) {
>+		uvc_video_free_request(ureq, video->ep);
> 	}

Please drop the extra braces.

>
>+	INIT_LIST_HEAD(&video->ureqs);
> 	INIT_LIST_HEAD(&video->req_free);
> 	video->req_size = 0;
> 	return 0;
>@@ -328,6 +331,7 @@ uvc_video_free_requests(struct uvc_video *video)
> static int
> uvc_video_alloc_requests(struct uvc_video *video)
> {
>+	struct uvc_request *ureq;
> 	unsigned int req_size;
> 	unsigned int i;
> 	int ret = -ENOMEM;
>@@ -338,29 +342,31 @@ uvc_video_alloc_requests(struct uvc_video *video)
> 		 * max_t(unsigned int, video->ep->maxburst, 1)
> 		 * (video->ep->mult);
>
>-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
>-	if (video->ureq == NULL)
>-		return -ENOMEM;
>-
>-	for (i = 0; i < video->uvc_num_requests; ++i) {
>-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
>-		if (video->ureq[i].req_buffer == NULL)
>+	INIT_LIST_HEAD(&video->ureqs);
>+	for (i = 0; i < video->uvc_num_requests; i++) {
>+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
>+		if (ureq == NULL)
> 			goto error;
>+		INIT_LIST_HEAD(&ureq->list);
>+		list_add_tail(&ureq->list, &video->ureqs);
>+	}
>
>-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>-		if (video->ureq[i].req == NULL)
>+	list_for_each_entry(ureq, &video->ureqs, list) {
>+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
>+		if (ureq->req_buffer == NULL)
> 			goto error;
>-

Instead of looping twice. Wouldn't it be possible to fill the
just created ureq from the for loop here directly.

>-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
>-		video->ureq[i].req->length = 0;
>-		video->ureq[i].req->complete = uvc_video_complete;
>-		video->ureq[i].req->context = &video->ureq[i];
>-		video->ureq[i].video = video;
>-		video->ureq[i].last_buf = NULL;
>-
>-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
>+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>+		if (ureq->req == NULL)
>+			goto error;
>+		ureq->req->buf = ureq->req_buffer;
>+		ureq->req->length = 0;
>+		ureq->req->complete = uvc_video_complete;
>+		ureq->req->context = ureq;
>+		ureq->video = video;
>+		ureq->last_buf = NULL;

Add an extra spare line just like in the code before.

>+		list_add_tail(&ureq->req->list, &video->req_free);
> 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
>-		sg_alloc_table(&video->ureq[i].sgt,
>+		sg_alloc_table(&ureq->sgt,
> 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
> 					    PAGE_SIZE) + 2, GFP_KERNEL);
> 	}
>@@ -504,7 +510,7 @@ static void uvcg_video_pump(struct work_struct *work)
> int uvcg_video_enable(struct uvc_video *video, int enable)
> {
> 	struct uvc_device *uvc = video->uvc;
>-	unsigned int i;
>+	struct uvc_request *ureq;
> 	int ret;
>
> 	if (video->ep == NULL) {
>@@ -519,9 +525,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
> 		cancel_work_sync(&video->pump);
> 		uvcg_queue_cancel(&video->queue, 0);
>
>-		for (i = 0; i < video->uvc_num_requests; ++i)
>-			if (video->ureq && video->ureq[i].req)
>-				usb_ep_dequeue(video->ep, video->ureq[i].req);
>+		list_for_each_entry(ureq, &video->ureqs, list) {
>+			if (ureq->req)
>+				usb_ep_dequeue(video->ep, ureq->req);
>+		}
>
> 		uvc_video_free_requests(video);
> 		uvcg_queue_enable(&video->queue, 0);
>@@ -555,6 +562,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>  */
> int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
> {
>+	INIT_LIST_HEAD(&video->ureqs);
> 	INIT_LIST_HEAD(&video->req_free);
> 	spin_lock_init(&video->req_lock);
> 	INIT_WORK(&video->pump, uvcg_video_pump);
>--
>2.42.0.609.gbb76f46606-goog
>

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-06 17:00           ` Avichal Rakesh
@ 2023-10-06 22:53             ` Michael Grzeschik
  2023-10-06 23:48               ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-06 22:53 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 7265 bytes --]

On Fri, Oct 06, 2023 at 10:00:11AM -0700, Avichal Rakesh wrote:
>
>
>On 10/5/23 15:05, Michael Grzeschik wrote:
>> Hi Avichal,
>>
>> On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>>> On 10/5/23 03:14, Michael Grzeschik wrote:
>>>> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>>>>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>>>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>>>> > We have been seeing two main stability issues that uvc gadget driver
>>>>>> > runs into when stopping streams:
>>>>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>>>>> >  2. use-after-free issue for inflight usb_requests
>>>>>> >
>>>>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>>>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>>>> >
>>>>>> > Avichal Rakesh (3):
>>>>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>>>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>>>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>>>> >
>>>>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>>>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>>>>
>>>>>> These patches are not applying on gregkh/usb-testing since
>>>>>> Greg did take my patches first. I have already rebased them.
>>>>>
>>>>> I think they got merged too soon :-( We could fix things on top, but
>>>>> there's very little time to do so for v6.7.
>>>>
>>>> Agreed. I was jumping from one workaround to another one, since this
>>>> is not easy to fix in a proper way. And still after this long discussion
>>>> with Avichal I don't think we are there yet.
>>>>
>>>>
>>>> So far the first two patches from Avichal look legit. But the overall
>>>> Use-After-Free fix is yet to be done properly.
>>>>
>>>> The "abondoned" method he suggested is really bad to follow and will
>>>> add too much complexity and will be hard to debug.
>>>>
>>>> IMHO it should be possible to introduce two cleanup pathes.
>>>>
>>>> One path would be in the uvc_cleanup_requests that will cleanup the
>>>> requests that are actually not used in the controller and are registered
>>>> in the req_free list.
>>>>
>>>> The second path would be the complete functions that are being run
>>>> from the controller and will ensure that the cleanup will really free
>>>> the requests from the controller after they were consumed.
>>>>
>>>> What do you think?
>>>
>>> I am not sure I follow. Patch 3/3 does exactly what you say here.
>>
>> Yes, it was just to summ up what the latest state of the idea was,
>> so Laurent does not read the whole thread in detail. Sorry for not
>> being clear enough about that.
>
>Whoops! Sorry about the misunderstanding!
>
>>
>>> There are two cleanup paths:
>>>  1. uvcg_video_disable cleans up only the requests in req_free, and
>>>  2. complete handler cleans up the in-flight requests.
>>>
>>> The "abandoned" flag is simply to let the completion handler know
>>> which requests to clean up and which ones to re-queue back to
>>> the gadget driver.
>>
>> What I don't get is, why in the case of shutdown there needs to
>> be something re-queued back to the gadget driver. There should not
>> need to be any sort of barrier flag for the requests. Just the
>> complete handler running past a barrier where it knows that the
>> whole device is stopped. So every call on complete should then clean
>> that exact request it is touching currently.
>>
>> I don't know where the extra complexity comes from.
>
>A lot of this complexity comes from assuming a back to back
>STREAMOFF -> STREAMON sequence is possible where the gadget driver
>doesn't have the time to clean up all in-flight usb_requests.
>However, looking through the usb gadget APIs again, and it
>looks like  usb_ep_disable enforces that all requests will
>be sent back to the gadget driver before it returns.

Great!

>So you're right:
>With Patch 1/3 in place, I think we can just guard on uvc->state
>alone, because control requests are blocked until usb_ep_disable
>is finished anyway. I'll upload v4 with the "is_abandoned"
>flag removed and the checks simplified once I've verified the
>fix locally.
>
>That should also remove any bookkeeping issues that may have
>triggered the stack below.

I am currious if we should handle -ECONNRESET and -ESHUTDOWN in more
detail in the completion handler and make sure that the request will not
be added into the req_free list from there.

Will review your v4 then.

>>> The other "complications" are around making sure we can trust
>>> the values in an inherently racey situation. The reasoning
>>> can admittedly be difficult to follow at a glance, which incidentally
>>> is why I went with a simple to prove timed wait in the past
>>> (https://lore.kernel.org/20230912041910.726442-3-arakesh@google.com).
>>>
>>> I am not suggesting we go back to a timed wait, but please do look
>>> at the patch and let me know which parts don't make sense, or are
>>> difficult to understand. We can add more documentation about our
>>> assumptions there, or if you have a way to do this that you
>>> think is simpler to reason about, then please let me know and I'll
>>> be more than happy to use that!
>>
>> I really try to spin my head around the idea of the is_abondoned flag
>> you are using. Unfortunatly for now I am out to debug the issues I see
>> with your series.
>>
>> So I did try these patches you send. Yes the deadlock error is gone with
>> v3. But the linked list is still running into cases where
>> dwc3_gadget_giveback(complete) is touching requests that are already
>> freed.
>>
>> [   61.408715] ------------[ cut here ]------------
>> [   61.413897] kernel BUG at lib/list_debug.c:56!
>> ...
>> [   61.590762] Call trace:
>> [   61.596890]  __list_del_entry_valid+0xb8/0xe8
>> [   61.603408]  dwc3_gadget_giveback+0x3c/0x1b0
>> [   61.607594]  dwc3_remove_requests.part.0+0xcc/0x100
>> [   61.612948]  __dwc3_gadget_ep_disable+0xbc/0x1b8
>> [   61.621019]  dwc3_gadget_ep_disable+0x48/0x100
>> [   61.627925]  usb_ep_disable+0x3c/0x138
>> [   61.638230]  uvc_function_setup_continue+0x3c/0x60
>> [   61.645040]  uvc_v4l2_streamoff+0x5c/0x80
>> [   61.659812]  v4l_streamoff+0x40/0x60
>> [   61.668950]  __video_do_ioctl+0x344/0x420
>> [   61.679548]  video_usercopy+0x1d0/0x788
>> [   61.685677]  video_ioctl2+0x40/0x70
>> [   61.697439]  v4l2_ioctl+0x68/0xa0
>> [   61.709200]  __arm64_sys_ioctl+0x304/0xda0
>> [   61.720768]  invoke_syscall.constprop.0+0x70/0x130
>>
>
>

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-06 22:53             ` Michael Grzeschik
@ 2023-10-06 23:48               ` Avichal Rakesh
  2023-10-08 19:48                 ` Michael Grzeschik
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-06 23:48 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel



On 10/6/23 15:53, Michael Grzeschik wrote:
> On Fri, Oct 06, 2023 at 10:00:11AM -0700, Avichal Rakesh wrote:
>>
>>
>> On 10/5/23 15:05, Michael Grzeschik wrote:
>>> Hi Avichal,
>>>
>>> On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>>>> On 10/5/23 03:14, Michael Grzeschik wrote:
>>>>> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>>>>>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>>>>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>>>>> > We have been seeing two main stability issues that uvc gadget driver
>>>>>>> > runs into when stopping streams:
>>>>>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>>>>>> >  2. use-after-free issue for inflight usb_requests
>>>>>>> >
>>>>>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>>>>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>>>>> >
>>>>>>> > Avichal Rakesh (3):
>>>>>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>>>>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>>>>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>>>>> >
>>>>>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>>>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>>>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>>>>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>>>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>>>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>>>>>
>>>>>>> These patches are not applying on gregkh/usb-testing since
>>>>>>> Greg did take my patches first. I have already rebased them.
>>>>>>
>>>>>> I think they got merged too soon :-( We could fix things on top, but
>>>>>> there's very little time to do so for v6.7.
>>>>>
>>>>> Agreed. I was jumping from one workaround to another one, since this
>>>>> is not easy to fix in a proper way. And still after this long discussion
>>>>> with Avichal I don't think we are there yet.
>>>>>
>>>>>
>>>>> So far the first two patches from Avichal look legit. But the overall
>>>>> Use-After-Free fix is yet to be done properly.
>>>>>
>>>>> The "abondoned" method he suggested is really bad to follow and will
>>>>> add too much complexity and will be hard to debug.
>>>>>
>>>>> IMHO it should be possible to introduce two cleanup pathes.
>>>>>
>>>>> One path would be in the uvc_cleanup_requests that will cleanup the
>>>>> requests that are actually not used in the controller and are registered
>>>>> in the req_free list.
>>>>>
>>>>> The second path would be the complete functions that are being run
>>>>> from the controller and will ensure that the cleanup will really free
>>>>> the requests from the controller after they were consumed.
>>>>>
>>>>> What do you think?
>>>>
>>>> I am not sure I follow. Patch 3/3 does exactly what you say here.
>>>
>>> Yes, it was just to summ up what the latest state of the idea was,
>>> so Laurent does not read the whole thread in detail. Sorry for not
>>> being clear enough about that.
>>
>> Whoops! Sorry about the misunderstanding!
>>
>>>
>>>> There are two cleanup paths:
>>>>  1. uvcg_video_disable cleans up only the requests in req_free, and
>>>>  2. complete handler cleans up the in-flight requests.
>>>>
>>>> The "abandoned" flag is simply to let the completion handler know
>>>> which requests to clean up and which ones to re-queue back to
>>>> the gadget driver.
>>>
>>> What I don't get is, why in the case of shutdown there needs to
>>> be something re-queued back to the gadget driver. There should not
>>> need to be any sort of barrier flag for the requests. Just the
>>> complete handler running past a barrier where it knows that the
>>> whole device is stopped. So every call on complete should then clean
>>> that exact request it is touching currently.
>>>
>>> I don't know where the extra complexity comes from.
>>
>> A lot of this complexity comes from assuming a back to back
>> STREAMOFF -> STREAMON sequence is possible where the gadget driver
>> doesn't have the time to clean up all in-flight usb_requests.
>> However, looking through the usb gadget APIs again, and it
>> looks like  usb_ep_disable enforces that all requests will
>> be sent back to the gadget driver before it returns.
> 
> Great!

Uhh...apologies, I will have to take this back. I've been
trying to use uvc->state as the condition for when completion
handler should clean up usb_requests, and I cannot figure 
out a way to do so cleanly.

The fundamental problem with using uvc->state is that it is
not protected by any locks. So there is no real way to
assert that its value has not changed between reading
uvc->state and acting on it.

Naively we can write something like the following in the 
completion handler:

void uvc_video_complete(...) {
    if (uvc->state != UVC_EVENT_STREAMING) {
        usb_ep_free_request(....);
    } else { 
        // handle usb_request normally
    }
}

But without any locks, there are no guarantees that 
uvc->state didn't mutate immediately after the if 
condition was checked, and the complete handler is 
handling a request that it should've freed instead
or vice-versa. This argument would hold for any logic 
we guard with uvc->state, making uvc->state effectively 
useless as a check for freeing memory.

We can work around it by either
1. Locking uvc->state with some driver level lock
   to ensure that we can trust the value of uvc->state
   at least for a little while, or
2. Using some other barrier condition that is protected by 
   another lock

If we go with (1), we'd have to add a lock around every
and every write to uvc->state, which isn't terrible, but
would require more testing to ensure that it doesn't
create any new deadlocks.

For (2), with the realization that usb_ep_disable flushes 
all requests, we can add a barrier in uvc_video, protected by 
req_lock. That should simplify the logic a little bit and 
will hopefully be easier to reason about.

I could of course be missing a simpler solution here,
and am happy to be wrong. So please let me know if you 
have any other ideas on how to guarantee such a check. 


> 
>> So you're right:
>> With Patch 1/3 in place, I think we can just guard on uvc->state
>> alone, because control requests are blocked until usb_ep_disable
>> is finished anyway. I'll upload v4 with the "is_abandoned"
>> flag removed and the checks simplified once I've verified the
>> fix locally.
>>
>> That should also remove any bookkeeping issues that may have
>> triggered the stack below.
> 
> I am currious if we should handle -ECONNRESET and -ESHUTDOWN in more
> detail in the completion handler and make sure that the request will not
> be added into the req_free list from there.
> 
> Will review your v4 then.

Appreciate the reviews, thank you!

> 
>>>> The other "complications" are around making sure we can trust
>>>> the values in an inherently racey situation. The reasoning
>>>> can admittedly be difficult to follow at a glance, which incidentally
>>>> is why I went with a simple to prove timed wait in the past
>>>> (https://lore.kernel.org/20230912041910.726442-3-arakesh@google.com).
>>>>
>>>> I am not suggesting we go back to a timed wait, but please do look
>>>> at the patch and let me know which parts don't make sense, or are
>>>> difficult to understand. We can add more documentation about our
>>>> assumptions there, or if you have a way to do this that you
>>>> think is simpler to reason about, then please let me know and I'll
>>>> be more than happy to use that!
>>>
>>> I really try to spin my head around the idea of the is_abondoned flag
>>> you are using. Unfortunatly for now I am out to debug the issues I see
>>> with your series.
>>>
>>> So I did try these patches you send. Yes the deadlock error is gone with
>>> v3. But the linked list is still running into cases where
>>> dwc3_gadget_giveback(complete) is touching requests that are already
>>> freed.
>>>
>>> [   61.408715] ------------[ cut here ]------------
>>> [   61.413897] kernel BUG at lib/list_debug.c:56!
>>> ...
>>> [   61.590762] Call trace:
>>> [   61.596890]  __list_del_entry_valid+0xb8/0xe8
>>> [   61.603408]  dwc3_gadget_giveback+0x3c/0x1b0
>>> [   61.607594]  dwc3_remove_requests.part.0+0xcc/0x100
>>> [   61.612948]  __dwc3_gadget_ep_disable+0xbc/0x1b8
>>> [   61.621019]  dwc3_gadget_ep_disable+0x48/0x100
>>> [   61.627925]  usb_ep_disable+0x3c/0x138
>>> [   61.638230]  uvc_function_setup_continue+0x3c/0x60
>>> [   61.645040]  uvc_v4l2_streamoff+0x5c/0x80
>>> [   61.659812]  v4l_streamoff+0x40/0x60
>>> [   61.668950]  __video_do_ioctl+0x344/0x420
>>> [   61.679548]  video_usercopy+0x1d0/0x788
>>> [   61.685677]  video_ioctl2+0x40/0x70
>>> [   61.697439]  v4l2_ioctl+0x68/0xa0
>>> [   61.709200]  __arm64_sys_ioctl+0x304/0xda0
>>> [   61.720768]  invoke_syscall.constprop.0+0x70/0x130

Just to confirm: this stack was with all 3 patches applied? 
Want to make sure this won't happen with v4.



Regards,
Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-06 23:48               ` Avichal Rakesh
@ 2023-10-08 19:48                 ` Michael Grzeschik
  2023-10-12  0:33                   ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-08 19:48 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 7229 bytes --]

On Fri, Oct 06, 2023 at 04:48:19PM -0700, Avichal Rakesh wrote:
>On 10/6/23 15:53, Michael Grzeschik wrote:
>> On Fri, Oct 06, 2023 at 10:00:11AM -0700, Avichal Rakesh wrote:
>>>
>>>
>>> On 10/5/23 15:05, Michael Grzeschik wrote:
>>>> Hi Avichal,
>>>>
>>>> On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>>>>> On 10/5/23 03:14, Michael Grzeschik wrote:
>>>>>> On Thu, Oct 05, 2023 at 11:23:27AM +0300, Laurent Pinchart wrote:
>>>>>>> On Tue, Oct 03, 2023 at 01:09:06PM +0200, Michael Grzeschik wrote:
>>>>>>>> On Sat, Sep 30, 2023 at 11:48:18AM -0700, Avichal Rakesh wrote:
>>>>>>>> > We have been seeing two main stability issues that uvc gadget driver
>>>>>>>> > runs into when stopping streams:
>>>>>>>> >  1. Attempting to queue usb_requests to a disabled usb_ep
>>>>>>>> >  2. use-after-free issue for inflight usb_requests
>>>>>>>> >
>>>>>>>> > The three patches below fix the two issues above. Patch 1/3 fixes the
>>>>>>>> > first issue, and Patch 2/3 and 3/3 fix the second issue.
>>>>>>>> >
>>>>>>>> > Avichal Rakesh (3):
>>>>>>>> >   usb: gadget: uvc: prevent use of disabled endpoint
>>>>>>>> >   usb: gadget: uvc: Allocate uvc_requests one at a time
>>>>>>>> >   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
>>>>>>>> >
>>>>>>>> > drivers/usb/gadget/function/f_uvc.c     |  11 +-
>>>>>>>> > drivers/usb/gadget/function/f_uvc.h     |   2 +-
>>>>>>>> > drivers/usb/gadget/function/uvc.h       |   6 +-
>>>>>>>> > drivers/usb/gadget/function/uvc_v4l2.c  |  21 ++-
>>>>>>>> > drivers/usb/gadget/function/uvc_video.c | 189 +++++++++++++++++-------
>>>>>>>> > 5 files changed, 164 insertions(+), 65 deletions(-)
>>>>>>>>
>>>>>>>> These patches are not applying on gregkh/usb-testing since
>>>>>>>> Greg did take my patches first. I have already rebased them.
>>>>>>>
>>>>>>> I think they got merged too soon :-( We could fix things on top, but
>>>>>>> there's very little time to do so for v6.7.
>>>>>>
>>>>>> Agreed. I was jumping from one workaround to another one, since this
>>>>>> is not easy to fix in a proper way. And still after this long discussion
>>>>>> with Avichal I don't think we are there yet.
>>>>>>
>>>>>>
>>>>>> So far the first two patches from Avichal look legit. But the overall
>>>>>> Use-After-Free fix is yet to be done properly.
>>>>>>
>>>>>> The "abondoned" method he suggested is really bad to follow and will
>>>>>> add too much complexity and will be hard to debug.
>>>>>>
>>>>>> IMHO it should be possible to introduce two cleanup pathes.
>>>>>>
>>>>>> One path would be in the uvc_cleanup_requests that will cleanup the
>>>>>> requests that are actually not used in the controller and are registered
>>>>>> in the req_free list.
>>>>>>
>>>>>> The second path would be the complete functions that are being run
>>>>>> from the controller and will ensure that the cleanup will really free
>>>>>> the requests from the controller after they were consumed.
>>>>>>
>>>>>> What do you think?
>>>>>
>>>>> I am not sure I follow. Patch 3/3 does exactly what you say here.
>>>>
>>>> Yes, it was just to summ up what the latest state of the idea was,
>>>> so Laurent does not read the whole thread in detail. Sorry for not
>>>> being clear enough about that.
>>>
>>> Whoops! Sorry about the misunderstanding!
>>>
>>>>
>>>>> There are two cleanup paths:
>>>>>  1. uvcg_video_disable cleans up only the requests in req_free, and
>>>>>  2. complete handler cleans up the in-flight requests.
>>>>>
>>>>> The "abandoned" flag is simply to let the completion handler know
>>>>> which requests to clean up and which ones to re-queue back to
>>>>> the gadget driver.
>>>>
>>>> What I don't get is, why in the case of shutdown there needs to
>>>> be something re-queued back to the gadget driver. There should not
>>>> need to be any sort of barrier flag for the requests. Just the
>>>> complete handler running past a barrier where it knows that the
>>>> whole device is stopped. So every call on complete should then clean
>>>> that exact request it is touching currently.
>>>>
>>>> I don't know where the extra complexity comes from.
>>>
>>> A lot of this complexity comes from assuming a back to back
>>> STREAMOFF -> STREAMON sequence is possible where the gadget driver
>>> doesn't have the time to clean up all in-flight usb_requests.
>>> However, looking through the usb gadget APIs again, and it
>>> looks like  usb_ep_disable enforces that all requests will
>>> be sent back to the gadget driver before it returns.
>>
>> Great!
>
>Uhh...apologies, I will have to take this back. I've been
>trying to use uvc->state as the condition for when completion
>handler should clean up usb_requests, and I cannot figure
>out a way to do so cleanly.
>
>The fundamental problem with using uvc->state is that it is
>not protected by any locks. So there is no real way to
>assert that its value has not changed between reading
>uvc->state and acting on it.
>
>Naively we can write something like the following in the
>completion handler:
>
>void uvc_video_complete(...) {
>    if (uvc->state != UVC_EVENT_STREAMING) {
>        usb_ep_free_request(....);
>    } else {
>        // handle usb_request normally
>    }
>}
>
>But without any locks, there are no guarantees that
>uvc->state didn't mutate immediately after the if
>condition was checked, and the complete handler is
>handling a request that it should've freed instead
>or vice-versa. This argument would hold for any logic
>we guard with uvc->state, making uvc->state effectively
>useless as a check for freeing memory.

Yes, this makes total sense. Since the above condition was also part of
the wait_event patch you created in the first place, I bet this issue
was there aswell and was probably causing the issues I saw while testing
it.


>We can work around it by either
>1. Locking uvc->state with some driver level lock
>   to ensure that we can trust the value of uvc->state
>   at least for a little while, or
>2. Using some other barrier condition that is protected by
>   another lock
>
>If we go with (1), we'd have to add a lock around every
>and every write to uvc->state, which isn't terrible, but
>would require more testing to ensure that it doesn't
>create any new deadlocks.
>
>For (2), with the realization that usb_ep_disable flushes
>all requests, we can add a barrier in uvc_video, protected by
>req_lock. That should simplify the logic a little bit and
>will hopefully be easier to reason about.
>
>I could of course be missing a simpler solution here,
>and am happy to be wrong. So please let me know if you
>have any other ideas on how to guarantee such a check.

For now, I have no better Idea. Idea (2) sounds like
a good compromise. But I will have to review that code
to really judge.

Thanks for the work!

Michael


-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v4 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                   ` (4 preceding siblings ...)
  2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-12  0:24 ` Avichal Rakesh
  2023-10-12  0:24   ` [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-12  0:24   ` [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-10-18 19:46 ` [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  7 siblings, 2 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-12  0:24 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart, m.grzeschik
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 21 ++++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..c0d77564a204 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,19 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0) {
+		return ret;
+	}
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +508,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +663,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.609.gbb76f46606-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-12  0:24 ` [PATCH v4 " Avichal Rakesh
@ 2023-10-12  0:24   ` Avichal Rakesh
  2023-10-18 13:03     ` Michael Grzeschik
  2023-10-12  0:24   ` [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  1 sibling, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-12  0:24 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart, m.grzeschik
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 87 ++++++++++++++-----------
 2 files changed, 50 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..b62b3de79153 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
+	struct uvc_request *ureq, *temp;

-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
-
-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,32 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+		INIT_LIST_HEAD(&ureq->list);
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +496,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +509,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +544,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.609.gbb76f46606-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-12  0:24 ` [PATCH v4 " Avichal Rakesh
  2023-10-12  0:24   ` [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-12  0:24   ` Avichal Rakesh
  2023-10-12  0:42     ` Avichal Rakesh
  2023-10-18 13:10     ` Michael Grzeschik
  1 sibling, 2 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-12  0:24 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart, m.grzeschik
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accomodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
 drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
 3 files changed, 128 insertions(+), 41 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index c0d77564a204..ded7d33c2a52 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,12 +468,12 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_enable(video, 0);
 	if (ret < 0) {
 		return ret;
 	}

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -508,14 +508,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index b62b3de79153..05b89b5b6c48 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/**
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -391,20 +419,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while(true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -486,9 +516,78 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
+	spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
+/*
+ * Disable video stream
+ */
+static int
+uvcg_video_disable(struct uvc_video *video) {
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;
+
+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
+	}
+
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
 }

 /*
@@ -497,28 +596,22 @@ static void uvcg_video_pump(struct work_struct *work)
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
 			  "Video enable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}
-
-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
-	}
-
+	if (!enable)
+		return uvcg_video_disable(video);
+
+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -544,6 +637,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.609.gbb76f46606-goog


^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-08 19:48                 ` Michael Grzeschik
@ 2023-10-12  0:33                   ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-12  0:33 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: Laurent Pinchart, Daniel Scally, Greg Kroah-Hartman, jchowdhary,
	etalvala, linux-usb, linux-kernel



On 10/8/23 12:48, Michael Grzeschik wrote:
> On Fri, Oct 06, 2023 at 04:48:19PM -0700, Avichal Rakesh wrote:
>> On 10/6/23 15:53, Michael Grzeschik wrote:
>>> On Fri, Oct 06, 2023 at 10:00:11AM -0700, Avichal Rakesh wrote:
>>>>
>>>>
>>>> On 10/5/23 15:05, Michael Grzeschik wrote:
>>>>> Hi Avichal,
>>>>>
>>>>> On Thu, Oct 05, 2023 at 11:30:32AM -0700, Avichal Rakesh wrote:
>>>>>> On 10/5/23 03:14, Michael Grzeschik wrote:
>>>>> <snip>
>>>>> I don't know where the extra complexity comes from.
>>>>
>>>> A lot of this complexity comes from assuming a back to back
>>>> STREAMOFF -> STREAMON sequence is possible where the gadget driver
>>>> doesn't have the time to clean up all in-flight usb_requests.
>>>> However, looking through the usb gadget APIs again, and it
>>>> looks like  usb_ep_disable enforces that all requests will
>>>> be sent back to the gadget driver before it returns.
>>>
>>> Great!
>>
>> Uhh...apologies, I will have to take this back. I've been
>> trying to use uvc->state as the condition for when completion
>> handler should clean up usb_requests, and I cannot figure
>> out a way to do so cleanly.
>>
>> The fundamental problem with using uvc->state is that it is
>> not protected by any locks. So there is no real way to
>> assert that its value has not changed between reading
>> uvc->state and acting on it.
>>
>> Naively we can write something like the following in the
>> completion handler:
>>
>> void uvc_video_complete(...) {
>>    if (uvc->state != UVC_EVENT_STREAMING) {
>>        usb_ep_free_request(....);
>>    } else {
>>        // handle usb_request normally
>>    }
>> }
>>
>> But without any locks, there are no guarantees that
>> uvc->state didn't mutate immediately after the if
>> condition was checked, and the complete handler is
>> handling a request that it should've freed instead
>> or vice-versa. This argument would hold for any logic
>> we guard with uvc->state, making uvc->state effectively
>> useless as a check for freeing memory.
> 
> Yes, this makes total sense. Since the above condition was also part of
> the wait_event patch you created in the first place, I bet this issue
> was there aswell and was probably causing the issues I saw while testing
> it> 
> 
>> We can work around it by either
>> 1. Locking uvc->state with some driver level lock
>>   to ensure that we can trust the value of uvc->state
>>   at least for a little while, or
>> 2. Using some other barrier condition that is protected by
>>   another lock
>>
>> If we go with (1), we'd have to add a lock around every
>> and every write to uvc->state, which isn't terrible, but
>> would require more testing to ensure that it doesn't
>> create any new deadlocks.
>>
>> For (2), with the realization that usb_ep_disable flushes
>> all requests, we can add a barrier in uvc_video, protected by
>> req_lock. That should simplify the logic a little bit and
>> will hopefully be easier to reason about.
>>
>> I could of course be missing a simpler solution here,
>> and am happy to be wrong. So please let me know if you
>> have any other ideas on how to guarantee such a check.
> 
> For now, I have no better Idea. Idea (2) sounds like
> a good compromise. But I will have to review that code
> to really judge.
> 

Sent out v4 patches with option (2). It simplifies the logic 
decently because we no longer have to reason about per-request
consistency. uvc_video now tracks its own state of whether
requests should be flowing or not based on calls to
uvcg_video_enable. This state is protected, and is the source
of truth for queueing usb_requests.

The last bit of complexity left is around returning in-flight
video buffers. AFAICT it should now be protected, and in my 
local testing I didn't notice any un-returned buffers, but 
please to take a look and let me know if your testing 
uncovers anything.

Thanks,
Avi.
 

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-12  0:24   ` [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-12  0:42     ` Avichal Rakesh
  2023-10-18 13:10     ` Michael Grzeschik
  1 sibling, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-12  0:42 UTC (permalink / raw)
  To: dan.scally, laurent.pinchart, m.grzeschik
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb



On 10/11/23 17:24, Avichal Rakesh wrote:
> Currently, the uvc gadget driver allocates all uvc_requests as one array
> and deallocates them all when the video stream stops. This includes
> de-allocating all the usb_requests associated with those uvc_requests.
> This can lead to use-after-free issues if any of those de-allocated
> usb_requests were still owned by the usb controller.
> 
> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
> flag to uvc_video to track when frames and requests should be flowing.
> When disabling the video stream, the flag is tripped and, instead
> of de-allocating all uvc_requests and usb_requests, the gadget
> driver only de-allocates those usb_requests that are currently
> owned by it (as present in req_free). Other usb_requests are left
> untouched until their completion handler is called which takes care
> of freeing the usb_request and its corresponding uvc_request.
> 
> Now that uvc_video does not depends on uvc->state, this patch removes
> unnecessary upates to uvc->state that were made to accomodate uvc_video
> logic. This should ensure that uvc gadget driver never accidentally
> de-allocates a usb_request that it doesn't own.
> 
> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>           https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
> v2 -> v3: Fix email threading goof-up
> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>           as discussed in
>           https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
> 
>  drivers/usb/gadget/function/uvc.h       |   1 +
>  drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
>  drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
>  3 files changed, 128 insertions(+), 41 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h

Apologies, I realized I forgot to run checkpatch on this patch. Will fix 
the lint issues in the next version. This patch is functionally okay, but 
has 2 minor formatting issues. Feel free to review the patch, and I will
fix the formatting as I am addressing the comments.

- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-12  0:24   ` [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-18 13:03     ` Michael Grzeschik
  2023-10-18 19:53       ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-18 13:03 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 7556 bytes --]

A short second review.

On Wed, Oct 11, 2023 at 05:24:50PM -0700, Avichal Rakesh wrote:
>Currently, the uvc gadget driver allocates all uvc_requests as one array
>and deallocates them all when the video stream stops. This includes
>de-allocating all the usb_requests associated with those uvc_requests.
>This can lead to use-after-free issues if any of those de-allocated
>usb_requests were still owned by the usb controller.
>
>This patch is 1 of 2 patches addressing the use-after-free issue.
>Instead of bulk allocating all uvc_requests as an array, this patch
>allocates uvc_requests one at a time, which should allows for similar
>granularity when deallocating the uvc_requests. This patch has no
>functional changes other than allocating each uvc_request separately,
>and similarly freeing each of them separately.
>
>Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>Signed-off-by: Avichal Rakesh <arakesh@google.com>
>---
>v1 -> v2: Rebased to ToT
>v2 -> v3: Fix email threading goof-up
>v3 -> v4: Address review comments & re-rebase to ToT
>
> drivers/usb/gadget/function/uvc.h       |  3 +-
> drivers/usb/gadget/function/uvc_video.c | 87 ++++++++++++++-----------
> 2 files changed, 50 insertions(+), 40 deletions(-)
>
>diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>index 989bc6b4e93d..993694da0bbc 100644
>--- a/drivers/usb/gadget/function/uvc.h
>+++ b/drivers/usb/gadget/function/uvc.h
>@@ -81,6 +81,7 @@ struct uvc_request {
> 	struct sg_table sgt;
> 	u8 header[UVCG_REQUEST_HEADER_LEN];
> 	struct uvc_buffer *last_buf;
>+	struct list_head list;
> };
>
> struct uvc_video {
>@@ -102,7 +103,7 @@ struct uvc_video {
>
> 	/* Requests */
> 	unsigned int req_size;
>-	struct uvc_request *ureq;
>+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
> 	struct list_head req_free;
> 	spinlock_t req_lock;
>
>diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>index c334802ac0a4..b62b3de79153 100644
>--- a/drivers/usb/gadget/function/uvc_video.c
>+++ b/drivers/usb/gadget/function/uvc_video.c
>@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>  * Request handling
>  */
>
>+static void
>+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>+{
>+	sg_free_table(&ureq->sgt);
>+	if (ureq->req && ep) {
>+		usb_ep_free_request(ep, ureq->req);
>+		ureq->req = NULL;
>+	}
>+
>+	kfree(ureq->req_buffer);
>+	ureq->req_buffer = NULL;
>+
>+	if (!list_empty(&ureq->list))
>+		list_del_init(&ureq->list);
>+
>+	kfree(ureq);
>+}
>+
> static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
> {
> 	int ret;
>@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
> static int
> uvc_video_free_requests(struct uvc_video *video)
> {
>-	unsigned int i;
>+	struct uvc_request *ureq, *temp;
>
>-	if (video->ureq) {
>-		for (i = 0; i < video->uvc_num_requests; ++i) {
>-			sg_free_table(&video->ureq[i].sgt);
>-
>-			if (video->ureq[i].req) {
>-				usb_ep_free_request(video->ep, video->ureq[i].req);
>-				video->ureq[i].req = NULL;
>-			}
>-
>-			if (video->ureq[i].req_buffer) {
>-				kfree(video->ureq[i].req_buffer);
>-				video->ureq[i].req_buffer = NULL;
>-			}
>-		}
>-
>-		kfree(video->ureq);
>-		video->ureq = NULL;
>-	}
>+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
>+		uvc_video_free_request(ureq, video->ep);
>
>+	INIT_LIST_HEAD(&video->ureqs);
> 	INIT_LIST_HEAD(&video->req_free);
> 	video->req_size = 0;
> 	return 0;
>@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
> static int
> uvc_video_alloc_requests(struct uvc_video *video)
> {
>+	struct uvc_request *ureq;
> 	unsigned int req_size;
> 	unsigned int i;
> 	int ret = -ENOMEM;
>@@ -332,29 +336,32 @@ uvc_video_alloc_requests(struct uvc_video *video)
> 		 * max_t(unsigned int, video->ep->maxburst, 1)
> 		 * (video->ep->mult);
>
>-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
>-	if (video->ureq == NULL)
>-		return -ENOMEM;
>+	INIT_LIST_HEAD(&video->ureqs);
>+	for (i = 0; i < video->uvc_num_requests; i++) {
>+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
>+		if (ureq == NULL)
>+			goto error;
Please add an empty line here.
>+		INIT_LIST_HEAD(&ureq->list);

Please add an empty line here.

>+		list_add_tail(&ureq->list, &video->ureqs);
>
>-	for (i = 0; i < video->uvc_num_requests; ++i) {
>-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
>-		if (video->ureq[i].req_buffer == NULL)
>+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
>+		if (ureq->req_buffer == NULL)
You could also use if (!ureq->req_buffer)

> 			goto error;
>
>-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>-		if (video->ureq[i].req == NULL)
>+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>+		if (ureq->req == NULL)
> 			goto error;
>
>-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
>-		video->ureq[i].req->length = 0;
>-		video->ureq[i].req->complete = uvc_video_complete;
>-		video->ureq[i].req->context = &video->ureq[i];
>-		video->ureq[i].video = video;
>-		video->ureq[i].last_buf = NULL;
>+		ureq->req->buf = ureq->req_buffer;
>+		ureq->req->length = 0;
>+		ureq->req->complete = uvc_video_complete;
>+		ureq->req->context = ureq;
>+		ureq->video = video;
>+		ureq->last_buf = NULL;
>
>-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
>+		list_add_tail(&ureq->req->list, &video->req_free);
> 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
>-		sg_alloc_table(&video->ureq[i].sgt,
>+		sg_alloc_table(&ureq->sgt,
> 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
> 					    PAGE_SIZE) + 2, GFP_KERNEL);
> 	}
>@@ -489,8 +496,8 @@ static void uvcg_video_pump(struct work_struct *work)
>  */
> int uvcg_video_enable(struct uvc_video *video, int enable)
> {
>-	unsigned int i;
> 	int ret;
>+	struct uvc_request *ureq;
>
> 	if (video->ep == NULL) {
> 		uvcg_info(&video->uvc->func,
>@@ -502,9 +509,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
> 		cancel_work_sync(&video->pump);
> 		uvcg_queue_cancel(&video->queue, 0);
>
>-		for (i = 0; i < video->uvc_num_requests; ++i)
>-			if (video->ureq && video->ureq[i].req)
>-				usb_ep_dequeue(video->ep, video->ureq[i].req);
>+		list_for_each_entry(ureq, &video->ureqs, list) {
>+			if (ureq->req)
>+				usb_ep_dequeue(video->ep, ureq->req);
>+		}
>
> 		uvc_video_free_requests(video);
> 		uvcg_queue_enable(&video->queue, 0);
>@@ -536,6 +544,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>  */
> int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
> {
>+	INIT_LIST_HEAD(&video->ureqs);
> 	INIT_LIST_HEAD(&video->req_free);
> 	spin_lock_init(&video->req_lock);
> 	INIT_WORK(&video->pump, uvcg_video_pump);
>--
>2.42.0.609.gbb76f46606-goog
>
>

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-12  0:24   ` [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-10-12  0:42     ` Avichal Rakesh
@ 2023-10-18 13:10     ` Michael Grzeschik
  2023-10-18 21:50       ` Avichal Rakesh
  1 sibling, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-18 13:10 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 12289 bytes --]

On Wed, Oct 11, 2023 at 05:24:51PM -0700, Avichal Rakesh wrote:
>Currently, the uvc gadget driver allocates all uvc_requests as one array
>and deallocates them all when the video stream stops. This includes
>de-allocating all the usb_requests associated with those uvc_requests.
>This can lead to use-after-free issues if any of those de-allocated
>usb_requests were still owned by the usb controller.
>
>This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>flag to uvc_video to track when frames and requests should be flowing.
>When disabling the video stream, the flag is tripped and, instead
>of de-allocating all uvc_requests and usb_requests, the gadget
>driver only de-allocates those usb_requests that are currently
>owned by it (as present in req_free). Other usb_requests are left
>untouched until their completion handler is called which takes care
>of freeing the usb_request and its corresponding uvc_request.
>
>Now that uvc_video does not depends on uvc->state, this patch removes
>unnecessary upates to uvc->state that were made to accomodate uvc_video
>logic. This should ensure that uvc gadget driver never accidentally
>de-allocates a usb_request that it doesn't own.
>
>Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>Signed-off-by: Avichal Rakesh <arakesh@google.com>
>---
>v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>v2 -> v3: Fix email threading goof-up
>v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>          as discussed in
>          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/

I tested this and I no longer saw any use after free
errors anymore! :)

Here comes some more review:

> drivers/usb/gadget/function/uvc.h       |   1 +
> drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
> drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
> 3 files changed, 128 insertions(+), 41 deletions(-)
>
>diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>index 993694da0bbc..be0d012aa244 100644
>--- a/drivers/usb/gadget/function/uvc.h
>+++ b/drivers/usb/gadget/function/uvc.h
>@@ -102,6 +102,7 @@ struct uvc_video {
> 	unsigned int uvc_num_requests;
>
> 	/* Requests */
>+	bool is_enabled; /* tracks whether video stream is enabled */
> 	unsigned int req_size;
> 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
> 	struct list_head req_free;
>diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>index c0d77564a204..ded7d33c2a52 100644
>--- a/drivers/usb/gadget/function/uvc_v4l2.c
>+++ b/drivers/usb/gadget/function/uvc_v4l2.c
>@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
> 	 * Complete the alternate setting selection setup phase now that
> 	 * userspace is ready to provide video frames.
> 	 */
>-	uvc_function_setup_continue(uvc, 0);
> 	uvc->state = UVC_STATE_STREAMING;
>+	uvc_function_setup_continue(uvc, 0);
>
> 	return 0;
> }
>@@ -468,12 +468,12 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
> 	if (type != video->queue.queue.type)
> 		return -EINVAL;
>
>-	uvc->state = UVC_STATE_CONNECTED;
> 	ret = uvcg_video_enable(video, 0);
> 	if (ret < 0) {
> 		return ret;
> 	}
>
>+	uvc->state = UVC_STATE_CONNECTED;
> 	uvc_function_setup_continue(uvc, 1);
> 	return 0;
> }
>@@ -508,14 +508,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
> static void uvc_v4l2_disable(struct uvc_device *uvc)
> {
> 	uvc_function_disconnect(uvc);
>-	/*
>-	 * Drop uvc->state to CONNECTED if it was streaming before.
>-	 * This ensures that the usb_requests are no longer queued
>-	 * to the controller.
>-	 */
>-	if (uvc->state == UVC_STATE_STREAMING)
>-		uvc->state = UVC_STATE_CONNECTED;
>-
> 	uvcg_video_enable(&uvc->video, 0);
> 	uvcg_free_buffers(&uvc->video.queue);
> 	uvc->func_connected = false;
>diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>index b62b3de79153..05b89b5b6c48 100644
>--- a/drivers/usb/gadget/function/uvc_video.c
>+++ b/drivers/usb/gadget/function/uvc_video.c
>@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>  * Request handling
>  */
>
>+/**
>+ * Must be called with req_lock held as it modifies the list ureq is held in
>+ */
> static void
> uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
> {
>@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
> 	struct uvc_request *ureq = req->context;
> 	struct uvc_video *video = ureq->video;
> 	struct uvc_video_queue *queue = &video->queue;
>-	struct uvc_device *uvc = video->uvc;
>+	struct uvc_buffer *last_buf = NULL;
> 	unsigned long flags;
>
>+	spin_lock_irqsave(&video->req_lock, flags);
>+	if (!video->is_enabled) {
>+		/*
>+		 * When is_enabled is false, uvc_video_disable ensures that
>+		 * in-flight uvc_buffers are returned, so we can safely
>+		 * call free_request without worrying about last_buf.
>+		 */
>+		uvc_video_free_request(ureq, ep);
>+		spin_unlock_irqrestore(&video->req_lock, flags);
>+		return;
>+	}
>+
>+	last_buf = ureq->last_buf;
>+	ureq->last_buf = NULL;
>+	spin_unlock_irqrestore(&video->req_lock, flags);
>+
> 	switch (req->status) {
> 	case 0:
> 		break;
>@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
> 		uvcg_queue_cancel(queue, 0);
> 	}
>
>-	if (ureq->last_buf) {
>-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
>-		ureq->last_buf = NULL;
>+	if (last_buf) {
>+		spin_lock_irqsave(&queue->irqlock, flags);
>+		uvcg_complete_buffer(&video->queue, last_buf);
>+		spin_unlock_irqrestore(&queue->irqlock, flags);
> 	}
>
> 	spin_lock_irqsave(&video->req_lock, flags);
>-	list_add_tail(&req->list, &video->req_free);
>-	spin_unlock_irqrestore(&video->req_lock, flags);
>-
>-	if (uvc->state == UVC_STATE_STREAMING)
>+	/*
>+	 * Video stream might have been disabled while we were
>+	 * processing the current usb_request. So make sure
>+	 * we're still streaming before queueing the usb_request
>+	 * back to req_free
>+	 */
>+	if (video->is_enabled) {
>+		list_add_tail(&req->list, &video->req_free);
> 		queue_work(video->async_wq, &video->pump);
>+	} else {
>+		uvc_video_free_request(ureq, ep);
>+	}
>+	spin_unlock_irqrestore(&video->req_lock, flags);
> }
>
> static int
>@@ -391,20 +419,22 @@ static void uvcg_video_pump(struct work_struct *work)
> 	struct uvc_video_queue *queue = &video->queue;
> 	/* video->max_payload_size is only set when using bulk transfer */
> 	bool is_bulk = video->max_payload_size;
>-	struct uvc_device *uvc = video->uvc;
> 	struct usb_request *req = NULL;
> 	struct uvc_buffer *buf;
> 	unsigned long flags;
> 	bool buf_done;
> 	int ret;
>
>-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>+	while(true) {

Missing space after "while".

>+		if (!video->ep->enabled)
>+			return;
>+
> 		/*
>-		 * Retrieve the first available USB request, protected by the
>-		 * request lock.
>+		 * Check is_enabled and retrieve the first available USB
>+		 * request, protected by the request lock.
> 		 */
> 		spin_lock_irqsave(&video->req_lock, flags);
>-		if (list_empty(&video->req_free)) {
>+		if (!video->is_enabled || list_empty(&video->req_free)) {
> 			spin_unlock_irqrestore(&video->req_lock, flags);
> 			return;
> 		}
>@@ -486,9 +516,78 @@ static void uvcg_video_pump(struct work_struct *work)
> 		return;
>
> 	spin_lock_irqsave(&video->req_lock, flags);
>-	list_add_tail(&req->list, &video->req_free);
>+	if (video->is_enabled)
>+		list_add_tail(&req->list, &video->req_free);
>+	else
>+		uvc_video_free_request(req->context, video->ep);
>+	spin_unlock_irqrestore(&video->req_lock, flags);
>+}
>+
>+/*
>+ * Disable video stream
>+ */
>+static int
>+uvcg_video_disable(struct uvc_video *video) {
>+	unsigned long flags;
>+	struct list_head inflight_bufs;
>+	struct usb_request *req, *temp;
>+	struct uvc_buffer *buf, *btemp;
>+	struct uvc_request *ureq, *utemp;
>+
>+	INIT_LIST_HEAD(&inflight_bufs);
>+	spin_lock_irqsave(&video->req_lock, flags);
>+	video->is_enabled = false;
>+
>+	/*
>+	 * Remove any in-flight buffers from the uvc_requests
>+	 * because we want to return them before cancelling the
>+	 * queue. This ensures that we aren't stuck waiting for
>+	 * all complete callbacks to come through before disabling
>+	 * vb2 queue.
>+	 */
>+	list_for_each_entry(ureq, &video->ureqs, list) {
>+		if (ureq->last_buf) {
>+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>+			ureq->last_buf = NULL;
>+		}
>+	}
> 	spin_unlock_irqrestore(&video->req_lock, flags);
>-	return;
>+
>+	cancel_work_sync(&video->pump);
>+	uvcg_queue_cancel(&video->queue, 0);
>+
>+	spin_lock_irqsave(&video->req_lock, flags);
>+	/*
>+	 * Remove all uvc_reqeusts from from ureqs with list_del_init
>+	 * This lets uvc_video_free_request correctly identify
>+	 * if the uvc_request is attached to a list or not when freeing
>+	 * memory.
>+	 */
>+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>+		list_del_init(&ureq->list);
>+
>+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
>+		list_del(&req->list);
>+		uvc_video_free_request(req->context, video->ep);
>+	}
>+
>+	INIT_LIST_HEAD(&video->ureqs);
>+	INIT_LIST_HEAD(&video->req_free);
>+	video->req_size = 0;
>+	spin_unlock_irqrestore(&video->req_lock, flags);
>+
>+	/*
>+	 * Return all the video buffers before disabling the queue.
>+	 */
>+	spin_lock_irqsave(&video->queue.irqlock, flags);
>+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>+		list_del(&buf->queue);
>+		uvcg_complete_buffer(&video->queue, buf);
>+	}
>+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
>+
>+	uvcg_queue_enable(&video->queue, 0);
>+	return 0;
> }
>
> /*
>@@ -497,28 +596,22 @@ static void uvcg_video_pump(struct work_struct *work)
> int uvcg_video_enable(struct uvc_video *video, int enable)
> {
> 	int ret;
>-	struct uvc_request *ureq;
>
> 	if (video->ep == NULL) {
> 		uvcg_info(&video->uvc->func,
> 			  "Video enable failed, device is uninitialized.\n");
> 		return -ENODEV;
> 	}
>-
>-	if (!enable) {
>-		cancel_work_sync(&video->pump);
>-		uvcg_queue_cancel(&video->queue, 0);
>-
>-		list_for_each_entry(ureq, &video->ureqs, list) {
>-			if (ureq->req)
>-				usb_ep_dequeue(video->ep, ureq->req);
>-		}
>-
>-		uvc_video_free_requests(video);
>-		uvcg_queue_enable(&video->queue, 0);
>-		return 0;
>-	}
>-
>+	if (!enable)
>+		return uvcg_video_disable(video);

Could you refactor this code as it is to an separate
function and prepand this change as an extra patch
to this one? It would make the changes in the functions
more obvious and better to review.

>+
>+	/*
>+	 * Safe to access request related fields without req_lock because
>+	 * this is the only thread currently active, and no other
>+	 * request handling thread will become active until this function
>+	 * returns.
>+	 */
>+	video->is_enabled = true;

Add an extra empty line.

> 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
> 		return ret;
>
>@@ -544,6 +637,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>  */
> int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
> {
>+	video->is_enabled = false;
> 	INIT_LIST_HEAD(&video->ureqs);
> 	INIT_LIST_HEAD(&video->req_free);
> 	spin_lock_init(&video->req_lock);
>--
>2.42.0.609.gbb76f46606-goog
>
>

Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>


-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                   ` (5 preceding siblings ...)
  2023-10-12  0:24 ` [PATCH v4 " Avichal Rakesh
@ 2023-10-18 19:46 ` Avichal Rakesh
  2023-10-18 19:46   ` [PATCH v5 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-18 19:46   ` [PATCH v5 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  7 siblings, 2 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-18 19:46 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Add Reviewed-by & Tested-by

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v5 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-18 19:46 ` [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-18 19:46   ` Avichal Rakesh
  2023-10-18 19:46   ` [PATCH v5 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  1 sibling, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-18 19:46 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
 2 files changed, 52 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..c180866c8e34 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v5 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-18 19:46 ` [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-18 19:46   ` [PATCH v5 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-18 19:46   ` Avichal Rakesh
  1 sibling, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-18 19:46 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
 drivers/usb/gadget/function/uvc_video.c | 154 +++++++++++++++++++-----
 3 files changed, 128 insertions(+), 39 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..f4d2e24835d4 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_enable(video, 0);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c180866c8e34..3a5192f10f95 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/**
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -488,9 +518,79 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
+}
+
+/*
+ * Disable video stream
+ */
+static int
+uvcg_video_disable(struct uvc_video *video)
+{
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;
+
+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
+	}
+
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
 }

 /*
@@ -499,27 +599,22 @@ static void uvcg_video_pump(struct work_struct *work)
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
 			  "Video enable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}
+	if (!enable)
+		return uvcg_video_disable(video);

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
-	}
+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;
@@ -546,6 +641,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-18 13:03     ` Michael Grzeschik
@ 2023-10-18 19:53       ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-18 19:53 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb



On 10/18/23 06:03, Michael Grzeschik wrote:
> A short second review.

Thank you for the review. Sent out a v5 with the comments 
addressed!

> 
> On Wed, Oct 11, 2023 at 05:24:50PM -0700, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This patch is 1 of 2 patches addressing the use-after-free issue.
>> Instead of bulk allocating all uvc_requests as an array, this patch
>> allocates uvc_requests one at a time, which should allows for similar
>> granularity when deallocating the uvc_requests. This patch has no
>> functional changes other than allocating each uvc_request separately,
>> and similarly freeing each of them separately.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>> ---
>> v1 -> v2: Rebased to ToT
>> v2 -> v3: Fix email threading goof-up
>> v3 -> v4: Address review comments & re-rebase to ToT
>>
>> drivers/usb/gadget/function/uvc.h       |  3 +-
>> drivers/usb/gadget/function/uvc_video.c | 87 ++++++++++++++-----------
>> 2 files changed, 50 insertions(+), 40 deletions(-)
>> -        if (video->ureq[i].req_buffer == NULL)
>> +        ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
>> +        if (ureq->req_buffer == NULL)
> You could also use if (!ureq->req_buffer)

Keeping this as is because I prefer `== NULL` check for readability.
Didn't find any specific rules in the kernel codestyle, so sticking
with the more readable option (in my opinion).

> 
>>             goto error;
>>
>> -        video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>> -        if (video->ureq[i].req == NULL)
>> +        ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>> +        if (ureq->req == NULL)
>>             goto error;
>>
>> -        video->ureq[i].req->buf = video->ureq[i].req_buffer;
>> -        video->ureq[i].req->length = 0;
>> -        video->ureq[i].req->complete = uvc_video_complete;
>> -        video->ureq[i].req->context = &video->ureq[i];
>> -        video->ureq[i].video = video;
>> -        video->ureq[i].last_buf = NULL;
>> +        ureq->req->buf = ureq->req_buffer;
>> +        ureq->req->length = 0;
>> +        ureq->req->complete = uvc_video_complete;
>> +        ureq->req->context = ureq;
>> +        ureq->video = video;
>> +        ureq->last_buf = NULL;
>>
>> -        list_add_tail(&video->ureq[i].req->list, &video->req_free);
>> +        list_add_tail(&ureq->req->list, &video->req_free);
>>         /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
>> -        sg_alloc_table(&video->ureq[i].sgt,
>> +        sg_alloc_table(&ureq->sgt,
>>                    DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
>>                         PAGE_SIZE) + 2, GFP_KERNEL);
>>     }
>> @@ -489,8 +496,8 @@ static void uvcg_video_pump(struct work_struct *work)
>>  */
>> int uvcg_video_enable(struct uvc_video *video, int enable)
>> {
>> -    unsigned int i;
>>     int ret;
>> +    struct uvc_request *ureq;
>>
>>     if (video->ep == NULL) {
>>         uvcg_info(&video->uvc->func,
>> @@ -502,9 +509,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>>         cancel_work_sync(&video->pump);
>>         uvcg_queue_cancel(&video->queue, 0);
>>
>> -        for (i = 0; i < video->uvc_num_requests; ++i)
>> -            if (video->ureq && video->ureq[i].req)
>> -                usb_ep_dequeue(video->ep, video->ureq[i].req);
>> +        list_for_each_entry(ureq, &video->ureqs, list) {
>> +            if (ureq->req)
>> +                usb_ep_dequeue(video->ep, ureq->req);
>> +        }
>>
>>         uvc_video_free_requests(video);
>>         uvcg_queue_enable(&video->queue, 0);
>> @@ -536,6 +544,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>>  */
>> int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>> {
>> +    INIT_LIST_HEAD(&video->ureqs);
>>     INIT_LIST_HEAD(&video->req_free);
>>     spin_lock_init(&video->req_lock);
>>     INIT_WORK(&video->pump, uvcg_video_pump);
>> -- 
>> 2.42.0.609.gbb76f46606-goog
>>
>>
> 

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-18 13:10     ` Michael Grzeschik
@ 2023-10-18 21:50       ` Avichal Rakesh
  2023-10-18 22:06         ` Michael Grzeschik
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-18 21:50 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb



On 10/18/23 06:10, Michael Grzeschik wrote:
> On Wed, Oct 11, 2023 at 05:24:51PM -0700, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>> flag to uvc_video to track when frames and requests should be flowing.
>> When disabling the video stream, the flag is tripped and, instead
>> of de-allocating all uvc_requests and usb_requests, the gadget
>> driver only de-allocates those usb_requests that are currently
>> owned by it (as present in req_free). Other usb_requests are left
>> untouched until their completion handler is called which takes care
>> of freeing the usb_request and its corresponding uvc_request.
>>
>> Now that uvc_video does not depends on uvc->state, this patch removes
>> unnecessary upates to uvc->state that were made to accomodate uvc_video
>> logic. This should ensure that uvc gadget driver never accidentally
>> de-allocates a usb_request that it doesn't own.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>> ---
>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>> v2 -> v3: Fix email threading goof-up
>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>          as discussed in
>>          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
> 
> I tested this and I no longer saw any use after free
> errors anymore! :)

Yay! Glad to hear!

> 
> Here comes some more review:
> 
>> drivers/usb/gadget/function/uvc.h       |   1 +
>> drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
>> drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
>> 3 files changed, 128 insertions(+), 41 deletions(-)
>>

>> +
>> +/*
>> + * Disable video stream
>> + */
>> +static int
>> +uvcg_video_disable(struct uvc_video *video) {
>> +    unsigned long flags;
>> +    struct list_head inflight_bufs;
>> +    struct usb_request *req, *temp;
>> +    struct uvc_buffer *buf, *btemp;
>> +    struct uvc_request *ureq, *utemp;
>> +
>> +    INIT_LIST_HEAD(&inflight_bufs);
>> +    spin_lock_irqsave(&video->req_lock, flags);
>> +    video->is_enabled = false;
>> +
>> +    /*
>> +     * Remove any in-flight buffers from the uvc_requests
>> +     * because we want to return them before cancelling the
>> +     * queue. This ensures that we aren't stuck waiting for
>> +     * all complete callbacks to come through before disabling
>> +     * vb2 queue.
>> +     */
>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>> +        if (ureq->last_buf) {
>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>> +            ureq->last_buf = NULL;
>> +        }
>> +    }
>>     spin_unlock_irqrestore(&video->req_lock, flags);
>> -    return;
>> +
>> +    cancel_work_sync(&video->pump);
>> +    uvcg_queue_cancel(&video->queue, 0);
>> +
>> +    spin_lock_irqsave(&video->req_lock, flags);
>> +    /*
>> +     * Remove all uvc_reqeusts from from ureqs with list_del_init
>> +     * This lets uvc_video_free_request correctly identify
>> +     * if the uvc_request is attached to a list or not when freeing
>> +     * memory.
>> +     */
>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>> +        list_del_init(&ureq->list);
>> +
>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>> +        list_del(&req->list);
>> +        uvc_video_free_request(req->context, video->ep);
>> +    }
>> +
>> +    INIT_LIST_HEAD(&video->ureqs);
>> +    INIT_LIST_HEAD(&video->req_free);
>> +    video->req_size = 0;
>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>> +
>> +    /*
>> +     * Return all the video buffers before disabling the queue.
>> +     */
>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>> +        list_del(&buf->queue);
>> +        uvcg_complete_buffer(&video->queue, buf);
>> +    }
>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>> +
>> +    uvcg_queue_enable(&video->queue, 0);
>> +    return 0;
>> }
>>
>> /*
>> @@ -497,28 +596,22 @@ static void uvcg_video_pump(struct work_struct *work)
>> int uvcg_video_enable(struct uvc_video *video, int enable)
>> {
>>     int ret;
>> -    struct uvc_request *ureq;
>>
>>     if (video->ep == NULL) {
>>         uvcg_info(&video->uvc->func,
>>               "Video enable failed, device is uninitialized.\n");
>>         return -ENODEV;
>>     }
>> -
>> -    if (!enable) {
>> -        cancel_work_sync(&video->pump);
>> -        uvcg_queue_cancel(&video->queue, 0);
>> -
>> -        list_for_each_entry(ureq, &video->ureqs, list) {
>> -            if (ureq->req)
>> -                usb_ep_dequeue(video->ep, ureq->req);
>> -        }
>> -
>> -        uvc_video_free_requests(video);
>> -        uvcg_queue_enable(&video->queue, 0);
>> -        return 0;
>> -    }
>> -
>> +    if (!enable)
>> +        return uvcg_video_disable(video);
> 
> Could you refactor this code as it is to an separate
> function and prepand this change as an extra patch
> to this one? It would make the changes in the functions
> more obvious and better to review.

Sure I can send a follow up patch, but I am curious why you think this 
needs to be a separate function? Refactoring into a function would 
have the functions structured something like:

uvcg_video_disable(video) {
    // ...
    // disable impl
    // ...
}

uvcg_video_enable(video) {
    // ...
    // enable impl
    // ...
}

uvcg_video_enable(video, enable) {
    // ep test
   
    if (!enable)
        return uvcg_video_disable(video);

    return uvc_video_enable(video);
}

instead of the current structure:

uvcg_video_disable(video) {
    // ...
    // disable impl
    // ...
}

uvcg_video_enable(video, enable) {
    // ep test
   
    if (!enable)
        return uvcg_video_disable(video);

    // ...
    // enable impl
    // ...
}

I am not sure if one is more readable than the other.

> 
>> +
>> +    /*
>> +     * Safe to access request related fields without req_lock because
>> +     * this is the only thread currently active, and no other
>> +     * request handling thread will become active until this function
>> +     * returns.
>> +     */
>> +    video->is_enabled = true;
> 
> Add an extra empty line.
> 
>>     if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>>         return ret;
>>
>> @@ -544,6 +637,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>>  */
>> int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>> {
>> +    video->is_enabled = false;
>>     INIT_LIST_HEAD(&video->ureqs);
>>     INIT_LIST_HEAD(&video->req_free);
>>     spin_lock_init(&video->req_lock);
>> -- 
>> 2.42.0.609.gbb76f46606-goog
>>
>>
> 
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> 
> 

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-18 21:50       ` Avichal Rakesh
@ 2023-10-18 22:06         ` Michael Grzeschik
  2023-10-19 18:54           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Michael Grzeschik @ 2023-10-18 22:06 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 7957 bytes --]

On Wed, Oct 18, 2023 at 02:50:08PM -0700, Avichal Rakesh wrote:
>
>
>On 10/18/23 06:10, Michael Grzeschik wrote:
>> On Wed, Oct 11, 2023 at 05:24:51PM -0700, Avichal Rakesh wrote:
>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>> and deallocates them all when the video stream stops. This includes
>>> de-allocating all the usb_requests associated with those uvc_requests.
>>> This can lead to use-after-free issues if any of those de-allocated
>>> usb_requests were still owned by the usb controller.
>>>
>>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>>> flag to uvc_video to track when frames and requests should be flowing.
>>> When disabling the video stream, the flag is tripped and, instead
>>> of de-allocating all uvc_requests and usb_requests, the gadget
>>> driver only de-allocates those usb_requests that are currently
>>> owned by it (as present in req_free). Other usb_requests are left
>>> untouched until their completion handler is called which takes care
>>> of freeing the usb_request and its corresponding uvc_request.
>>>
>>> Now that uvc_video does not depends on uvc->state, this patch removes
>>> unnecessary upates to uvc->state that were made to accomodate uvc_video
>>> logic. This should ensure that uvc gadget driver never accidentally
>>> de-allocates a usb_request that it doesn't own.
>>>
>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>> ---
>>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>>          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>>> v2 -> v3: Fix email threading goof-up
>>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>>          as discussed in
>>>          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>>
>> I tested this and I no longer saw any use after free
>> errors anymore! :)
>
>Yay! Glad to hear!
>
>>
>> Here comes some more review:
>>
>>> drivers/usb/gadget/function/uvc.h       |   1 +
>>> drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
>>> drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
>>> 3 files changed, 128 insertions(+), 41 deletions(-)
>>>
>
>>> +
>>> +/*
>>> + * Disable video stream
>>> + */
>>> +static int
>>> +uvcg_video_disable(struct uvc_video *video) {
>>> +    unsigned long flags;
>>> +    struct list_head inflight_bufs;
>>> +    struct usb_request *req, *temp;
>>> +    struct uvc_buffer *buf, *btemp;
>>> +    struct uvc_request *ureq, *utemp;
>>> +
>>> +    INIT_LIST_HEAD(&inflight_bufs);
>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>> +    video->is_enabled = false;
>>> +
>>> +    /*
>>> +     * Remove any in-flight buffers from the uvc_requests
>>> +     * because we want to return them before cancelling the
>>> +     * queue. This ensures that we aren't stuck waiting for
>>> +     * all complete callbacks to come through before disabling
>>> +     * vb2 queue.
>>> +     */
>>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>>> +        if (ureq->last_buf) {
>>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>>> +            ureq->last_buf = NULL;
>>> +        }
>>> +    }
>>>     spin_unlock_irqrestore(&video->req_lock, flags);
>>> -    return;
>>> +
>>> +    cancel_work_sync(&video->pump);
>>> +    uvcg_queue_cancel(&video->queue, 0);
>>> +
>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>> +    /*
>>> +     * Remove all uvc_reqeusts from from ureqs with list_del_init
>>> +     * This lets uvc_video_free_request correctly identify
>>> +     * if the uvc_request is attached to a list or not when freeing
>>> +     * memory.
>>> +     */
>>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>>> +        list_del_init(&ureq->list);
>>> +
>>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>>> +        list_del(&req->list);
>>> +        uvc_video_free_request(req->context, video->ep);
>>> +    }
>>> +
>>> +    INIT_LIST_HEAD(&video->ureqs);
>>> +    INIT_LIST_HEAD(&video->req_free);
>>> +    video->req_size = 0;
>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>> +
>>> +    /*
>>> +     * Return all the video buffers before disabling the queue.
>>> +     */
>>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>>> +        list_del(&buf->queue);
>>> +        uvcg_complete_buffer(&video->queue, buf);
>>> +    }
>>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>>> +
>>> +    uvcg_queue_enable(&video->queue, 0);
>>> +    return 0;
>>> }
>>>
>>> /*
>>> @@ -497,28 +596,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>> int uvcg_video_enable(struct uvc_video *video, int enable)
>>> {
>>>     int ret;
>>> -    struct uvc_request *ureq;
>>>
>>>     if (video->ep == NULL) {
>>>         uvcg_info(&video->uvc->func,
>>>               "Video enable failed, device is uninitialized.\n");
>>>         return -ENODEV;
>>>     }
>>> -
>>> -    if (!enable) {
>>> -        cancel_work_sync(&video->pump);
>>> -        uvcg_queue_cancel(&video->queue, 0);
>>> -
>>> -        list_for_each_entry(ureq, &video->ureqs, list) {
>>> -            if (ureq->req)
>>> -                usb_ep_dequeue(video->ep, ureq->req);
>>> -        }
>>> -
>>> -        uvc_video_free_requests(video);
>>> -        uvcg_queue_enable(&video->queue, 0);
>>> -        return 0;
>>> -    }
>>> -
>>> +    if (!enable)
>>> +        return uvcg_video_disable(video);
>>
>> Could you refactor this code as it is to an separate
>> function and prepand this change as an extra patch
>> to this one? It would make the changes in the functions
>> more obvious and better to review.
>
>Sure I can send a follow up patch, but I am curious why you think this
>needs to be a separate function? Refactoring into a function would
>have the functions structured something like:
>
>uvcg_video_disable(video) {
>    // ...
>    // disable impl
>    // ...
>}
>
>uvcg_video_enable(video) {
>    // ...
>    // enable impl
>    // ...
>}
>
>uvcg_video_enable(video, enable) {
>    // ep test
>
>    if (!enable)
>        return uvcg_video_disable(video);
>
>    return uvc_video_enable(video);
>}
>
>instead of the current structure:
>
>uvcg_video_disable(video) {
>    // ...
>    // disable impl
>    // ...
>}
>
>uvcg_video_enable(video, enable) {
>    // ep test
>
>    if (!enable)
>        return uvcg_video_disable(video);
>
>    // ...
>    // enable impl
>    // ...
>}
>
>I am not sure if one is more readable than the other.

I think you misunderstood. The second structure is all right.

What I did want you to do is as follows.

Lets look at your series:

patch 0/3
patch 1/3
patch 2/3

<--- add a patch here that does the refactoring of the separate
      function uvcg_video_disable without changing the functional
      content of it:

uvcg_video_disable(video) {
     // ...
     // disable impl
     // ...
}

uvcg_video_enable(video, enable) {
     // ep test

     if (!enable)
         return uvcg_video_disable(video);

     // ...
     // enable impl
     // ...
}

patch 3/3

This way in the patch 3/3 the functional changes you introduce to the
uvcg_video_diable will get better to review.

Regards,
Michael

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                   ` (6 preceding siblings ...)
  2023-10-18 19:46 ` [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-19 18:53 ` Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                     ` (6 more replies)
  7 siblings, 7 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:53 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

We have been seeing two main stability issues that uvc gadget driver
runs into when stopping streams:
 1. Attempting to queue usb_requests to a disabled usb_ep
 2. use-after-free issue for inflight usb_requests

The four patches below fix the two issues above. Patch 1/4 fixes the
first issue, and Patch 2/4 and 4/4 fix the second issue. Patch 3/4
is only there to make the diff in 4/4 cleaner.

Avichal Rakesh (4):
  usb: gadget: uvc: prevent use of disabled endpoint
  usb: gadget: uvc: Allocate uvc_requests one at a time
  usb: gadget: uvc: move video disable logic to its own function
  usb: gadget: uvc: Fix use-after-free for inflight usb_requests

 drivers/usb/gadget/function/f_uvc.c     |  11 +-
 drivers/usb/gadget/function/f_uvc.h     |   2 +-
 drivers/usb/gadget/function/uvc.h       |   6 +-
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
 drivers/usb/gadget/function/uvc_video.c | 231 +++++++++++++++++-------
 5 files changed, 189 insertions(+), 73 deletions(-)

--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v6 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
@ 2023-10-19 18:53   ` Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:53 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Add Reviewed-by & Tested-by
v5 -> v6: No change

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v6 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-19 18:53   ` Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
                     ` (4 subsequent siblings)
  6 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:53 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
v5 -> v6: No change

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
 2 files changed, 52 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..c180866c8e34 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v6 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-19 18:53   ` Avichal Rakesh
  2023-10-19 18:53   ` [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:53 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'.

Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v6: Introduced this patch to make the next one easier to review

 drivers/usb/gadget/function/uvc_video.c | 37 +++++++++++++++----------
 1 file changed, 23 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c180866c8e34..80b8eaea2d39 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,13 +493,33 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }

+/*
+ * Disable video stream
+ */
+static int
+uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_request *ureq;
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -507,19 +527,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
-	}
+	if (!enable)
+		return uvcg_video_disable(video);

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                     ` (2 preceding siblings ...)
  2023-10-19 18:53   ` [PATCH v6 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-19 18:53   ` Avichal Rakesh
  2023-10-19 20:32     ` kernel test robot
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                     ` (2 subsequent siblings)
  6 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:53 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
v5 -> v6: Added another patch before this one to make uvcg_video_disable
          easier to review.

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
 drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
 3 files changed, 111 insertions(+), 30 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..f4d2e24835d4 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_enable(video, 0);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 80b8eaea2d39..41fb4f24e829 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/**
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -499,17 +531,64 @@ static void uvcg_video_pump(struct work_struct *work)
 static int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;
+
+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);

 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -530,6 +609,14 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 	if (!enable)
 		return uvcg_video_disable(video);

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -555,6 +642,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-18 22:06         ` Michael Grzeschik
@ 2023-10-19 18:54           ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:54 UTC (permalink / raw)
  To: Michael Grzeschik
  Cc: dan.scally, laurent.pinchart, etalvala, gregkh, jchowdhary,
	linux-kernel, linux-usb



On 10/18/23 15:06, Michael Grzeschik wrote:
> On Wed, Oct 18, 2023 at 02:50:08PM -0700, Avichal Rakesh wrote:
>>
>>
>> On 10/18/23 06:10, Michael Grzeschik wrote:
>>> On Wed, Oct 11, 2023 at 05:24:51PM -0700, Avichal Rakesh wrote:
>>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>>> and deallocates them all when the video stream stops. This includes
>>>> de-allocating all the usb_requests associated with those uvc_requests.
>>>> This can lead to use-after-free issues if any of those de-allocated
>>>> usb_requests were still owned by the usb controller.
>>>>
>>>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>>>> flag to uvc_video to track when frames and requests should be flowing.
>>>> When disabling the video stream, the flag is tripped and, instead
>>>> of de-allocating all uvc_requests and usb_requests, the gadget
>>>> driver only de-allocates those usb_requests that are currently
>>>> owned by it (as present in req_free). Other usb_requests are left
>>>> untouched until their completion handler is called which takes care
>>>> of freeing the usb_request and its corresponding uvc_request.
>>>>
>>>> Now that uvc_video does not depends on uvc->state, this patch removes
>>>> unnecessary upates to uvc->state that were made to accomodate uvc_video
>>>> logic. This should ensure that uvc gadget driver never accidentally
>>>> de-allocates a usb_request that it doesn't own.
>>>>
>>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>>> ---
>>>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>>>          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>>>> v2 -> v3: Fix email threading goof-up
>>>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>>>          as discussed in
>>>>          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>>>
>>> I tested this and I no longer saw any use after free
>>> errors anymore! :)
>>
>> Yay! Glad to hear!
>>
>>>
>>> Here comes some more review:
>>>
>>>> drivers/usb/gadget/function/uvc.h       |   1 +
>>>> drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
>>>> drivers/usb/gadget/function/uvc_video.c | 156 +++++++++++++++++++-----
>>>> 3 files changed, 128 insertions(+), 41 deletions(-)
>>>>
>>
>>>> +
>>>> +/*
>>>> + * Disable video stream
>>>> + */
>>>> +static int
>>>> +uvcg_video_disable(struct uvc_video *video) {
>>>> +    unsigned long flags;
>>>> +    struct list_head inflight_bufs;
>>>> +    struct usb_request *req, *temp;
>>>> +    struct uvc_buffer *buf, *btemp;
>>>> +    struct uvc_request *ureq, *utemp;
>>>> +
>>>> +    INIT_LIST_HEAD(&inflight_bufs);
>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>> +    video->is_enabled = false;
>>>> +
>>>> +    /*
>>>> +     * Remove any in-flight buffers from the uvc_requests
>>>> +     * because we want to return them before cancelling the
>>>> +     * queue. This ensures that we aren't stuck waiting for
>>>> +     * all complete callbacks to come through before disabling
>>>> +     * vb2 queue.
>>>> +     */
>>>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>>>> +        if (ureq->last_buf) {
>>>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>>>> +            ureq->last_buf = NULL;
>>>> +        }
>>>> +    }
>>>>     spin_unlock_irqrestore(&video->req_lock, flags);
>>>> -    return;
>>>> +
>>>> +    cancel_work_sync(&video->pump);
>>>> +    uvcg_queue_cancel(&video->queue, 0);
>>>> +
>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>> +    /*
>>>> +     * Remove all uvc_reqeusts from from ureqs with list_del_init
>>>> +     * This lets uvc_video_free_request correctly identify
>>>> +     * if the uvc_request is attached to a list or not when freeing
>>>> +     * memory.
>>>> +     */
>>>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>>>> +        list_del_init(&ureq->list);
>>>> +
>>>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>>>> +        list_del(&req->list);
>>>> +        uvc_video_free_request(req->context, video->ep);
>>>> +    }
>>>> +
>>>> +    INIT_LIST_HEAD(&video->ureqs);
>>>> +    INIT_LIST_HEAD(&video->req_free);
>>>> +    video->req_size = 0;
>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>> +
>>>> +    /*
>>>> +     * Return all the video buffers before disabling the queue.
>>>> +     */
>>>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>>>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>>>> +        list_del(&buf->queue);
>>>> +        uvcg_complete_buffer(&video->queue, buf);
>>>> +    }
>>>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>>>> +
>>>> +    uvcg_queue_enable(&video->queue, 0);
>>>> +    return 0;
>>>> }
>>>>
>>>> /*
>>>> @@ -497,28 +596,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>>> int uvcg_video_enable(struct uvc_video *video, int enable)
>>>> {
>>>>     int ret;
>>>> -    struct uvc_request *ureq;
>>>>
>>>>     if (video->ep == NULL) {
>>>>         uvcg_info(&video->uvc->func,
>>>>               "Video enable failed, device is uninitialized.\n");
>>>>         return -ENODEV;
>>>>     }
>>>> -
>>>> -    if (!enable) {
>>>> -        cancel_work_sync(&video->pump);
>>>> -        uvcg_queue_cancel(&video->queue, 0);
>>>> -
>>>> -        list_for_each_entry(ureq, &video->ureqs, list) {
>>>> -            if (ureq->req)
>>>> -                usb_ep_dequeue(video->ep, ureq->req);
>>>> -        }
>>>> -
>>>> -        uvc_video_free_requests(video);
>>>> -        uvcg_queue_enable(&video->queue, 0);
>>>> -        return 0;
>>>> -    }
>>>> -
>>>> +    if (!enable)
>>>> +        return uvcg_video_disable(video);
>>>
>>> Could you refactor this code as it is to an separate
>>> function and prepand this change as an extra patch
>>> to this one? It would make the changes in the functions
>>> more obvious and better to review.
>>
>> Sure I can send a follow up patch, but I am curious why you think this
>> needs to be a separate function? Refactoring into a function would
>> have the functions structured something like:
>>
>> uvcg_video_disable(video) {
>>    // ...
>>    // disable impl
>>    // ...
>> }
>>
>> uvcg_video_enable(video) {
>>    // ...
>>    // enable impl
>>    // ...
>> }
>>
>> uvcg_video_enable(video, enable) {
>>    // ep test
>>
>>    if (!enable)
>>        return uvcg_video_disable(video);
>>
>>    return uvc_video_enable(video);
>> }
>>
>> instead of the current structure:
>>
>> uvcg_video_disable(video) {
>>    // ...
>>    // disable impl
>>    // ...
>> }
>>
>> uvcg_video_enable(video, enable) {
>>    // ep test
>>
>>    if (!enable)
>>        return uvcg_video_disable(video);
>>
>>    // ...
>>    // enable impl
>>    // ...
>> }
>>
>> I am not sure if one is more readable than the other.
> 
> I think you misunderstood. The second structure is all right.
> 
> What I did want you to do is as follows.
> 
> Lets look at your series:
> 
> patch 0/3
> patch 1/3
> patch 2/3
> 
> <--- add a patch here that does the refactoring of the separate
>      function uvcg_video_disable without changing the functional
>      content of it:
> 
> uvcg_video_disable(video) {
>     // ...
>     // disable impl
>     // ...
> }
> 
> uvcg_video_enable(video, enable) {
>     // ep test
> 
>     if (!enable)
>         return uvcg_video_disable(video);
> 
>     // ...
>     // enable impl
>     // ...
> }
> 
> patch 3/3
> 
> This way in the patch 3/3 the functional changes you introduce to the
> uvcg_video_diable will get better to review.

I see! I did indeed misunderstand. Sent out v6 with 4 patches!

Thank you!
- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF.
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                     ` (3 preceding siblings ...)
  2023-10-19 18:53   ` [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-19 18:59   ` Avichal Rakesh
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                       ` (3 more replies)
  2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  6 siblings, 4 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 18:59 UTC (permalink / raw)
  To: dan.scally, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, gregkh, m.grzeschik



On 10/19/23 11:53, Avichal Rakesh wrote:
> We have been seeing two main stability issues that uvc gadget driver
> runs into when stopping streams:
>  1. Attempting to queue usb_requests to a disabled usb_ep
>  2. use-after-free issue for inflight usb_requests
> 
> The four patches below fix the two issues above. Patch 1/4 fixes the
> first issue, and Patch 2/4 and 4/4 fix the second issue. Patch 3/4
> is only there to make the diff in 4/4 cleaner.
> 
> Avichal Rakesh (4):
>   usb: gadget: uvc: prevent use of disabled endpoint
>   usb: gadget: uvc: Allocate uvc_requests one at a time
>   usb: gadget: uvc: move video disable logic to its own function
>   usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> 
>  drivers/usb/gadget/function/f_uvc.c     |  11 +-
>  drivers/usb/gadget/function/f_uvc.h     |   2 +-
>  drivers/usb/gadget/function/uvc.h       |   6 +-
>  drivers/usb/gadget/function/uvc_v4l2.c  |  12 +-
>  drivers/usb/gadget/function/uvc_video.c | 231 +++++++++++++++++-------
>  5 files changed, 189 insertions(+), 73 deletions(-)
> 
> --
> 2.42.0.758.gaed0368e0e-goog

Dan and Laurent, please go over the patches whenever you get a
chance. I think they're ready to submit as neither Michael 
nor I have seen any use-after-free issues after the patches.

Thank you!
- Avi.


^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-19 18:53   ` [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-19 20:32     ` kernel test robot
  2023-10-19 22:30       ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: kernel test robot @ 2023-10-19 20:32 UTC (permalink / raw)
  To: Avichal Rakesh, dan.scally, gregkh, laurent.pinchart, m.grzeschik
  Cc: oe-kbuild-all, etalvala, jchowdhary, linux-kernel, linux-usb

Hi Avichal,

kernel test robot noticed the following build warnings:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
    * Must be called with req_lock held as it modifies the list ureq is held in


vim +231 drivers/usb/gadget/function/uvc_video.c

   225	
   226	/* --------------------------------------------------------------------------
   227	 * Request handling
   228	 */
   229	
   230	/**
 > 231	 * Must be called with req_lock held as it modifies the list ureq is held in
   232	 */
   233	static void
   234	uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
   235	{
   236		sg_free_table(&ureq->sgt);
   237		if (ureq->req && ep) {
   238			usb_ep_free_request(ep, ureq->req);
   239			ureq->req = NULL;
   240		}
   241	
   242		kfree(ureq->req_buffer);
   243		ureq->req_buffer = NULL;
   244	
   245		if (!list_empty(&ureq->list))
   246			list_del_init(&ureq->list);
   247	
   248		kfree(ureq);
   249	}
   250	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-19 20:32     ` kernel test robot
@ 2023-10-19 22:30       ` Avichal Rakesh
  2023-10-21 10:05         ` Greg KH
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-19 22:30 UTC (permalink / raw)
  To: gregkh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, dan.scally,
	laurent.pinchart, m.grzeschik



On 10/19/23 13:32, kernel test robot wrote:
> Hi Avichal,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on usb/usb-testing]
> [also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
> base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
> patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
> patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
> compiler: m68k-linux-gcc (GCC) 13.2.0
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/
> 
> All warnings (new ones prefixed by >>):
> 
>>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
>     * Must be called with req_lock held as it modifies the list ureq is held in
> 
> 

Greg, apologies for the newb question: do you want me to upload
the fix for this as a reply to [PATCH v6 4/4], or upload a new chain of 
v7s with this patch fixed? 

I am not familiar with the kernel merging process, so not sure 
which one would work better for you to pick up.

Regards,
Avi

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v7 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                     ` (4 preceding siblings ...)
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
@ 2023-10-20 17:36   ` Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                       ` (2 more replies)
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  6 siblings, 3 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-20 17:36 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Add Reviewed-by & Tested-by
v5 -> v6: No change
v6 -> v7: No change

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v7 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
@ 2023-10-20 17:36     ` Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-20 17:36 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
v5 -> v6: No change
v6 -> v7: No change

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
 2 files changed, 52 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..c180866c8e34 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v7 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-20 17:36     ` Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-20 17:36 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb, m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'.

Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v6: Introduced this patch to make the next one easier to review
v6 -> v7: Add Suggested-by

 drivers/usb/gadget/function/uvc_video.c | 37 +++++++++++++++----------
 1 file changed, 23 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c180866c8e34..80b8eaea2d39 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,13 +493,33 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }

+/*
+ * Disable video stream
+ */
+static int
+uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_request *ureq;
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -507,19 +527,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
-	}
+	if (!enable)
+		return uvcg_video_disable(video);

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v7 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-20 17:36     ` [PATCH v7 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-20 17:36     ` Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-20 17:36 UTC (permalink / raw)
  To: arakesh, dan.scally, laurent.pinchart
  Cc: etalvala, gregkh, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
v5 -> v6: Added another patch before this one to make uvcg_video_disable
          easier to review.
v6 -> v7: Fix warning reported in
          https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
 drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
 3 files changed, 111 insertions(+), 30 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..f4d2e24835d4 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_enable(video, 0);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 80b8eaea2d39..ab3f02054e85 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -499,17 +531,64 @@ static void uvcg_video_pump(struct work_struct *work)
 static int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;
+
+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);

 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -530,6 +609,14 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 	if (!enable)
 		return uvcg_video_disable(video);

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -555,6 +642,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-19 22:30       ` Avichal Rakesh
@ 2023-10-21 10:05         ` Greg KH
  2023-10-23 21:25           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Greg KH @ 2023-10-21 10:05 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, dan.scally,
	laurent.pinchart, m.grzeschik

On Thu, Oct 19, 2023 at 03:30:00PM -0700, Avichal Rakesh wrote:
> 
> 
> On 10/19/23 13:32, kernel test robot wrote:
> > Hi Avichal,
> > 
> > kernel test robot noticed the following build warnings:
> > 
> > [auto build test WARNING on usb/usb-testing]
> > [also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
> > [If your patch is applied to the wrong git tree, kindly drop us a note.
> > And when submitting patch, we suggest to use '--base' as documented in
> > https://git-scm.com/docs/git-format-patch#_base_tree_information]
> > 
> > url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
> > base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
> > patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
> > patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> > config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
> > compiler: m68k-linux-gcc (GCC) 13.2.0
> > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)
> > 
> > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > the same patch/commit), kindly add following tags
> > | Reported-by: kernel test robot <lkp@intel.com>
> > | Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/
> > 
> > All warnings (new ones prefixed by >>):
> > 
> >>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
> >     * Must be called with req_lock held as it modifies the list ureq is held in
> > 
> > 
> 
> Greg, apologies for the newb question: do you want me to upload
> the fix for this as a reply to [PATCH v6 4/4], or upload a new chain of 
> v7s with this patch fixed? 

A whole new v7 series please.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-21 10:05         ` Greg KH
@ 2023-10-23 21:25           ` Avichal Rakesh
  2023-10-24  9:27             ` Greg KH
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-23 21:25 UTC (permalink / raw)
  To: Greg KH
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, dan.scally,
	laurent.pinchart, m.grzeschik

On Sat, Oct 21, 2023 at 3:05 AM Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Thu, Oct 19, 2023 at 03:30:00PM -0700, Avichal Rakesh wrote:
> >
> >
> > On 10/19/23 13:32, kernel test robot wrote:
> > > Hi Avichal,
> > >
> > > kernel test robot noticed the following build warnings:
> > >
> > > [auto build test WARNING on usb/usb-testing]
> > > [also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
> > > [If your patch is applied to the wrong git tree, kindly drop us a note.
> > > And when submitting patch, we suggest to use '--base' as documented in
> > > https://git-scm.com/docs/git-format-patch#_base_tree_information]
> > >
> > > url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
> > > base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
> > > patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
> > > patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> > > config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
> > > compiler: m68k-linux-gcc (GCC) 13.2.0
> > > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)
> > >
> > > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > > the same patch/commit), kindly add following tags
> > > | Reported-by: kernel test robot <lkp@intel.com>
> > > | Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/
> > >
> > > All warnings (new ones prefixed by >>):
> > >
> > >>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
> > >     * Must be called with req_lock held as it modifies the list ureq is held in
> > >
> > >
> >
> > Greg, apologies for the newb question: do you want me to upload
> > the fix for this as a reply to [PATCH v6 4/4], or upload a new chain of
> > v7s with this patch fixed?
>
> A whole new v7 series please.
>

Had a feeling, so sent out v7 series preemptively. Let me know if that
doesn't work.

v7: https://lore.kernel.org/20231020173626.2978356-1-arakesh@google.com/

Thank you!
- Avi

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-23 21:25           ` Avichal Rakesh
@ 2023-10-24  9:27             ` Greg KH
  2023-10-24 20:00               ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Greg KH @ 2023-10-24  9:27 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, dan.scally,
	laurent.pinchart, m.grzeschik

On Mon, Oct 23, 2023 at 02:25:30PM -0700, Avichal Rakesh wrote:
> On Sat, Oct 21, 2023 at 3:05 AM Greg KH <gregkh@linuxfoundation.org> wrote:
> >
> > On Thu, Oct 19, 2023 at 03:30:00PM -0700, Avichal Rakesh wrote:
> > >
> > >
> > > On 10/19/23 13:32, kernel test robot wrote:
> > > > Hi Avichal,
> > > >
> > > > kernel test robot noticed the following build warnings:
> > > >
> > > > [auto build test WARNING on usb/usb-testing]
> > > > [also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
> > > > [If your patch is applied to the wrong git tree, kindly drop us a note.
> > > > And when submitting patch, we suggest to use '--base' as documented in
> > > > https://git-scm.com/docs/git-format-patch#_base_tree_information]
> > > >
> > > > url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
> > > > base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
> > > > patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
> > > > patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> > > > config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
> > > > compiler: m68k-linux-gcc (GCC) 13.2.0
> > > > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)
> > > >
> > > > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > > > the same patch/commit), kindly add following tags
> > > > | Reported-by: kernel test robot <lkp@intel.com>
> > > > | Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/
> > > >
> > > > All warnings (new ones prefixed by >>):
> > > >
> > > >>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
> > > >     * Must be called with req_lock held as it modifies the list ureq is held in
> > > >
> > > >
> > >
> > > Greg, apologies for the newb question: do you want me to upload
> > > the fix for this as a reply to [PATCH v6 4/4], or upload a new chain of
> > > v7s with this patch fixed?
> >
> > A whole new v7 series please.
> >
> 
> Had a feeling, so sent out v7 series preemptively. Let me know if that
> doesn't work.
> 
> v7: https://lore.kernel.org/20231020173626.2978356-1-arakesh@google.com/

I have already dropped that from my review queue as your emails crossed
with that, so I thought it was obsolete by now, sorry.

Can you send a v8 please?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                     ` (5 preceding siblings ...)
  2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
@ 2023-10-24 18:36   ` Avichal Rakesh
  2023-10-24 18:36     ` [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                       ` (3 more replies)
  6 siblings, 4 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-24 18:36 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Add Reviewed-by & Tested-by
v5 -> v6: No change
v6 -> v7: No change
v7 -> v8: No change. Getting back in review queue

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..e7f9f13f14dc 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-24 18:36     ` Avichal Rakesh
       [not found]       ` <421d1996-8544-45ac-9f31-551ef597546c@ideasonboard.com>
  2023-10-24 18:36     ` [PATCH v8 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-24 18:36 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
v5 -> v6: No change
v6 -> v7: No change
v7 -> v8: No change. Getting back in review queue

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
 2 files changed, 52 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..c180866c8e34 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	INIT_LIST_HEAD(&video->ureqs);
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v8 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-24 18:36     ` [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-24 18:36     ` Avichal Rakesh
  2023-10-24 18:36     ` [PATCH v8 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-10-26 20:23     ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-24 18:36 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'.

Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v6: Introduced this patch to make the next one easier to review
v6 -> v7: Add Suggested-by
v7 -> v8: No change. Getting back in review queue

 drivers/usb/gadget/function/uvc_video.c | 37 +++++++++++++++----------
 1 file changed, 23 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c180866c8e34..80b8eaea2d39 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,13 +493,33 @@ static void uvcg_video_pump(struct work_struct *work)
 	return;
 }

+/*
+ * Disable video stream
+ */
+static int
+uvcg_video_disable(struct uvc_video *video)
+{
+	struct uvc_request *ureq;
+
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);
+
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}
+
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
 /*
  * Enable or disable the video stream.
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
 	int ret;
-	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -507,19 +527,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
-
-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
-
-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
-	}
+	if (!enable)
+		return uvcg_video_disable(video);

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v8 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-24 18:36     ` [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-24 18:36     ` [PATCH v8 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-24 18:36     ` Avichal Rakesh
  2023-10-26 20:23     ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-24 18:36 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
v5 -> v6: Added another patch before this one to make uvcg_video_disable
          easier to review.
v6 -> v7: Fix warning reported in
          https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
v7 -> v8: No change. Getting back in review queue

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
 drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
 3 files changed, 111 insertions(+), 30 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..f4d2e24835d4 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_enable(video, 0);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 80b8eaea2d39..ab3f02054e85 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -499,17 +531,64 @@ static void uvcg_video_pump(struct work_struct *work)
 static int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;
+
+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);

 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -530,6 +609,14 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 	if (!enable)
 		return uvcg_video_disable(video);

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -555,6 +642,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.758.gaed0368e0e-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-24  9:27             ` Greg KH
@ 2023-10-24 20:00               ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-24 20:00 UTC (permalink / raw)
  To: Greg KH
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, dan.scally,
	laurent.pinchart, m.grzeschik

On Tue, Oct 24, 2023 at 2:27 AM Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Mon, Oct 23, 2023 at 02:25:30PM -0700, Avichal Rakesh wrote:
> > On Sat, Oct 21, 2023 at 3:05 AM Greg KH <gregkh@linuxfoundation.org> wrote:
> > >
> > > On Thu, Oct 19, 2023 at 03:30:00PM -0700, Avichal Rakesh wrote:
> > > >
> > > >
> > > > On 10/19/23 13:32, kernel test robot wrote:
> > > > > Hi Avichal,
> > > > >
> > > > > kernel test robot noticed the following build warnings:
> > > > >
> > > > > [auto build test WARNING on usb/usb-testing]
> > > > > [also build test WARNING on usb/usb-next usb/usb-linus linus/master v6.6-rc6 next-20231019]
> > > > > [If your patch is applied to the wrong git tree, kindly drop us a note.
> > > > > And when submitting patch, we suggest to use '--base' as documented in
> > > > > https://git-scm.com/docs/git-format-patch#_base_tree_information]
> > > > >
> > > > > url:    https://github.com/intel-lab-lkp/linux/commits/Avichal-Rakesh/usb-gadget-uvc-prevent-use-of-disabled-endpoint/20231020-025512
> > > > > base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
> > > > > patch link:    https://lore.kernel.org/r/20231019185319.2714000-5-arakesh%40google.com
> > > > > patch subject: [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
> > > > > config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/config)
> > > > > compiler: m68k-linux-gcc (GCC) 13.2.0
> > > > > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310200457.GwPPFuHX-lkp@intel.com/reproduce)
> > > > >
> > > > > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > > > > the same patch/commit), kindly add following tags
> > > > > | Reported-by: kernel test robot <lkp@intel.com>
> > > > > | Closes: https://lore.kernel.org/oe-kbuild-all/202310200457.GwPPFuHX-lkp@intel.com/
> > > > >
> > > > > All warnings (new ones prefixed by >>):
> > > > >
> > > > >>> drivers/usb/gadget/function/uvc_video.c:231: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
> > > > >     * Must be called with req_lock held as it modifies the list ureq is held in
> > > > >
> > > > >
> > > >
> > > > Greg, apologies for the newb question: do you want me to upload
> > > > the fix for this as a reply to [PATCH v6 4/4], or upload a new chain of
> > > > v7s with this patch fixed?
> > >
> > > A whole new v7 series please.
> > >
> >
> > Had a feeling, so sent out v7 series preemptively. Let me know if that
> > doesn't work.
> >
> > v7: https://lore.kernel.org/20231020173626.2978356-1-arakesh@google.com/
>
> I have already dropped that from my review queue as your emails crossed
> with that, so I thought it was obsolete by now, sorry.
>
> Can you send a v8 please?
>
Sent out v8! PTAL when you get the chance.

https://lore.kernel.org/20231024183605.908253-1-arakesh@google.com/

Thank you!
- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                       ` (2 preceding siblings ...)
  2023-10-24 18:36     ` [PATCH v8 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-26 20:23     ` Avichal Rakesh
  2023-10-27 10:51       ` Greg KH
  3 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-26 20:23 UTC (permalink / raw)
  To: gregkh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik,
	dan.scally, laurent.pinchart



On 10/24/23 11:36, Avichal Rakesh wrote:
> Currently the set_alt callback immediately disables the endpoint and queues
> the v4l2 streamoff event. However, as the streamoff event is processed
> asynchronously, it is possible that the video_pump thread attempts to queue
> requests to an already disabled endpoint.
> 
> This change moves disabling usb endpoint to the end of streamoff event
> callback. As the endpoint's state can no longer be used, video_pump is
> now guarded by uvc->state as well. To be consistent with the actual
> streaming state, uvc->state is now toggled between CONNECTED and STREAMING
> from the v4l2 event callback only.
> 
> Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
> Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v1 -> v2: Rebased to ToT and reworded commit message.
> v2 -> v3: Fix email threading goof-up
> v3 -> v4: Address review comments & re-rebase to ToT
> v4 -> v5: Add Reviewed-by & Tested-by
> v5 -> v6: No change
> v6 -> v7: No change
> v7 -> v8: No change. Getting back in review queue
> 
>  drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
>  drivers/usb/gadget/function/f_uvc.h     |  2 +-
>  drivers/usb/gadget/function/uvc.h       |  2 +-
>  drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
>  drivers/usb/gadget/function/uvc_video.c |  3 ++-
>  5 files changed, 26 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index faa398109431..ae08341961eb 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
>  	return 0;
>  }
> 
> -void uvc_function_setup_continue(struct uvc_device *uvc)
> +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
>  {
>  	struct usb_composite_dev *cdev = uvc->func.config->cdev;
> 
> +	if (disable_ep && uvc->video.ep)
> +		usb_ep_disable(uvc->video.ep);
> +
>  	usb_composite_setup_continue(cdev);
>  }
> 
> @@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
>  		if (uvc->state != UVC_STATE_STREAMING)
>  			return 0;
> 
> -		if (uvc->video.ep)
> -			usb_ep_disable(uvc->video.ep);
> -
>  		memset(&v4l2_event, 0, sizeof(v4l2_event));
>  		v4l2_event.type = UVC_EVENT_STREAMOFF;
>  		v4l2_event_queue(&uvc->vdev, &v4l2_event);
> 
> -		uvc->state = UVC_STATE_CONNECTED;
> -		return 0;
> +		return USB_GADGET_DELAYED_STATUS;
> 
>  	case 1:
>  		if (uvc->state != UVC_STATE_CONNECTED)
> diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
> index 1db972d4beeb..e7f9f13f14dc 100644
> --- a/drivers/usb/gadget/function/f_uvc.h
> +++ b/drivers/usb/gadget/function/f_uvc.h
> @@ -11,7 +11,7 @@
> 
>  struct uvc_device;
> 
> -void uvc_function_setup_continue(struct uvc_device *uvc);
> +void uvc_function_setup_continue(struct uvc_device *uvc, int disale_ep);
> 
>  void uvc_function_connect(struct uvc_device *uvc);
> 
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 6751de8b63ad..989bc6b4e93d 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -177,7 +177,7 @@ struct uvc_file_handle {
>   * Functions
>   */
> 
> -extern void uvc_function_setup_continue(struct uvc_device *uvc);
> +extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
>  extern void uvc_function_connect(struct uvc_device *uvc);
>  extern void uvc_function_disconnect(struct uvc_device *uvc);
> 
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 3f0a9795c0d4..7cb8d027ff0c 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>  	 * Complete the alternate setting selection setup phase now that
>  	 * userspace is ready to provide video frames.
>  	 */
> -	uvc_function_setup_continue(uvc);
> +	uvc_function_setup_continue(uvc, 0);
>  	uvc->state = UVC_STATE_STREAMING;
> 
>  	return 0;
> @@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>  	struct video_device *vdev = video_devdata(file);
>  	struct uvc_device *uvc = video_get_drvdata(vdev);
>  	struct uvc_video *video = &uvc->video;
> +	int ret = 0;
> 
>  	if (type != video->queue.queue.type)
>  		return -EINVAL;
> 
> -	return uvcg_video_enable(video, 0);
> +	uvc->state = UVC_STATE_CONNECTED;
> +	ret = uvcg_video_enable(video, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	uvc_function_setup_continue(uvc, 1);
> +	return 0;
>  }
> 
>  static int
> @@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>  static void uvc_v4l2_disable(struct uvc_device *uvc)
>  {
>  	uvc_function_disconnect(uvc);
> +	/*
> +	 * Drop uvc->state to CONNECTED if it was streaming before.
> +	 * This ensures that the usb_requests are no longer queued
> +	 * to the controller.
> +	 */
> +	if (uvc->state == UVC_STATE_STREAMING)
> +		uvc->state = UVC_STATE_CONNECTED;
> +
>  	uvcg_video_enable(&uvc->video, 0);
>  	uvcg_free_buffers(&uvc->video.queue);
>  	uvc->func_connected = false;
> @@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
>  	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
>  #endif
>  };
> -
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index 91af3b1ef0d4..c334802ac0a4 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
>  	struct uvc_video_queue *queue = &video->queue;
>  	/* video->max_payload_size is only set when using bulk transfer */
>  	bool is_bulk = video->max_payload_size;
> +	struct uvc_device *uvc = video->uvc;
>  	struct usb_request *req = NULL;
>  	struct uvc_buffer *buf;
>  	unsigned long flags;
>  	bool buf_done;
>  	int ret;
> 
> -	while (video->ep->enabled) {
> +	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>  		/*
>  		 * Retrieve the first available USB request, protected by the
>  		 * request lock.
> --
> 2.42.0.758.gaed0368e0e-goog

Hey Greg,

Considering Laurent and Dan haven't responded, and Michael and I have 
tested this change, would it be possible to merge this patch set
if the changes look OK to you? I don't think there are any outstanding
items to be done around these fixes.

Thank you!
- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-26 20:23     ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-27 10:51       ` Greg KH
  2023-10-27 10:52         ` Dan Scally
  0 siblings, 1 reply; 94+ messages in thread
From: Greg KH @ 2023-10-27 10:51 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik,
	dan.scally, laurent.pinchart

On Thu, Oct 26, 2023 at 01:23:44PM -0700, Avichal Rakesh wrote:
> Considering Laurent and Dan haven't responded, and Michael and I have 
> tested this change, would it be possible to merge this patch set
> if the changes look OK to you? I don't think there are any outstanding
> items to be done around these fixes.

I would like their review first please.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-27 10:51       ` Greg KH
@ 2023-10-27 10:52         ` Dan Scally
  0 siblings, 0 replies; 94+ messages in thread
From: Dan Scally @ 2023-10-27 10:52 UTC (permalink / raw)
  To: Greg KH, Avichal Rakesh
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik,
	laurent.pinchart

Good morning

On 27/10/2023 11:51, Greg KH wrote:
> On Thu, Oct 26, 2023 at 01:23:44PM -0700, Avichal Rakesh wrote:
>> Considering Laurent and Dan haven't responded, and Michael and I have
>> tested this change, would it be possible to merge this patch set
>> if the changes look OK to you? I don't think there are any outstanding
>> items to be done around these fixes.
> I would like their review first please.


Apologies for the delay - I am reviewing it now.

>
> thanks,
>
> greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
@ 2023-10-27 20:19     ` Avichal Rakesh
  2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                         ` (2 more replies)
  2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                       ` (2 subsequent siblings)
  3 siblings, 3 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-27 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT and reworded commit message.
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Add Reviewed-by & Tested-by
v5 -> v6: No change
v6 -> v7: No change
v7 -> v8: No change. Getting back in review queue
v8 -> v9: Fix typo. No functional change.

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index faa398109431..ae08341961eb 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..083aef0c65c6 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-27 20:19       ` Avichal Rakesh
  2023-10-28 10:31         ` Greg KH
  2023-10-28 20:13         ` Dan Scally
  2023-10-27 20:19       ` [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
  2023-10-27 20:19       ` [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 2 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-27 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT
v2 -> v3: Fix email threading goof-up
v3 -> v4: Address review comments & re-rebase to ToT
v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
v5 -> v6: No change
v6 -> v7: No change
v7 -> v8: No change. Getting back in review queue
v8 -> v9: Address review comments.

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
 2 files changed, 52 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..f8f9209fee50 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,39 +325,45 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;

 	BUG_ON(video->req_size);
+	BUG_ON(!list_empty(&video->ureqs));

 	req_size = video->ep->maxpacket
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-27 20:19       ` Avichal Rakesh
  2023-10-28 20:16         ` Dan Scally
  2023-10-27 20:19       ` [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-27 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'. This function
is now used anywhere uvcg_video_enable(video, 0) was used.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v6: Introduced this patch to make the next one easier to review
v6 -> v7: Add Suggested-by
v7 -> v8: No change. Getting back in review queue
v8 -> v9: Call uvcg_video_disable directly instead of uvcg_video_enable(video, 0)

 drivers/usb/gadget/function/uvc_v4l2.c  |  6 ++--
 drivers/usb/gadget/function/uvc_video.c | 40 ++++++++++++++++---------
 drivers/usb/gadget/function/uvc_video.h |  3 +-
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..904dd283cbf7 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	/* Enable UVC video. */
-	ret = uvcg_video_enable(video, 1);
+	ret = uvcg_video_enable(video);
 	if (ret < 0)
 		return ret;

@@ -469,7 +469,7 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	uvc->state = UVC_STATE_CONNECTED;
-	ret = uvcg_video_enable(video, 0);
+	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

@@ -515,7 +515,7 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
 	if (uvc->state == UVC_STATE_STREAMING)
 		uvc->state = UVC_STATE_CONNECTED;

-	uvcg_video_enable(&uvc->video, 0);
+	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
 	wake_up_interruptible(&uvc->func_connected_queue);
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index f8f9209fee50..1081dd790fd6 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -494,31 +494,43 @@ static void uvcg_video_pump(struct work_struct *work)
 }

 /*
- * Enable or disable the video stream.
+ * Disable the video stream
  */
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
 {
-	int ret;
 	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
-			  "Video enable failed, device is uninitialized.\n");
+			  "Video disable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);

-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}

-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
+/*
+ * Enable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+	int ret;
+
+	if (video->ep == NULL) {
+		uvcg_info(&video->uvc->func,
+			  "Video enable failed, device is uninitialized.\n");
+		return -ENODEV;
 	}

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
index 03adeefa343b..8ef6259741f1 100644
--- a/drivers/usb/gadget/function/uvc_video.h
+++ b/drivers/usb/gadget/function/uvc_video.h
@@ -14,7 +14,8 @@

 struct uvc_video;

-int uvcg_video_enable(struct uvc_video *video, int enable);
+int uvcg_video_enable(struct uvc_video *video);
+int uvcg_video_disable(struct uvc_video *video);

 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);

--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-27 20:19       ` [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-27 20:19       ` Avichal Rakesh
  2023-10-28 20:56         ` Dan Scally
  2 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-27 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2: Rebased to ToT, and fixed deadlock reported in
          https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3: Fix email threading goof-up
v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
          as discussed in
          https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
v5 -> v6: Added another patch before this one to make uvcg_video_disable
          easier to review.
v6 -> v7: Fix warning reported in
          https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
v7 -> v8: No change. Getting back in review queue
v8 -> v9: No change.

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
 drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
 3 files changed, 111 insertions(+), 30 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 904dd283cbf7..2f8634e05612 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;
+	uvc_function_setup_continue(uvc, 0);

 	return 0;
 }
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 1081dd790fd6..8f330ce696ec 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvc_video_disable ensures that
+		 * in-flight uvc_buffers are returned, so we can safely
+		 * call free_request without worrying about last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
 int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
       [not found]       ` <421d1996-8544-45ac-9f31-551ef597546c@ideasonboard.com>
@ 2023-10-27 20:31         ` Avichal Rakesh
  2023-10-28  5:30           ` Greg KH
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-27 20:31 UTC (permalink / raw)
  To: Dan Scally, gregkh, laurent.pinchart
  Cc: etalvala, jchowdhary, linux-kernel, linux-usb, m.grzeschik

Thank you for the reviews, Dan!

Uploaded v9 with the comments addressed.

On 10/27/23 05:57, Dan Scally wrote:
> Hi Avichal - thanks for the patch
> 
> On 24/10/2023 19:36, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> <snip>
>>
>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>> index c334802ac0a4..c180866c8e34 100644
>> --- a/drivers/usb/gadget/function/uvc_video.c
>> +++ b/drivers/usb/gadget/function/uvc_video.c
>> @@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>    * Request handling
>>    */
>>
>> +static void
>> +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>> +{
>> +    sg_free_table(&ureq->sgt);
>> +    if (ureq->req && ep) {
>> +        usb_ep_free_request(ep, ureq->req);
>> +        ureq->req = NULL;
>> +    }
>> +
>> +    kfree(ureq->req_buffer);
>> +    ureq->req_buffer = NULL;
>> +
>> +    if (!list_empty(&ureq->list))
> 
> 
> Is this conditional needed? You can only get here through the list_for_each_entry_safe()

Strictly speaking, we don't need this check right now. As you said, we currently
only get to this from within a list_for_each_entry_safe block. However, we end up
needing the check in the very next patch. Considering this is a function
with no real control over who might call it, it seemed reasonable to write 
this a little defensively in case of a partial revert of the patchset.

> 
>> +        list_del_init(&ureq->list);
>> +
>> +    kfree(ureq);
>> +}
>> +
>>  <snip>
>> @@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
>>   static int
>>   uvc_video_alloc_requests(struct uvc_video *video)
>>   {
>> +    struct uvc_request *ureq;
>>       unsigned int req_size;
>>       unsigned int i;
>>       int ret = -ENOMEM;
>> @@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
>>            * max_t(unsigned int, video->ep->maxburst, 1)
>>            * (video->ep->mult);
>>
>> -    video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
>> -    if (video->ureq == NULL)
>> -        return -ENOMEM;
>> +    INIT_LIST_HEAD(&video->ureqs);
> 
> 
> Probably unecessary here; it's done in uvc_video_free_requests() and uvcg_video_init() already

Ah, that is fair. Added a BUG_ON instead, like we do for video->req_size
so we still catch cases where the state might be inconsistent.

> 
>> +    for (i = 0; i < video->uvc_num_requests; i++) {
>> +        ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
>> +        if (ureq == NULL)
>> +            goto error;
>> +
>> +        INIT_LIST_HEAD(&ureq->list);
>> +
>> +        list_add_tail(&ureq->list, &video->ureqs);
>>
>> <snip>

Regards,
Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-27 20:31         ` Avichal Rakesh
@ 2023-10-28  5:30           ` Greg KH
  0 siblings, 0 replies; 94+ messages in thread
From: Greg KH @ 2023-10-28  5:30 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: Dan Scally, laurent.pinchart, etalvala, jchowdhary, linux-kernel,
	linux-usb, m.grzeschik

On Fri, Oct 27, 2023 at 01:31:26PM -0700, Avichal Rakesh wrote:
> >> @@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
> >>   static int
> >>   uvc_video_alloc_requests(struct uvc_video *video)
> >>   {
> >> +    struct uvc_request *ureq;
> >>       unsigned int req_size;
> >>       unsigned int i;
> >>       int ret = -ENOMEM;
> >> @@ -332,29 +336,34 @@ uvc_video_alloc_requests(struct uvc_video *video)
> >>            * max_t(unsigned int, video->ep->maxburst, 1)
> >>            * (video->ep->mult);
> >>
> >> -    video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
> >> -    if (video->ureq == NULL)
> >> -        return -ENOMEM;
> >> +    INIT_LIST_HEAD(&video->ureqs);
> > 
> > 
> > Probably unecessary here; it's done in uvc_video_free_requests() and uvcg_video_init() already
> 
> Ah, that is fair. Added a BUG_ON instead, like we do for video->req_size
> so we still catch cases where the state might be inconsistent.

Please no, that means you just crashed a machine and all data is lost
and the user will get very mad.

Either handle the error properly or it's something that can never happen
and so you don't need to handle it.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-28 10:31         ` Greg KH
  2023-10-28 20:13         ` Dan Scally
  1 sibling, 0 replies; 94+ messages in thread
From: Greg KH @ 2023-10-28 10:31 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: dan.scally, etalvala, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

On Fri, Oct 27, 2023 at 01:19:57PM -0700, Avichal Rakesh wrote:
>  	BUG_ON(video->req_size);
> +	BUG_ON(!list_empty(&video->ureqs));

Again, please do not add new BUG_ON() lines, the existing ones need to
be removed as well, but you can do that in later changes.  I can't take
changes that add new ones, sorry.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-28 10:31         ` Greg KH
@ 2023-10-28 20:13         ` Dan Scally
  2023-10-30 20:26           ` Avichal Rakesh
  1 sibling, 1 reply; 94+ messages in thread
From: Dan Scally @ 2023-10-28 20:13 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hi Avichal

On 27/10/2023 21:19, Avichal Rakesh wrote:
> Currently, the uvc gadget driver allocates all uvc_requests as one array
> and deallocates them all when the video stream stops. This includes
> de-allocating all the usb_requests associated with those uvc_requests.
> This can lead to use-after-free issues if any of those de-allocated
> usb_requests were still owned by the usb controller.
>
> This patch is 1 of 2 patches addressing the use-after-free issue.
> Instead of bulk allocating all uvc_requests as an array, this patch
> allocates uvc_requests one at a time, which should allows for similar
> granularity when deallocating the uvc_requests. This patch has no
> functional changes other than allocating each uvc_request separately,
> and similarly freeing each of them separately.
>
> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>


Sorry - I was unclear in my response to the first patch on v8. I meant my R-b to apply to the first 
patch only rather than to all of them. For this one I understand now the use of the conditional in 
uvc_video_free_request(), so that point is fine. I agree with Greg that the BUG_ON() shouldn't stand 
though.

> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v1 -> v2: Rebased to ToT
> v2 -> v3: Fix email threading goof-up
> v3 -> v4: Address review comments & re-rebase to ToT
> v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
> v5 -> v6: No change
> v6 -> v7: No change
> v7 -> v8: No change. Getting back in review queue
> v8 -> v9: Address review comments.
>
>   drivers/usb/gadget/function/uvc.h       |  3 +-
>   drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
>   2 files changed, 52 insertions(+), 40 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 989bc6b4e93d..993694da0bbc 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -81,6 +81,7 @@ struct uvc_request {
>   	struct sg_table sgt;
>   	u8 header[UVCG_REQUEST_HEADER_LEN];
>   	struct uvc_buffer *last_buf;
> +	struct list_head list;
>   };
>
>   struct uvc_video {
> @@ -102,7 +103,7 @@ struct uvc_video {
>
>   	/* Requests */
>   	unsigned int req_size;
> -	struct uvc_request *ureq;
> +	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>   	struct list_head req_free;
>   	spinlock_t req_lock;
>
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index c334802ac0a4..f8f9209fee50 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>    * Request handling
>    */
>
> +static void
> +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
> +{
> +	sg_free_table(&ureq->sgt);
> +	if (ureq->req && ep) {
> +		usb_ep_free_request(ep, ureq->req);
> +		ureq->req = NULL;
> +	}
> +
> +	kfree(ureq->req_buffer);
> +	ureq->req_buffer = NULL;
> +
> +	if (!list_empty(&ureq->list))
> +		list_del_init(&ureq->list);
> +
> +	kfree(ureq);
> +}
> +
>   static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
>   {
>   	int ret;
> @@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   static int
>   uvc_video_free_requests(struct uvc_video *video)
>   {
> -	unsigned int i;
> -
> -	if (video->ureq) {
> -		for (i = 0; i < video->uvc_num_requests; ++i) {
> -			sg_free_table(&video->ureq[i].sgt);
> +	struct uvc_request *ureq, *temp;
>
> -			if (video->ureq[i].req) {
> -				usb_ep_free_request(video->ep, video->ureq[i].req);
> -				video->ureq[i].req = NULL;
> -			}
> -
> -			if (video->ureq[i].req_buffer) {
> -				kfree(video->ureq[i].req_buffer);
> -				video->ureq[i].req_buffer = NULL;
> -			}
> -		}
> -
> -		kfree(video->ureq);
> -		video->ureq = NULL;
> -	}
> +	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
> +		uvc_video_free_request(ureq, video->ep);
>
> +	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	video->req_size = 0;
>   	return 0;
> @@ -322,39 +325,45 @@ uvc_video_free_requests(struct uvc_video *video)
>   static int
>   uvc_video_alloc_requests(struct uvc_video *video)
>   {
> +	struct uvc_request *ureq;
>   	unsigned int req_size;
>   	unsigned int i;
>   	int ret = -ENOMEM;
>
>   	BUG_ON(video->req_size);
> +	BUG_ON(!list_empty(&video->ureqs));
>
>   	req_size = video->ep->maxpacket
>   		 * max_t(unsigned int, video->ep->maxburst, 1)
>   		 * (video->ep->mult);
>
> -	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
> -	if (video->ureq == NULL)
> -		return -ENOMEM;
> +	for (i = 0; i < video->uvc_num_requests; i++) {
> +		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
> +		if (ureq == NULL)
> +			goto error;
> +
> +		INIT_LIST_HEAD(&ureq->list);
> +
> +		list_add_tail(&ureq->list, &video->ureqs);
>
> -	for (i = 0; i < video->uvc_num_requests; ++i) {
> -		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
> -		if (video->ureq[i].req_buffer == NULL)
> +		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
> +		if (ureq->req_buffer == NULL)
>   			goto error;
>
> -		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
> -		if (video->ureq[i].req == NULL)
> +		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
> +		if (ureq->req == NULL)
>   			goto error;
>
> -		video->ureq[i].req->buf = video->ureq[i].req_buffer;
> -		video->ureq[i].req->length = 0;
> -		video->ureq[i].req->complete = uvc_video_complete;
> -		video->ureq[i].req->context = &video->ureq[i];
> -		video->ureq[i].video = video;
> -		video->ureq[i].last_buf = NULL;
> +		ureq->req->buf = ureq->req_buffer;
> +		ureq->req->length = 0;
> +		ureq->req->complete = uvc_video_complete;
> +		ureq->req->context = ureq;
> +		ureq->video = video;
> +		ureq->last_buf = NULL;
>
> -		list_add_tail(&video->ureq[i].req->list, &video->req_free);
> +		list_add_tail(&ureq->req->list, &video->req_free);
>   		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
> -		sg_alloc_table(&video->ureq[i].sgt,
> +		sg_alloc_table(&ureq->sgt,
>   			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
>   					    PAGE_SIZE) + 2, GFP_KERNEL);
>   	}
> @@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
>    */
>   int uvcg_video_enable(struct uvc_video *video, int enable)
>   {
> -	unsigned int i;
>   	int ret;
> +	struct uvc_request *ureq;
>
>   	if (video->ep == NULL) {
>   		uvcg_info(&video->uvc->func,
> @@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>   		cancel_work_sync(&video->pump);
>   		uvcg_queue_cancel(&video->queue, 0);
>
> -		for (i = 0; i < video->uvc_num_requests; ++i)
> -			if (video->ureq && video->ureq[i].req)
> -				usb_ep_dequeue(video->ep, video->ureq[i].req);
> +		list_for_each_entry(ureq, &video->ureqs, list) {
> +			if (ureq->req)
> +				usb_ep_dequeue(video->ep, ureq->req);
> +		}
>
>   		uvc_video_free_requests(video);
>   		uvcg_queue_enable(&video->queue, 0);
> @@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>    */
>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>   {
> +	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	spin_lock_init(&video->req_lock);
>   	INIT_WORK(&video->pump, uvcg_video_pump);
> --
> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-27 20:19       ` [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-28 20:16         ` Dan Scally
  0 siblings, 0 replies; 94+ messages in thread
From: Dan Scally @ 2023-10-28 20:16 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hi Avichal

On 27/10/2023 21:19, Avichal Rakesh wrote:
> This patch refactors the video disable logic in uvcg_video_enable
> into its own separate function 'uvcg_video_disable'. This function
> is now used anywhere uvcg_video_enable(video, 0) was used.
>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>


For this patch you can keep the R-b - it's fine by me now :)

> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v6: Introduced this patch to make the next one easier to review
> v6 -> v7: Add Suggested-by
> v7 -> v8: No change. Getting back in review queue
> v8 -> v9: Call uvcg_video_disable directly instead of uvcg_video_enable(video, 0)
>
>   drivers/usb/gadget/function/uvc_v4l2.c  |  6 ++--
>   drivers/usb/gadget/function/uvc_video.c | 40 ++++++++++++++++---------
>   drivers/usb/gadget/function/uvc_video.h |  3 +-
>   3 files changed, 31 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 7cb8d027ff0c..904dd283cbf7 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>   		return -EINVAL;
>
>   	/* Enable UVC video. */
> -	ret = uvcg_video_enable(video, 1);
> +	ret = uvcg_video_enable(video);
>   	if (ret < 0)
>   		return ret;
>
> @@ -469,7 +469,7 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>   		return -EINVAL;
>
>   	uvc->state = UVC_STATE_CONNECTED;
> -	ret = uvcg_video_enable(video, 0);
> +	ret = uvcg_video_disable(video);
>   	if (ret < 0)
>   		return ret;
>
> @@ -515,7 +515,7 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
>   	if (uvc->state == UVC_STATE_STREAMING)
>   		uvc->state = UVC_STATE_CONNECTED;
>
> -	uvcg_video_enable(&uvc->video, 0);
> +	uvcg_video_disable(&uvc->video);
>   	uvcg_free_buffers(&uvc->video.queue);
>   	uvc->func_connected = false;
>   	wake_up_interruptible(&uvc->func_connected_queue);
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index f8f9209fee50..1081dd790fd6 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -494,31 +494,43 @@ static void uvcg_video_pump(struct work_struct *work)
>   }
>
>   /*
> - * Enable or disable the video stream.
> + * Disable the video stream
>    */
> -int uvcg_video_enable(struct uvc_video *video, int enable)
> +int
> +uvcg_video_disable(struct uvc_video *video)
>   {
> -	int ret;
>   	struct uvc_request *ureq;
>
>   	if (video->ep == NULL) {
>   		uvcg_info(&video->uvc->func,
> -			  "Video enable failed, device is uninitialized.\n");
> +			  "Video disable failed, device is uninitialized.\n");
>   		return -ENODEV;
>   	}
>
> -	if (!enable) {
> -		cancel_work_sync(&video->pump);
> -		uvcg_queue_cancel(&video->queue, 0);
> +	cancel_work_sync(&video->pump);
> +	uvcg_queue_cancel(&video->queue, 0);
>
> -		list_for_each_entry(ureq, &video->ureqs, list) {
> -			if (ureq->req)
> -				usb_ep_dequeue(video->ep, ureq->req);
> -		}
> +	list_for_each_entry(ureq, &video->ureqs, list) {
> +		if (ureq->req)
> +			usb_ep_dequeue(video->ep, ureq->req);
> +	}
>
> -		uvc_video_free_requests(video);
> -		uvcg_queue_enable(&video->queue, 0);
> -		return 0;
> +	uvc_video_free_requests(video);
> +	uvcg_queue_enable(&video->queue, 0);
> +	return 0;
> +}
> +
> +/*
> + * Enable the video stream.
> + */
> +int uvcg_video_enable(struct uvc_video *video)
> +{
> +	int ret;
> +
> +	if (video->ep == NULL) {
> +		uvcg_info(&video->uvc->func,
> +			  "Video enable failed, device is uninitialized.\n");
> +		return -ENODEV;
>   	}
>
>   	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
> diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
> index 03adeefa343b..8ef6259741f1 100644
> --- a/drivers/usb/gadget/function/uvc_video.h
> +++ b/drivers/usb/gadget/function/uvc_video.h
> @@ -14,7 +14,8 @@
>
>   struct uvc_video;
>
> -int uvcg_video_enable(struct uvc_video *video, int enable);
> +int uvcg_video_enable(struct uvc_video *video);
> +int uvcg_video_disable(struct uvc_video *video);
>
>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);
>
> --
> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-27 20:19       ` [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-10-28 20:56         ` Dan Scally
  2023-10-30 20:56           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Dan Scally @ 2023-10-28 20:56 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hi Avichal

On 27/10/2023 21:19, Avichal Rakesh wrote:
> Currently, the uvc gadget driver allocates all uvc_requests as one array
> and deallocates them all when the video stream stops. This includes
> de-allocating all the usb_requests associated with those uvc_requests.
> This can lead to use-after-free issues if any of those de-allocated
> usb_requests were still owned by the usb controller.
>
> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
> flag to uvc_video to track when frames and requests should be flowing.
> When disabling the video stream, the flag is tripped and, instead
> of de-allocating all uvc_requests and usb_requests, the gadget
> driver only de-allocates those usb_requests that are currently
> owned by it (as present in req_free). Other usb_requests are left
> untouched until their completion handler is called which takes care
> of freeing the usb_request and its corresponding uvc_request.
>
> Now that uvc_video does not depends on uvc->state, this patch removes
> unnecessary upates to uvc->state that were made to accommodate uvc_video
> logic. This should ensure that uvc gadget driver never accidentally
> de-allocates a usb_request that it doesn't own.
>
> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>            https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
> v2 -> v3: Fix email threading goof-up
> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>            as discussed in
>            https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
> v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
> v5 -> v6: Added another patch before this one to make uvcg_video_disable
>            easier to review.
> v6 -> v7: Fix warning reported in
>            https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
> v7 -> v8: No change. Getting back in review queue
> v8 -> v9: No change.
>
>   drivers/usb/gadget/function/uvc.h       |   1 +
>   drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
>   drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
>   3 files changed, 111 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 993694da0bbc..be0d012aa244 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -102,6 +102,7 @@ struct uvc_video {
>   	unsigned int uvc_num_requests;
>
>   	/* Requests */
> +	bool is_enabled; /* tracks whether video stream is enabled */
>   	unsigned int req_size;
>   	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>   	struct list_head req_free;
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 904dd283cbf7..2f8634e05612 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>   	 * Complete the alternate setting selection setup phase now that
>   	 * userspace is ready to provide video frames.
>   	 */
> -	uvc_function_setup_continue(uvc, 0);
>   	uvc->state = UVC_STATE_STREAMING;
> +	uvc_function_setup_continue(uvc, 0);
>
>   	return 0;
>   }
> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>   	if (type != video->queue.queue.type)
>   		return -EINVAL;
>
> -	uvc->state = UVC_STATE_CONNECTED;
>   	ret = uvcg_video_disable(video);
>   	if (ret < 0)
>   		return ret;
>
> +	uvc->state = UVC_STATE_CONNECTED;
>   	uvc_function_setup_continue(uvc, 1);
>   	return 0;
>   }


I'm not sure I understand what these re-orderings are for...can you explain please?

> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>   static void uvc_v4l2_disable(struct uvc_device *uvc)
>   {
>   	uvc_function_disconnect(uvc);
> -	/*
> -	 * Drop uvc->state to CONNECTED if it was streaming before.
> -	 * This ensures that the usb_requests are no longer queued
> -	 * to the controller.
> -	 */
> -	if (uvc->state == UVC_STATE_STREAMING)
> -		uvc->state = UVC_STATE_CONNECTED;
> -
>   	uvcg_video_disable(&uvc->video);
>   	uvcg_free_buffers(&uvc->video.queue);
>   	uvc->func_connected = false;
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index 1081dd790fd6..8f330ce696ec 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>    * Request handling
>    */
>
> +/*
> + * Must be called with req_lock held as it modifies the list ureq is held in
> + */



This comment probably belongs in patch #2. And in that case, shouldn't uvc_video_free_requests() 
hold the lock in that patch?

>   static void
>   uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>   {
> @@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   	struct uvc_request *ureq = req->context;
>   	struct uvc_video *video = ureq->video;
>   	struct uvc_video_queue *queue = &video->queue;
> -	struct uvc_device *uvc = video->uvc;
> +	struct uvc_buffer *last_buf = NULL;
>   	unsigned long flags;
>
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	if (!video->is_enabled) {
> +		/*
> +		 * When is_enabled is false, uvc_video_disable ensures that
s/uvc_video_disable/uvc_video_disable()
> +		 * in-flight uvc_buffers are returned, so we can safely
> +		 * call free_request without worrying about last_buf.
> +		 */
> +		uvc_video_free_request(ureq, ep);
Now I understand the conditional in this function in patch 2 :)
> +		spin_unlock_irqrestore(&video->req_lock, flags);
> +		return;
> +	}
> +
> +	last_buf = ureq->last_buf;
> +	ureq->last_buf = NULL;
> +	spin_unlock_irqrestore(&video->req_lock, flags);


I'm not a huge fan of this locking, unlocking and relocking the same spinlock within the same 
function. Can we just hold the lock for the duration? if not, can there be an explanatory comment as 
to why?
> +
>   	switch (req->status) {
>   	case 0:
>   		break;
> @@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   		uvcg_queue_cancel(queue, 0);
>   	}
>
> -	if (ureq->last_buf) {
> -		uvcg_complete_buffer(&video->queue, ureq->last_buf);
> -		ureq->last_buf = NULL;
> +	if (last_buf) {
> +		spin_lock_irqsave(&queue->irqlock, flags);
> +		uvcg_complete_buffer(&video->queue, last_buf);
> +		spin_unlock_irqrestore(&queue->irqlock, flags);



I think it's right to take the irqlock here but it probably should have always been held, so this 
probably ought to go in its own commit with a Fixes:

>   	}
>
>   	spin_lock_irqsave(&video->req_lock, flags);
> -	list_add_tail(&req->list, &video->req_free);
> -	spin_unlock_irqrestore(&video->req_lock, flags);
> -
> -	if (uvc->state == UVC_STATE_STREAMING)
> +	/*
> +	 * Video stream might have been disabled while we were
> +	 * processing the current usb_request. So make sure
> +	 * we're still streaming before queueing the usb_request
> +	 * back to req_free
> +	 */
> +	if (video->is_enabled) {
> +		list_add_tail(&req->list, &video->req_free);
>   		queue_work(video->async_wq, &video->pump);
> +	} else {
> +		uvc_video_free_request(ureq, ep);
> +	}
> +	spin_unlock_irqrestore(&video->req_lock, flags);
>   }
>
>   static int
> @@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
>   	struct uvc_video_queue *queue = &video->queue;
>   	/* video->max_payload_size is only set when using bulk transfer */
>   	bool is_bulk = video->max_payload_size;
> -	struct uvc_device *uvc = video->uvc;
>   	struct usb_request *req = NULL;
>   	struct uvc_buffer *buf;
>   	unsigned long flags;
>   	bool buf_done;
>   	int ret;
>
> -	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
> +	while (true) {
> +		if (!video->ep->enabled)
> +			return;
> +
>   		/*
> -		 * Retrieve the first available USB request, protected by the
> -		 * request lock.
> +		 * Check is_enabled and retrieve the first available USB
> +		 * request, protected by the request lock.
>   		 */
>   		spin_lock_irqsave(&video->req_lock, flags);
> -		if (list_empty(&video->req_free)) {
> +		if (!video->is_enabled || list_empty(&video->req_free)) {
>   			spin_unlock_irqrestore(&video->req_lock, flags);
>   			return;
>   		}
> @@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
>   		return;
>
>   	spin_lock_irqsave(&video->req_lock, flags);
> -	list_add_tail(&req->list, &video->req_free);
> +	if (video->is_enabled)
> +		list_add_tail(&req->list, &video->req_free);
> +	else
> +		uvc_video_free_request(req->context, video->ep);
>   	spin_unlock_irqrestore(&video->req_lock, flags);
> -	return;
>   }
>
>   /*
> @@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
>   int
>   uvcg_video_disable(struct uvc_video *video)
>   {
> -	struct uvc_request *ureq;
> +	unsigned long flags;
> +	struct list_head inflight_bufs;
> +	struct usb_request *req, *temp;
> +	struct uvc_buffer *buf, *btemp;
> +	struct uvc_request *ureq, *utemp;
>
>   	if (video->ep == NULL) {
>   		uvcg_info(&video->uvc->func,
> @@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
>   		return -ENODEV;
>   	}
>
> +	INIT_LIST_HEAD(&inflight_bufs);
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	video->is_enabled = false;
> +
> +	/*
> +	 * Remove any in-flight buffers from the uvc_requests
> +	 * because we want to return them before cancelling the
> +	 * queue. This ensures that we aren't stuck waiting for
> +	 * all complete callbacks to come through before disabling
> +	 * vb2 queue.
> +	 */
> +	list_for_each_entry(ureq, &video->ureqs, list) {
> +		if (ureq->last_buf) {
> +			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
> +			ureq->last_buf = NULL;
> +		}
> +	}
> +	spin_unlock_irqrestore(&video->req_lock, flags);
> +
>   	cancel_work_sync(&video->pump);
>   	uvcg_queue_cancel(&video->queue, 0);
>
> -	list_for_each_entry(ureq, &video->ureqs, list) {
> -		if (ureq->req)
> -			usb_ep_dequeue(video->ep, ureq->req);
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	/*
> +	 * Remove all uvc_reqeusts from ureqs with list_del_init
> +	 * This lets uvc_video_free_request correctly identify
> +	 * if the uvc_request is attached to a list or not when freeing
> +	 * memory.
> +	 */
> +	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
> +		list_del_init(&ureq->list);
> +
> +	list_for_each_entry_safe(req, temp, &video->req_free, list) {
> +		list_del(&req->list);
> +		uvc_video_free_request(req->context, video->ep);
>   	}
>
> -	uvc_video_free_requests(video);
> +	INIT_LIST_HEAD(&video->ureqs);
> +	INIT_LIST_HEAD(&video->req_free);
> +	video->req_size = 0;
> +	spin_unlock_irqrestore(&video->req_lock, flags);
> +
> +	/*
> +	 * Return all the video buffers before disabling the queue.
> +	 */
> +	spin_lock_irqsave(&video->queue.irqlock, flags);
> +	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
> +		list_del(&buf->queue);
> +		uvcg_complete_buffer(&video->queue, buf);
> +	}
> +	spin_unlock_irqrestore(&video->queue.irqlock, flags);
> +
>   	uvcg_queue_enable(&video->queue, 0);
>   	return 0;
>   }
> @@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
>   		return -ENODEV;
>   	}
>
> +	/*
> +	 * Safe to access request related fields without req_lock because
> +	 * this is the only thread currently active, and no other
> +	 * request handling thread will become active until this function
> +	 * returns.
> +	 */
> +	video->is_enabled = true;
> +
>   	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>   		return ret;
>
> @@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
>    */
>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>   {
> +	video->is_enabled = false;
>   	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	spin_lock_init(&video->req_lock);
> --
> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-30 20:22     ` Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                         ` (2 more replies)
  2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 3 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:22 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2 : Rebased to ToT and reworded commit message.
v2 -> v3 : Fix email threading goof-up
v3 -> v4 : Address review comments & re-rebase to ToT
v4 -> v5 : Add Reviewed-by & Tested-by
v5 -> v6 : No change
v6 -> v7 : No change
v7 -> v8 : No change. Getting back in review queue
v8 -> v9 : Fix typo. No functional change.
v9 -> v10: Rebase to ToT (usb-next)

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 786379f1b7b7..77999ed53d33 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..083aef0c65c6 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-10-30 20:22       ` Avichal Rakesh
  2023-11-01 11:06         ` Dan Scally
  2023-10-30 20:22       ` [PATCH v10 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:22 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2 : Rebased to ToT
v2 -> v3 : Fix email threading goof-up
v3 -> v4 : Address review comments & re-rebase to ToT
v4 -> v5 : Address more review comments. Add Reviewed-by & Tested-by.
v5 -> v6 : No change
v6 -> v7 : No change
v7 -> v8 : No change. Getting back in review queue
v8 -> v9 : Address review comments.
v9 -> v10: Address review comments; remove BUG_ON(&video->reqs);
           Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 88 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..1619f9664748 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +497,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +510,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +545,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v10 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-10-30 20:22       ` Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:22 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'. This function
is now used anywhere uvcg_video_enable(video, 0) was used.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v6       : Introduced this patch to make the next one easier to review
v6 -> v7 : Add Suggested-by
v7 -> v8 : No change. Getting back in review queue
v8 -> v9 : Call uvcg_video_disable directly instead of uvcg_video_enable(video, 0)
v9 -> v10: Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc_v4l2.c  |  6 ++--
 drivers/usb/gadget/function/uvc_video.c | 40 ++++++++++++++++---------
 drivers/usb/gadget/function/uvc_video.h |  3 +-
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..904dd283cbf7 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	/* Enable UVC video. */
-	ret = uvcg_video_enable(video, 1);
+	ret = uvcg_video_enable(video);
 	if (ret < 0)
 		return ret;

@@ -469,7 +469,7 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	uvc->state = UVC_STATE_CONNECTED;
-	ret = uvcg_video_enable(video, 0);
+	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

@@ -515,7 +515,7 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
 	if (uvc->state == UVC_STATE_STREAMING)
 		uvc->state = UVC_STATE_CONNECTED;

-	uvcg_video_enable(&uvc->video, 0);
+	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
 	wake_up_interruptible(&uvc->func_connected_queue);
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 1619f9664748..c3e8c48f46a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,31 +493,43 @@ static void uvcg_video_pump(struct work_struct *work)
 }

 /*
- * Enable or disable the video stream.
+ * Disable the video stream
  */
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
 {
-	int ret;
 	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
-			  "Video enable failed, device is uninitialized.\n");
+			  "Video disable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);

-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}

-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
+/*
+ * Enable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+	int ret;
+
+	if (video->ep == NULL) {
+		uvcg_info(&video->uvc->func,
+			  "Video enable failed, device is uninitialized.\n");
+		return -ENODEV;
 	}

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
index 03adeefa343b..8ef6259741f1 100644
--- a/drivers/usb/gadget/function/uvc_video.h
+++ b/drivers/usb/gadget/function/uvc_video.h
@@ -14,7 +14,8 @@

 struct uvc_video;

-int uvcg_video_enable(struct uvc_video *video, int enable);
+int uvcg_video_enable(struct uvc_video *video);
+int uvcg_video_disable(struct uvc_video *video);

 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);

--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v10 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-10-30 20:22       ` [PATCH v10 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-10-30 20:22       ` Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:22 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1 -> v2 : Rebased to ToT, and fixed deadlock reported in
           https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2 -> v3 : Fix email threading goof-up
v3 -> v4 : re-rebase to ToT & moved to a uvc_video level lock
           as discussed in
           https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4 -> v5 : Address review comments. Add Reviewed-by & Tested-by.
v5 -> v6 : Added another patch before this one to make uvcg_video_disable
           easier to review.
v6 -> v7 : Fix warning reported in
           https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
v7 -> v8 : No change. Getting back in review queue
v8 -> v9 : No change.
v9 -> v10: Address review comments. Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  10 +-
 drivers/usb/gadget/function/uvc_video.c | 129 ++++++++++++++++++++----
 3 files changed, 111 insertions(+), 29 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 904dd283cbf7..c7e5fa4f29e0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c3e8c48f46a9..53feb790a4c3 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Must be called with req_lock held as it modifies the list ureq is held in
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +274,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf = NULL;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvcg_video_disable() ensures
+		 * that in-flight uvc_buffers are returned, so we can
+		 * safely call free_request without worrying about
+		 * last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +315,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(&video->queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -392,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -487,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -498,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
 int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -506,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_reqeusts from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -532,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -557,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.820.g83a721a137-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-28 20:13         ` Dan Scally
@ 2023-10-30 20:26           ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:26 UTC (permalink / raw)
  To: Dan Scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik



On 10/28/23 13:13, Dan Scally wrote:
> Hi Avichal
> 
> On 27/10/2023 21:19, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This patch is 1 of 2 patches addressing the use-after-free issue.
>> Instead of bulk allocating all uvc_requests as an array, this patch
>> allocates uvc_requests one at a time, which should allows for similar
>> granularity when deallocating the uvc_requests. This patch has no
>> functional changes other than allocating each uvc_request separately,
>> and similarly freeing each of them separately.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> 
> 
> Sorry - I was unclear in my response to the first patch on v8. I meant my R-b to apply to the first patch only rather than to all of them. For this one I understand now the use of the conditional in uvc_video_free_request(), so that point is fine. I agree with Greg that the BUG_ON() shouldn't stand though.

Ah, didn't realize BUG_ON is discouraged. Removed BUG_ON. 
It was supposed to be a defensive bit of code anyway, 
so removing the check entirely. If the state is 
inconsistent, we'd see other errors, so the BUG_ON 
wasn't providing value anyway.

Also removed your Reviewed-by, my apologies.

> 
>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>> ---
>> v1 -> v2: Rebased to ToT
>> v2 -> v3: Fix email threading goof-up
>> v3 -> v4: Address review comments & re-rebase to ToT
>> v4 -> v5: Address more review comments. Add Reviewed-by & Tested-by.
>> v5 -> v6: No change
>> v6 -> v7: No change
>> v7 -> v8: No change. Getting back in review queue
>> v8 -> v9: Address review comments.
>>
>>   drivers/usb/gadget/function/uvc.h       |  3 +-
>>   drivers/usb/gadget/function/uvc_video.c | 89 ++++++++++++++-----------
>>   2 files changed, 52 insertions(+), 40 deletions(-)
>>
>> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>> index 989bc6b4e93d..993694da0bbc 100644
>> --- a/drivers/usb/gadget/function/uvc.h
>> +++ b/drivers/usb/gadget/function/uvc.h
>> @@ -81,6 +81,7 @@ struct uvc_request {
>>       struct sg_table sgt;
>>       u8 header[UVCG_REQUEST_HEADER_LEN];
>>       struct uvc_buffer *last_buf;
>> +    struct list_head list;
>>   };
>>
>>   struct uvc_video {
>> @@ -102,7 +103,7 @@ struct uvc_video {
>>
>>       /* Requests */
>>       unsigned int req_size;
>> -    struct uvc_request *ureq;
>> +    struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>>       struct list_head req_free;
>>       spinlock_t req_lock;
>>
>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>> index c334802ac0a4..f8f9209fee50 100644
>> --- a/drivers/usb/gadget/function/uvc_video.c
>> +++ b/drivers/usb/gadget/function/uvc_video.c
>> @@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>    * Request handling
>>    */
>>
>> +static void
>> +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>> +{
>> +    sg_free_table(&ureq->sgt);
>> +    if (ureq->req && ep) {
>> +        usb_ep_free_request(ep, ureq->req);
>> +        ureq->req = NULL;
>> +    }
>> +
>> +    kfree(ureq->req_buffer);
>> +    ureq->req_buffer = NULL;
>> +
>> +    if (!list_empty(&ureq->list))
>> +        list_del_init(&ureq->list);
>> +
>> +    kfree(ureq);
>> +}
>> +
>>   static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
>>   {
>>       int ret;
>> @@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>   static int
>>   uvc_video_free_requests(struct uvc_video *video)
>>   {
>> -    unsigned int i;
>> -
>> -    if (video->ureq) {
>> -        for (i = 0; i < video->uvc_num_requests; ++i) {
>> -            sg_free_table(&video->ureq[i].sgt);
>> +    struct uvc_request *ureq, *temp;
>>
>> -            if (video->ureq[i].req) {
>> -                usb_ep_free_request(video->ep, video->ureq[i].req);
>> -                video->ureq[i].req = NULL;
>> -            }
>> -
>> -            if (video->ureq[i].req_buffer) {
>> -                kfree(video->ureq[i].req_buffer);
>> -                video->ureq[i].req_buffer = NULL;
>> -            }
>> -        }
>> -
>> -        kfree(video->ureq);
>> -        video->ureq = NULL;
>> -    }
>> +    list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
>> +        uvc_video_free_request(ureq, video->ep);
>>
>> +    INIT_LIST_HEAD(&video->ureqs);
>>       INIT_LIST_HEAD(&video->req_free);
>>       video->req_size = 0;
>>       return 0;
>> @@ -322,39 +325,45 @@ uvc_video_free_requests(struct uvc_video *video)
>>   static int
>>   uvc_video_alloc_requests(struct uvc_video *video)
>>   {
>> +    struct uvc_request *ureq;
>>       unsigned int req_size;
>>       unsigned int i;
>>       int ret = -ENOMEM;
>>
>>       BUG_ON(video->req_size);
>> +    BUG_ON(!list_empty(&video->ureqs));
>>
>>       req_size = video->ep->maxpacket
>>            * max_t(unsigned int, video->ep->maxburst, 1)
>>            * (video->ep->mult);
>>
>> -    video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
>> -    if (video->ureq == NULL)
>> -        return -ENOMEM;
>> +    for (i = 0; i < video->uvc_num_requests; i++) {
>> +        ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
>> +        if (ureq == NULL)
>> +            goto error;
>> +
>> +        INIT_LIST_HEAD(&ureq->list);
>> +
>> +        list_add_tail(&ureq->list, &video->ureqs);
>>
>> -    for (i = 0; i < video->uvc_num_requests; ++i) {
>> -        video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
>> -        if (video->ureq[i].req_buffer == NULL)
>> +        ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
>> +        if (ureq->req_buffer == NULL)
>>               goto error;
>>
>> -        video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>> -        if (video->ureq[i].req == NULL)
>> +        ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
>> +        if (ureq->req == NULL)
>>               goto error;
>>
>> -        video->ureq[i].req->buf = video->ureq[i].req_buffer;
>> -        video->ureq[i].req->length = 0;
>> -        video->ureq[i].req->complete = uvc_video_complete;
>> -        video->ureq[i].req->context = &video->ureq[i];
>> -        video->ureq[i].video = video;
>> -        video->ureq[i].last_buf = NULL;
>> +        ureq->req->buf = ureq->req_buffer;
>> +        ureq->req->length = 0;
>> +        ureq->req->complete = uvc_video_complete;
>> +        ureq->req->context = ureq;
>> +        ureq->video = video;
>> +        ureq->last_buf = NULL;
>>
>> -        list_add_tail(&video->ureq[i].req->list, &video->req_free);
>> +        list_add_tail(&ureq->req->list, &video->req_free);
>>           /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
>> -        sg_alloc_table(&video->ureq[i].sgt,
>> +        sg_alloc_table(&ureq->sgt,
>>                      DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
>>                           PAGE_SIZE) + 2, GFP_KERNEL);
>>       }
>> @@ -489,8 +498,8 @@ static void uvcg_video_pump(struct work_struct *work)
>>    */
>>   int uvcg_video_enable(struct uvc_video *video, int enable)
>>   {
>> -    unsigned int i;
>>       int ret;
>> +    struct uvc_request *ureq;
>>
>>       if (video->ep == NULL) {
>>           uvcg_info(&video->uvc->func,
>> @@ -502,9 +511,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>>           cancel_work_sync(&video->pump);
>>           uvcg_queue_cancel(&video->queue, 0);
>>
>> -        for (i = 0; i < video->uvc_num_requests; ++i)
>> -            if (video->ureq && video->ureq[i].req)
>> -                usb_ep_dequeue(video->ep, video->ureq[i].req);
>> +        list_for_each_entry(ureq, &video->ureqs, list) {
>> +            if (ureq->req)
>> +                usb_ep_dequeue(video->ep, ureq->req);
>> +        }
>>
>>           uvc_video_free_requests(video);
>>           uvcg_queue_enable(&video->queue, 0);
>> @@ -536,6 +546,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>>    */
>>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>>   {
>> +    INIT_LIST_HEAD(&video->ureqs);
>>       INIT_LIST_HEAD(&video->req_free);
>>       spin_lock_init(&video->req_lock);
>>       INIT_WORK(&video->pump, uvcg_video_pump);
>> -- 
>> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-28 20:56         ` Dan Scally
@ 2023-10-30 20:56           ` Avichal Rakesh
  2023-11-02 13:29             ` Dan Scally
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-10-30 20:56 UTC (permalink / raw)
  To: Dan Scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Thank you for taking a look Dan!

On 10/28/23 13:56, Dan Scally wrote:
> Hi Avichal
> 
> On 27/10/2023 21:19, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>> flag to uvc_video to track when frames and requests should be flowing.
>> When disabling the video stream, the flag is tripped and, instead
>> of de-allocating all uvc_requests and usb_requests, the gadget
>> driver only de-allocates those usb_requests that are currently
>> owned by it (as present in req_free). Other usb_requests are left
>> untouched until their completion handler is called which takes care
>> of freeing the usb_request and its corresponding uvc_request.
>>
>> Now that uvc_video does not depends on uvc->state, this patch removes
>> unnecessary upates to uvc->state that were made to accommodate uvc_video
>> logic. This should ensure that uvc gadget driver never accidentally
>> de-allocates a usb_request that it doesn't own.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>> ---
>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>            https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>> v2 -> v3: Fix email threading goof-up
>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>            as discussed in
>>            https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>> v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
>> v5 -> v6: Added another patch before this one to make uvcg_video_disable
>>            easier to review.
>> v6 -> v7: Fix warning reported in
>>            https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
>> v7 -> v8: No change. Getting back in review queue
>> v8 -> v9: No change.
>>
>>   drivers/usb/gadget/function/uvc.h       |   1 +
>>   drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
>>   drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
>>   3 files changed, 111 insertions(+), 30 deletions(-)
>>
>> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>> index 993694da0bbc..be0d012aa244 100644
>> --- a/drivers/usb/gadget/function/uvc.h
>> +++ b/drivers/usb/gadget/function/uvc.h
>> @@ -102,6 +102,7 @@ struct uvc_video {
>>       unsigned int uvc_num_requests;
>>
>>       /* Requests */
>> +    bool is_enabled; /* tracks whether video stream is enabled */
>>       unsigned int req_size;
>>       struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>>       struct list_head req_free;
>> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>> index 904dd283cbf7..2f8634e05612 100644
>> --- a/drivers/usb/gadget/function/uvc_v4l2.c
>> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
>> @@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>>        * Complete the alternate setting selection setup phase now that
>>        * userspace is ready to provide video frames.
>>        */
>> -    uvc_function_setup_continue(uvc, 0);
>>       uvc->state = UVC_STATE_STREAMING;
>> +    uvc_function_setup_continue(uvc, 0);
>>
>>       return 0;
>>   }
>> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>>       if (type != video->queue.queue.type)
>>           return -EINVAL;
>>
>> -    uvc->state = UVC_STATE_CONNECTED;
>>       ret = uvcg_video_disable(video);
>>       if (ret < 0)
>>           return ret;
>>
>> +    uvc->state = UVC_STATE_CONNECTED;
>>       uvc_function_setup_continue(uvc, 1);
>>       return 0;
>>   }
> 
> 
> I'm not sure I understand what these re-orderings are for...can you explain please?

This specific one was a leftover from testing, removed this hunk.
But the ones below are undoing the change in patch 1, which is
flawed in its use of uvc->state without any memory guarantees.

So from patch 1 to patch 4, we shuffle the code around a bit,
but this makes patch 1 somewhat complete and functional even if 
patch 4 were to be reverted.

> 
>> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>>   static void uvc_v4l2_disable(struct uvc_device *uvc)
>>   {
>>       uvc_function_disconnect(uvc);
>> -    /*
>> -     * Drop uvc->state to CONNECTED if it was streaming before.
>> -     * This ensures that the usb_requests are no longer queued
>> -     * to the controller.
>> -     */
>> -    if (uvc->state == UVC_STATE_STREAMING)
>> -        uvc->state = UVC_STATE_CONNECTED;
>> -
>>       uvcg_video_disable(&uvc->video);
>>       uvcg_free_buffers(&uvc->video.queue);
>>       uvc->func_connected = false;
>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>> index 1081dd790fd6..8f330ce696ec 100644
>> --- a/drivers/usb/gadget/function/uvc_video.c
>> +++ b/drivers/usb/gadget/function/uvc_video.c
>> @@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>    * Request handling
>>    */
>>
>> +/*
>> + * Must be called with req_lock held as it modifies the list ureq is held in
>> + */
> 
> 
> 
> This comment probably belongs in patch #2. And in that case, shouldn't uvc_video_free_requests() hold the lock in that patch?

Patch 2 doesn't change any existing locking semantics. The current
code does not enforce any locking on freeing the requests, and neither
does patch 2. 

Patch 4 introduces another call site for uvc_video_free_request, so
some synchronization guarantees are needed (and hence the addition
of this comment).

As for uvc_video_free_requests not holding the lock, it is safe because
uvc_video_free_requests is only called if request initialization fails.
So uvc_video_free_requests should be the thread safe, as no other thread
is processing requests when it is called.

I did add a comment in uvcg_video_enable mentioning why it is safe to
not hold req_free even though it accesses request related fields.

Happy to add another comment to uvc_video_free_requests if that makes it 
clearer!

> 
>>   static void
>>   uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>>   {
>> @@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>       struct uvc_request *ureq = req->context;
>>       struct uvc_video *video = ureq->video;
>>       struct uvc_video_queue *queue = &video->queue;
>> -    struct uvc_device *uvc = video->uvc;
>> +    struct uvc_buffer *last_buf = NULL;
>>       unsigned long flags;
>>
>> +    spin_lock_irqsave(&video->req_lock, flags);
>> +    if (!video->is_enabled) {
>> +        /*
>> +         * When is_enabled is false, uvc_video_disable ensures that
> s/uvc_video_disable/uvc_video_disable()

Done!

>> +         * in-flight uvc_buffers are returned, so we can safely
>> +         * call free_request without worrying about last_buf.
>> +         */
>> +        uvc_video_free_request(ureq, ep);
> Now I understand the conditional in this function in patch 2 :)
>> +        spin_unlock_irqrestore(&video->req_lock, flags);
>> +        return;
>> +    }
>> +
>> +    last_buf = ureq->last_buf;
>> +    ureq->last_buf = NULL;
>> +    spin_unlock_irqrestore(&video->req_lock, flags);
> 
> 
> I'm not a huge fan of this locking, unlocking and relocking the same spinlock within the same function. Can we just hold the lock for the duration? if not, can there be an explanatory comment as to why?

I agree that this is a little unfortunate, and it'd be nice if we
only had a single driver level lock. However, as it stands, if 
we hold req_lock for the entirety of completion handler, we risk
two things:

1. Adding dependencies between queue->irqlock and video->reqlock
2. Starving the video_pump thread. 

As of this patch, uvc_video_complete follows the same pattern as
video_pump function:
1. Acquire req_lock
2. Fetch/Query usb_request
3. Drop req_lock

4. Acquire queue->irqlock
5. Buffer ops (encode/free/stop)
6. Drop queue->irqlock

7. Acquire req_lock
8. usb_request cleanup/handling
9. Drop req_lock

(7), (8), and (9) are optional for video_pump, while
(4), (5), and (6) are optional for uvc_video_complete.

We can short-circuit uvc_video_complete with only one lock
on the happy path, but this would have to be the flow for
non-happy paths unless we want to hold the two locks at 
the same time (which isn't the worst idea, but comes with 
its own set of concerns).


>> +
>>       switch (req->status) {
>>       case 0:
>>           break;
>> @@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>           uvcg_queue_cancel(queue, 0);
>>       }
>>
>> -    if (ureq->last_buf) {
>> -        uvcg_complete_buffer(&video->queue, ureq->last_buf);
>> -        ureq->last_buf = NULL;
>> +    if (last_buf) {
>> +        spin_lock_irqsave(&queue->irqlock, flags);
>> +        uvcg_complete_buffer(&video->queue, last_buf);
>> +        spin_unlock_irqrestore(&queue->irqlock, flags);
> 
> 
> 
> I think it's right to take the irqlock here but it probably should have always been held, so this probably ought to go in its own commit with a Fixes:

The lock here wasn't required before, because uvcg_complete_buffer was
only ever called by the completion handler, which is synchronized by
the usb controller. This is the reason we never saw an issue despite
not holding the lock.

This patch introduces another call site in uvcg_video_disable, so to
protect memory consistency, we need to make sure calls to 
uvcg_complete_buffer are synchronized on something other than
the usb controller.

> 
>>       }
>>
>>       spin_lock_irqsave(&video->req_lock, flags);
>> -    list_add_tail(&req->list, &video->req_free);
>> -    spin_unlock_irqrestore(&video->req_lock, flags);
>> -
>> -    if (uvc->state == UVC_STATE_STREAMING)
>> +    /*
>> +     * Video stream might have been disabled while we were
>> +     * processing the current usb_request. So make sure
>> +     * we're still streaming before queueing the usb_request
>> +     * back to req_free
>> +     */
>> +    if (video->is_enabled) {
>> +        list_add_tail(&req->list, &video->req_free);
>>           queue_work(video->async_wq, &video->pump);
>> +    } else {
>> +        uvc_video_free_request(ureq, ep);
>> +    }
>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>   }
>>
>>   static int
>> @@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>       struct uvc_video_queue *queue = &video->queue;
>>       /* video->max_payload_size is only set when using bulk transfer */
>>       bool is_bulk = video->max_payload_size;
>> -    struct uvc_device *uvc = video->uvc;
>>       struct usb_request *req = NULL;
>>       struct uvc_buffer *buf;
>>       unsigned long flags;
>>       bool buf_done;
>>       int ret;
>>
>> -    while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>> +    while (true) {
>> +        if (!video->ep->enabled)
>> +            return;
>> +
>>           /*
>> -         * Retrieve the first available USB request, protected by the
>> -         * request lock.
>> +         * Check is_enabled and retrieve the first available USB
>> +         * request, protected by the request lock.
>>            */
>>           spin_lock_irqsave(&video->req_lock, flags);
>> -        if (list_empty(&video->req_free)) {
>> +        if (!video->is_enabled || list_empty(&video->req_free)) {
>>               spin_unlock_irqrestore(&video->req_lock, flags);
>>               return;
>>           }
>> @@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>           return;
>>
>>       spin_lock_irqsave(&video->req_lock, flags);
>> -    list_add_tail(&req->list, &video->req_free);
>> +    if (video->is_enabled)
>> +        list_add_tail(&req->list, &video->req_free);
>> +    else
>> +        uvc_video_free_request(req->context, video->ep);
>>       spin_unlock_irqrestore(&video->req_lock, flags);
>> -    return;
>>   }
>>
>>   /*
>> @@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>   int
>>   uvcg_video_disable(struct uvc_video *video)
>>   {
>> -    struct uvc_request *ureq;
>> +    unsigned long flags;
>> +    struct list_head inflight_bufs;
>> +    struct usb_request *req, *temp;
>> +    struct uvc_buffer *buf, *btemp;
>> +    struct uvc_request *ureq, *utemp;
>>
>>       if (video->ep == NULL) {
>>           uvcg_info(&video->uvc->func,
>> @@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
>>           return -ENODEV;
>>       }
>>
>> +    INIT_LIST_HEAD(&inflight_bufs);
>> +    spin_lock_irqsave(&video->req_lock, flags);
>> +    video->is_enabled = false;
>> +
>> +    /*
>> +     * Remove any in-flight buffers from the uvc_requests
>> +     * because we want to return them before cancelling the
>> +     * queue. This ensures that we aren't stuck waiting for
>> +     * all complete callbacks to come through before disabling
>> +     * vb2 queue.
>> +     */
>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>> +        if (ureq->last_buf) {
>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>> +            ureq->last_buf = NULL;
>> +        }
>> +    }
>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>> +
>>       cancel_work_sync(&video->pump);
>>       uvcg_queue_cancel(&video->queue, 0);
>>
>> -    list_for_each_entry(ureq, &video->ureqs, list) {
>> -        if (ureq->req)
>> -            usb_ep_dequeue(video->ep, ureq->req);
>> +    spin_lock_irqsave(&video->req_lock, flags);
>> +    /*
>> +     * Remove all uvc_reqeusts from ureqs with list_del_init
>> +     * This lets uvc_video_free_request correctly identify
>> +     * if the uvc_request is attached to a list or not when freeing
>> +     * memory.
>> +     */
>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>> +        list_del_init(&ureq->list);
>> +
>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>> +        list_del(&req->list);
>> +        uvc_video_free_request(req->context, video->ep);
>>       }
>>
>> -    uvc_video_free_requests(video);
>> +    INIT_LIST_HEAD(&video->ureqs);
>> +    INIT_LIST_HEAD(&video->req_free);
>> +    video->req_size = 0;
>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>> +
>> +    /*
>> +     * Return all the video buffers before disabling the queue.
>> +     */
>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>> +        list_del(&buf->queue);
>> +        uvcg_complete_buffer(&video->queue, buf);
>> +    }
>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>> +
>>       uvcg_queue_enable(&video->queue, 0);
>>       return 0;
>>   }
>> @@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
>>           return -ENODEV;
>>       }
>>
>> +    /*
>> +     * Safe to access request related fields without req_lock because
>> +     * this is the only thread currently active, and no other
>> +     * request handling thread will become active until this function
>> +     * returns.
>> +     */
>> +    video->is_enabled = true;
>> +
>>       if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>>           return ret;
>>
>> @@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
>>    */
>>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>>   {
>> +    video->is_enabled = false;
>>       INIT_LIST_HEAD(&video->ureqs);
>>       INIT_LIST_HEAD(&video->req_free);
>>       spin_lock_init(&video->req_lock);
>> -- 
>> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-10-30 20:22       ` [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-11-01 11:06         ` Dan Scally
  2023-11-01 22:13           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Dan Scally @ 2023-11-01 11:06 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Morning Avichal

On 30/10/2023 20:22, Avichal Rakesh wrote:
> Currently, the uvc gadget driver allocates all uvc_requests as one array
> and deallocates them all when the video stream stops. This includes
> de-allocating all the usb_requests associated with those uvc_requests.
> This can lead to use-after-free issues if any of those de-allocated
> usb_requests were still owned by the usb controller.
>
> This patch is 1 of 2 patches addressing the use-after-free issue.
> Instead of bulk allocating all uvc_requests as an array, this patch
> allocates uvc_requests one at a time, which should allows for similar
> granularity when deallocating the uvc_requests. This patch has no
> functional changes other than allocating each uvc_request separately,
> and similarly freeing each of them separately.
>
> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>


Thanks for the update; this seems ok now:


Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

> ---
> v1 -> v2 : Rebased to ToT
> v2 -> v3 : Fix email threading goof-up
> v3 -> v4 : Address review comments & re-rebase to ToT
> v4 -> v5 : Address more review comments. Add Reviewed-by & Tested-by.
> v5 -> v6 : No change
> v6 -> v7 : No change
> v7 -> v8 : No change. Getting back in review queue
> v8 -> v9 : Address review comments.
> v9 -> v10: Address review comments; remove BUG_ON(&video->reqs);
>             Rebase to ToT (usb-next)
>
>   drivers/usb/gadget/function/uvc.h       |  3 +-
>   drivers/usb/gadget/function/uvc_video.c | 88 ++++++++++++++-----------
>   2 files changed, 51 insertions(+), 40 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 989bc6b4e93d..993694da0bbc 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -81,6 +81,7 @@ struct uvc_request {
>   	struct sg_table sgt;
>   	u8 header[UVCG_REQUEST_HEADER_LEN];
>   	struct uvc_buffer *last_buf;
> +	struct list_head list;
>   };
>
>   struct uvc_video {
> @@ -102,7 +103,7 @@ struct uvc_video {
>
>   	/* Requests */
>   	unsigned int req_size;
> -	struct uvc_request *ureq;
> +	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>   	struct list_head req_free;
>   	spinlock_t req_lock;
>
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index c334802ac0a4..1619f9664748 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>    * Request handling
>    */
>
> +static void
> +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
> +{
> +	sg_free_table(&ureq->sgt);
> +	if (ureq->req && ep) {
> +		usb_ep_free_request(ep, ureq->req);
> +		ureq->req = NULL;
> +	}
> +
> +	kfree(ureq->req_buffer);
> +	ureq->req_buffer = NULL;
> +
> +	if (!list_empty(&ureq->list))
> +		list_del_init(&ureq->list);
> +
> +	kfree(ureq);
> +}
> +
>   static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
>   {
>   	int ret;
> @@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   static int
>   uvc_video_free_requests(struct uvc_video *video)
>   {
> -	unsigned int i;
> -
> -	if (video->ureq) {
> -		for (i = 0; i < video->uvc_num_requests; ++i) {
> -			sg_free_table(&video->ureq[i].sgt);
> +	struct uvc_request *ureq, *temp;
>
> -			if (video->ureq[i].req) {
> -				usb_ep_free_request(video->ep, video->ureq[i].req);
> -				video->ureq[i].req = NULL;
> -			}
> -
> -			if (video->ureq[i].req_buffer) {
> -				kfree(video->ureq[i].req_buffer);
> -				video->ureq[i].req_buffer = NULL;
> -			}
> -		}
> -
> -		kfree(video->ureq);
> -		video->ureq = NULL;
> -	}
> +	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
> +		uvc_video_free_request(ureq, video->ep);
>
> +	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	video->req_size = 0;
>   	return 0;
> @@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
>   static int
>   uvc_video_alloc_requests(struct uvc_video *video)
>   {
> +	struct uvc_request *ureq;
>   	unsigned int req_size;
>   	unsigned int i;
>   	int ret = -ENOMEM;
> @@ -332,29 +336,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
>   		 * max_t(unsigned int, video->ep->maxburst, 1)
>   		 * (video->ep->mult);
>
> -	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
> -	if (video->ureq == NULL)
> -		return -ENOMEM;
> +	for (i = 0; i < video->uvc_num_requests; i++) {
> +		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
> +		if (ureq == NULL)
> +			goto error;
> +
> +		INIT_LIST_HEAD(&ureq->list);
> +
> +		list_add_tail(&ureq->list, &video->ureqs);
>
> -	for (i = 0; i < video->uvc_num_requests; ++i) {
> -		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
> -		if (video->ureq[i].req_buffer == NULL)
> +		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
> +		if (ureq->req_buffer == NULL)
>   			goto error;
>
> -		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
> -		if (video->ureq[i].req == NULL)
> +		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
> +		if (ureq->req == NULL)
>   			goto error;
>
> -		video->ureq[i].req->buf = video->ureq[i].req_buffer;
> -		video->ureq[i].req->length = 0;
> -		video->ureq[i].req->complete = uvc_video_complete;
> -		video->ureq[i].req->context = &video->ureq[i];
> -		video->ureq[i].video = video;
> -		video->ureq[i].last_buf = NULL;
> +		ureq->req->buf = ureq->req_buffer;
> +		ureq->req->length = 0;
> +		ureq->req->complete = uvc_video_complete;
> +		ureq->req->context = ureq;
> +		ureq->video = video;
> +		ureq->last_buf = NULL;
>
> -		list_add_tail(&video->ureq[i].req->list, &video->req_free);
> +		list_add_tail(&ureq->req->list, &video->req_free);
>   		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
> -		sg_alloc_table(&video->ureq[i].sgt,
> +		sg_alloc_table(&ureq->sgt,
>   			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
>   					    PAGE_SIZE) + 2, GFP_KERNEL);
>   	}
> @@ -489,8 +497,8 @@ static void uvcg_video_pump(struct work_struct *work)
>    */
>   int uvcg_video_enable(struct uvc_video *video, int enable)
>   {
> -	unsigned int i;
>   	int ret;
> +	struct uvc_request *ureq;
>
>   	if (video->ep == NULL) {
>   		uvcg_info(&video->uvc->func,
> @@ -502,9 +510,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>   		cancel_work_sync(&video->pump);
>   		uvcg_queue_cancel(&video->queue, 0);
>
> -		for (i = 0; i < video->uvc_num_requests; ++i)
> -			if (video->ureq && video->ureq[i].req)
> -				usb_ep_dequeue(video->ep, video->ureq[i].req);
> +		list_for_each_entry(ureq, &video->ureqs, list) {
> +			if (ureq->req)
> +				usb_ep_dequeue(video->ep, ureq->req);
> +		}
>
>   		uvc_video_free_requests(video);
>   		uvcg_queue_enable(&video->queue, 0);
> @@ -536,6 +545,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>    */
>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>   {
> +	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	spin_lock_init(&video->req_lock);
>   	INIT_WORK(&video->pump, uvcg_video_pump);
> --
> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-11-01 11:06         ` Dan Scally
@ 2023-11-01 22:13           ` Avichal Rakesh
  2023-11-02 11:38             ` Dan Scally
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-01 22:13 UTC (permalink / raw)
  To: Dan Scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik



On 11/1/23 04:06, Dan Scally wrote:
> Morning Avichal
> 
> On 30/10/2023 20:22, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This patch is 1 of 2 patches addressing the use-after-free issue.
>> Instead of bulk allocating all uvc_requests as an array, this patch
>> allocates uvc_requests one at a time, which should allows for similar
>> granularity when deallocating the uvc_requests. This patch has no
>> functional changes other than allocating each uvc_request separately,
>> and similarly freeing each of them separately.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> 
> 
> Thanks for the update; this seems ok now:
> 
> 
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

Awesome, thank you! I'll add the Reviewed-by in the next patchset
(assuming you have more review comments on patch 4/4 v10). 

Regards,
Avi.

> 
>> ---
>> v1 -> v2 : Rebased to ToT
>> v2 -> v3 : Fix email threading goof-up
>> v3 -> v4 : Address review comments & re-rebase to ToT
>> v4 -> v5 : Address more review comments. Add Reviewed-by & Tested-by.
>> v5 -> v6 : No change
>> v6 -> v7 : No change
>> v7 -> v8 : No change. Getting back in review queue
>> v8 -> v9 : Address review comments.
>> v9 -> v10: Address review comments; remove BUG_ON(&video->reqs);
>>             Rebase to ToT (usb-next)
>>
>>   <snip>

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-11-01 22:13           ` Avichal Rakesh
@ 2023-11-02 11:38             ` Dan Scally
  0 siblings, 0 replies; 94+ messages in thread
From: Dan Scally @ 2023-11-02 11:38 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik


On 01/11/2023 22:13, Avichal Rakesh wrote:
>
> On 11/1/23 04:06, Dan Scally wrote:
>> Morning Avichal
>>
>> On 30/10/2023 20:22, Avichal Rakesh wrote:
>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>> and deallocates them all when the video stream stops. This includes
>>> de-allocating all the usb_requests associated with those uvc_requests.
>>> This can lead to use-after-free issues if any of those de-allocated
>>> usb_requests were still owned by the usb controller.
>>>
>>> This patch is 1 of 2 patches addressing the use-after-free issue.
>>> Instead of bulk allocating all uvc_requests as an array, this patch
>>> allocates uvc_requests one at a time, which should allows for similar
>>> granularity when deallocating the uvc_requests. This patch has no
>>> functional changes other than allocating each uvc_request separately,
>>> and similarly freeing each of them separately.
>>>
>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>
>> Thanks for the update; this seems ok now:
>>
>>
>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Awesome, thank you! I'll add the Reviewed-by in the next patchset
> (assuming you have more review comments on patch 4/4 v10).


Sorry yes - taking me a while to wrap my head around everything but I hope to be done shortly!

>
> Regards,
> Avi.
>
>>> ---
>>> v1 -> v2 : Rebased to ToT
>>> v2 -> v3 : Fix email threading goof-up
>>> v3 -> v4 : Address review comments & re-rebase to ToT
>>> v4 -> v5 : Address more review comments. Add Reviewed-by & Tested-by.
>>> v5 -> v6 : No change
>>> v6 -> v7 : No change
>>> v7 -> v8 : No change. Getting back in review queue
>>> v8 -> v9 : Address review comments.
>>> v9 -> v10: Address review comments; remove BUG_ON(&video->reqs);
>>>              Rebase to ToT (usb-next)
>>>
>>>    <snip>

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-10-30 20:56           ` Avichal Rakesh
@ 2023-11-02 13:29             ` Dan Scally
  2023-11-02 20:39               ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Dan Scally @ 2023-11-02 13:29 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hi Avichal

On 30/10/2023 20:56, Avichal Rakesh wrote:
> Thank you for taking a look Dan!
>
> On 10/28/23 13:56, Dan Scally wrote:
>> Hi Avichal
>>
>> On 27/10/2023 21:19, Avichal Rakesh wrote:
>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>> and deallocates them all when the video stream stops. This includes
>>> de-allocating all the usb_requests associated with those uvc_requests.
>>> This can lead to use-after-free issues if any of those de-allocated
>>> usb_requests were still owned by the usb controller.
>>>
>>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>>> flag to uvc_video to track when frames and requests should be flowing.
>>> When disabling the video stream, the flag is tripped and, instead
>>> of de-allocating all uvc_requests and usb_requests, the gadget
>>> driver only de-allocates those usb_requests that are currently
>>> owned by it (as present in req_free). Other usb_requests are left
>>> untouched until their completion handler is called which takes care
>>> of freeing the usb_request and its corresponding uvc_request.
>>>
>>> Now that uvc_video does not depends on uvc->state, this patch removes
>>> unnecessary upates to uvc->state that were made to accommodate uvc_video
>>> logic. This should ensure that uvc gadget driver never accidentally
>>> de-allocates a usb_request that it doesn't own.
>>>
>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>> ---
>>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>>             https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>>> v2 -> v3: Fix email threading goof-up
>>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>>             as discussed in
>>>             https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>>> v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
>>> v5 -> v6: Added another patch before this one to make uvcg_video_disable
>>>             easier to review.
>>> v6 -> v7: Fix warning reported in
>>>             https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
>>> v7 -> v8: No change. Getting back in review queue
>>> v8 -> v9: No change.
>>>
>>>    drivers/usb/gadget/function/uvc.h       |   1 +
>>>    drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
>>>    drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
>>>    3 files changed, 111 insertions(+), 30 deletions(-)
>>>
>>> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>>> index 993694da0bbc..be0d012aa244 100644
>>> --- a/drivers/usb/gadget/function/uvc.h
>>> +++ b/drivers/usb/gadget/function/uvc.h
>>> @@ -102,6 +102,7 @@ struct uvc_video {
>>>        unsigned int uvc_num_requests;
>>>
>>>        /* Requests */
>>> +    bool is_enabled; /* tracks whether video stream is enabled */
>>>        unsigned int req_size;
>>>        struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>>>        struct list_head req_free;
>>> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>>> index 904dd283cbf7..2f8634e05612 100644
>>> --- a/drivers/usb/gadget/function/uvc_v4l2.c
>>> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
>>> @@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>>>         * Complete the alternate setting selection setup phase now that
>>>         * userspace is ready to provide video frames.
>>>         */
>>> -    uvc_function_setup_continue(uvc, 0);
>>>        uvc->state = UVC_STATE_STREAMING;
>>> +    uvc_function_setup_continue(uvc, 0);
>>>
>>>        return 0;
>>>    }
>>> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>>>        if (type != video->queue.queue.type)
>>>            return -EINVAL;
>>>
>>> -    uvc->state = UVC_STATE_CONNECTED;
>>>        ret = uvcg_video_disable(video);
>>>        if (ret < 0)
>>>            return ret;
>>>
>>> +    uvc->state = UVC_STATE_CONNECTED;
>>>        uvc_function_setup_continue(uvc, 1);
>>>        return 0;
>>>    }
>>
>> I'm not sure I understand what these re-orderings are for...can you explain please?
> This specific one was a leftover from testing, removed this hunk.
> But the ones below are undoing the change in patch 1, which is
> flawed in its use of uvc->state without any memory guarantees.
>
> So from patch 1 to patch 4, we shuffle the code around a bit,
> but this makes patch 1 somewhat complete and functional even if
> patch 4 were to be reverted.


Okedokey - that's fine (and good)

>
>>> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>>>    static void uvc_v4l2_disable(struct uvc_device *uvc)
>>>    {
>>>        uvc_function_disconnect(uvc);
>>> -    /*
>>> -     * Drop uvc->state to CONNECTED if it was streaming before.
>>> -     * This ensures that the usb_requests are no longer queued
>>> -     * to the controller.
>>> -     */
>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>> -        uvc->state = UVC_STATE_CONNECTED;
>>> -
>>>        uvcg_video_disable(&uvc->video);
>>>        uvcg_free_buffers(&uvc->video.queue);
>>>        uvc->func_connected = false;
>>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>>> index 1081dd790fd6..8f330ce696ec 100644
>>> --- a/drivers/usb/gadget/function/uvc_video.c
>>> +++ b/drivers/usb/gadget/function/uvc_video.c
>>> @@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>>     * Request handling
>>>     */
>>>
>>> +/*
>>> + * Must be called with req_lock held as it modifies the list ureq is held in
>>> + */
>>
>>
>> This comment probably belongs in patch #2. And in that case, shouldn't uvc_video_free_requests() hold the lock in that patch?
> Patch 2 doesn't change any existing locking semantics. The current
> code does not enforce any locking on freeing the requests, and neither
> does patch 2.
>
> Patch 4 introduces another call site for uvc_video_free_request, so
> some synchronization guarantees are needed (and hence the addition
> of this comment).
>
> As for uvc_video_free_requests not holding the lock, it is safe because
> uvc_video_free_requests is only called if request initialization fails.
> So uvc_video_free_requests should be the thread safe, as no other thread
> is processing requests when it is called.
>
> I did add a comment in uvcg_video_enable mentioning why it is safe to
> not hold req_free even though it accesses request related fields.


I understand, but in that case I think the comment is a little confusing - it's not the fact that 
the function modifies the list ureq is held in that requires it to be locked but that there're 
potentially multiple threads doing so. Can we go for something like "Callers must take care to hold 
req_lock when using this function outside of a single thread"? Feel free to re-word that however you 
like, as long as it's clear that it's only necessary when multiple threads could be active.

>
> Happy to add another comment to uvc_video_free_requests if that makes it
> clearer!
>
>>>    static void
>>>    uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>>>    {
>>> @@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>        struct uvc_request *ureq = req->context;
>>>        struct uvc_video *video = ureq->video;
>>>        struct uvc_video_queue *queue = &video->queue;
>>> -    struct uvc_device *uvc = video->uvc;
>>> +    struct uvc_buffer *last_buf = NULL;


This initialisation's unnecessary since it's unconditionally set below.

>>>        unsigned long flags;
>>>
>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>> +    if (!video->is_enabled) {
>>> +        /*
>>> +         * When is_enabled is false, uvc_video_disable ensures that
>> s/uvc_video_disable/uvc_video_disable()
> Done!
>
>>> +         * in-flight uvc_buffers are returned, so we can safely
>>> +         * call free_request without worrying about last_buf.
>>> +         */
>>> +        uvc_video_free_request(ureq, ep);
>> Now I understand the conditional in this function in patch 2 :)
>>> +        spin_unlock_irqrestore(&video->req_lock, flags);
>>> +        return;
>>> +    }
>>> +
>>> +    last_buf = ureq->last_buf;
>>> +    ureq->last_buf = NULL;
>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>
>> I'm not a huge fan of this locking, unlocking and relocking the same spinlock within the same function. Can we just hold the lock for the duration? if not, can there be an explanatory comment as to why?
> I agree that this is a little unfortunate, and it'd be nice if we
> only had a single driver level lock. However, as it stands, if
> we hold req_lock for the entirety of completion handler, we risk
> two things:
>
> 1. Adding dependencies between queue->irqlock and video->reqlock
> 2. Starving the video_pump thread.
>
> As of this patch, uvc_video_complete follows the same pattern as
> video_pump function:
> 1. Acquire req_lock
> 2. Fetch/Query usb_request
> 3. Drop req_lock
>
> 4. Acquire queue->irqlock
> 5. Buffer ops (encode/free/stop)
> 6. Drop queue->irqlock
>
> 7. Acquire req_lock
> 8. usb_request cleanup/handling
> 9. Drop req_lock
>
> (7), (8), and (9) are optional for video_pump, while
> (4), (5), and (6) are optional for uvc_video_complete.
>
> We can short-circuit uvc_video_complete with only one lock
> on the happy path, but this would have to be the flow for
> non-happy paths unless we want to hold the two locks at
> the same time (which isn't the worst idea, but comes with
> its own set of concerns).
>

Yeah. Sorry - I had missed that uvcg_video_pump() follows the same pattern too. Alright, I think 
leave this as-is.

>>> +
>>>        switch (req->status) {
>>>        case 0:
>>>            break;
>>> @@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>            uvcg_queue_cancel(queue, 0);
>>>        }
>>>
>>> -    if (ureq->last_buf) {
>>> -        uvcg_complete_buffer(&video->queue, ureq->last_buf);
>>> -        ureq->last_buf = NULL;
>>> +    if (last_buf) {
>>> +        spin_lock_irqsave(&queue->irqlock, flags);
>>> +        uvcg_complete_buffer(&video->queue, last_buf);
>>> +        spin_unlock_irqrestore(&queue->irqlock, flags);
>>
>>
>> I think it's right to take the irqlock here but it probably should have always been held, so this probably ought to go in its own commit with a Fixes:
> The lock here wasn't required before, because uvcg_complete_buffer was
> only ever called by the completion handler, which is synchronized by
> the usb controller. This is the reason we never saw an issue despite
> not holding the lock.


Ah - I was misled by the "called with &queue_irqlock held..." comment on uvc_complete_buffer()...I 
assumed that meant "you must call this function with &queue->irqlock held", but that turns out to be 
something of a hangover - originally it was placed there by 95faf82bd3ea6 because the callers of (at 
the time uvc_queue_next_buffer(), later renamed to) uvcg_complete_buffer() already held the irqlock. 
Clearly it needed locking then, but the function at the time manipulated queue->irqqueue and now it 
no longer does - that part has been stripped out to encode_bulk/encode_isoc/encode_isoc_sg(). So, 
probably that comment ought to have been removed at some point.

> This patch introduces another call site in uvcg_video_disable, so to
> protect memory consistency, we need to make sure calls to
> uvcg_complete_buffer are synchronized on something other than
> the usb controller.

If it's simply to prevent double calling uvcg_complete_buffer() for a buffer, is holding the irqlock 
necessary? Both uvc_video_complete() and uvc_video_disable() conditionally call 
uvcg_complete_buffer() based on whether ureq->last_buf is set or not, and both functions assess that 
whilst holding req_lock, so unless I'm missing something that situation is already guarded against.


>
>>>        }
>>>
>>>        spin_lock_irqsave(&video->req_lock, flags);
>>> -    list_add_tail(&req->list, &video->req_free);
>>> -    spin_unlock_irqrestore(&video->req_lock, flags);
>>> -
>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>> +    /*
>>> +     * Video stream might have been disabled while we were
>>> +     * processing the current usb_request. So make sure
>>> +     * we're still streaming before queueing the usb_request
>>> +     * back to req_free
>>> +     */
>>> +    if (video->is_enabled) {
>>> +        list_add_tail(&req->list, &video->req_free);
>>>            queue_work(video->async_wq, &video->pump);
>>> +    } else {
>>> +        uvc_video_free_request(ureq, ep);
>>> +    }
>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>    }
>>>
>>>    static int
>>> @@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>>        struct uvc_video_queue *queue = &video->queue;
>>>        /* video->max_payload_size is only set when using bulk transfer */
>>>        bool is_bulk = video->max_payload_size;
>>> -    struct uvc_device *uvc = video->uvc;
>>>        struct usb_request *req = NULL;
>>>        struct uvc_buffer *buf;
>>>        unsigned long flags;
>>>        bool buf_done;
>>>        int ret;
>>>
>>> -    while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>>> +    while (true) {
>>> +        if (!video->ep->enabled)
>>> +            return;
>>> +
>>>            /*
>>> -         * Retrieve the first available USB request, protected by the
>>> -         * request lock.
>>> +         * Check is_enabled and retrieve the first available USB
>>> +         * request, protected by the request lock.
>>>             */
>>>            spin_lock_irqsave(&video->req_lock, flags);
>>> -        if (list_empty(&video->req_free)) {
>>> +        if (!video->is_enabled || list_empty(&video->req_free)) {
>>>                spin_unlock_irqrestore(&video->req_lock, flags);
>>>                return;
>>>            }
>>> @@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>            return;
>>>
>>>        spin_lock_irqsave(&video->req_lock, flags);
>>> -    list_add_tail(&req->list, &video->req_free);
>>> +    if (video->is_enabled)
>>> +        list_add_tail(&req->list, &video->req_free);
>>> +    else
>>> +        uvc_video_free_request(req->context, video->ep);
>>>        spin_unlock_irqrestore(&video->req_lock, flags);
>>> -    return;
>>>    }
>>>
>>>    /*
>>> @@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>    int
>>>    uvcg_video_disable(struct uvc_video *video)
>>>    {
>>> -    struct uvc_request *ureq;
>>> +    unsigned long flags;
>>> +    struct list_head inflight_bufs;
>>> +    struct usb_request *req, *temp;
>>> +    struct uvc_buffer *buf, *btemp;
>>> +    struct uvc_request *ureq, *utemp;
>>>
>>>        if (video->ep == NULL) {
>>>            uvcg_info(&video->uvc->func,
>>> @@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
>>>            return -ENODEV;
>>>        }
>>>
>>> +    INIT_LIST_HEAD(&inflight_bufs);
>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>> +    video->is_enabled = false;
>>> +
>>> +    /*
>>> +     * Remove any in-flight buffers from the uvc_requests
>>> +     * because we want to return them before cancelling the
>>> +     * queue. This ensures that we aren't stuck waiting for
>>> +     * all complete callbacks to come through before disabling
>>> +     * vb2 queue.
>>> +     */
>>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>>> +        if (ureq->last_buf) {
>>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>>> +            ureq->last_buf = NULL;
>>> +        }
>>> +    }
>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>> +
>>>        cancel_work_sync(&video->pump);
>>>        uvcg_queue_cancel(&video->queue, 0);
>>>
>>> -    list_for_each_entry(ureq, &video->ureqs, list) {
>>> -        if (ureq->req)
>>> -            usb_ep_dequeue(video->ep, ureq->req);
>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>> +    /*
>>> +     * Remove all uvc_reqeusts from ureqs with list_del_init
s/uvc_reqeusts/uvc_requests
>>> +     * This lets uvc_video_free_request correctly identify
>>> +     * if the uvc_request is attached to a list or not when freeing
>>> +     * memory.
>>> +     */
>>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>>> +        list_del_init(&ureq->list);
>>> +
>>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>>> +        list_del(&req->list);
>>> +        uvc_video_free_request(req->context, video->ep);
>>>        }
>>>
>>> -    uvc_video_free_requests(video);
>>> +    INIT_LIST_HEAD(&video->ureqs);
>>> +    INIT_LIST_HEAD(&video->req_free);
>>> +    video->req_size = 0;
>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>> +
>>> +    /*
>>> +     * Return all the video buffers before disabling the queue.
>>> +     */
>>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>>> +        list_del(&buf->queue);
>>> +        uvcg_complete_buffer(&video->queue, buf);
>>> +    }
>>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>>> +
>>>        uvcg_queue_enable(&video->queue, 0);
>>>        return 0;
>>>    }
>>> @@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
>>>            return -ENODEV;
>>>        }
>>>
>>> +    /*
>>> +     * Safe to access request related fields without req_lock because
>>> +     * this is the only thread currently active, and no other
>>> +     * request handling thread will become active until this function
>>> +     * returns.
>>> +     */
>>> +    video->is_enabled = true;
>>> +
>>>        if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>>>            return ret;
>>>
>>> @@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
>>>     */
>>>    int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>>>    {
>>> +    video->is_enabled = false;
>>>        INIT_LIST_HEAD(&video->ureqs);
>>>        INIT_LIST_HEAD(&video->req_free);
>>>        spin_lock_init(&video->req_lock);
>>> -- 
>>> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
  2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-11-02 20:19     ` Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                         ` (2 more replies)
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 3 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-02 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT and reworded commit message.
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : Address review comments & re-rebase to ToT
v4  -> v5  : Add Reviewed-by & Tested-by
v5  -> v6  : No change
v6  -> v7  : No change
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Fix typo. No functional change.
v9  -> v10 : Rebase to ToT (usb-next)
v10 -> v11 : No change

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 786379f1b7b7..77999ed53d33 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..083aef0c65c6 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v11 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-11-02 20:19       ` Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-02 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : Address review comments & re-rebase to ToT
v4  -> v5  : Address more review comments. Add Reviewed-by & Tested-by.
v5  -> v6  : No change
v6  -> v7  : No change
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Address review comments.
v9  -> v10 : Address review comments; remove BUG_ON(&video->reqs);
             Rebase to ToT (usb-next)
v10 -> v11 : Add Reviewed-by

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 88 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..1619f9664748 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +497,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +510,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +545,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v11 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-11-02 20:19       ` Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-02 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'. This function
is now used anywhere uvcg_video_enable(video, 0) was used.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
XX  -> v6  : Introduced this patch to make the next one easier to review
v6  -> v7  : Add Suggested-by
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Call uvcg_video_disable directly instead of uvcg_video_enable(video, 0)
v9  -> v10 : Rebase to ToT (usb-next)
v10 -> v11 : No change

 drivers/usb/gadget/function/uvc_v4l2.c  |  6 ++--
 drivers/usb/gadget/function/uvc_video.c | 40 ++++++++++++++++---------
 drivers/usb/gadget/function/uvc_video.h |  3 +-
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..904dd283cbf7 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	/* Enable UVC video. */
-	ret = uvcg_video_enable(video, 1);
+	ret = uvcg_video_enable(video);
 	if (ret < 0)
 		return ret;

@@ -469,7 +469,7 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	uvc->state = UVC_STATE_CONNECTED;
-	ret = uvcg_video_enable(video, 0);
+	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

@@ -515,7 +515,7 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
 	if (uvc->state == UVC_STATE_STREAMING)
 		uvc->state = UVC_STATE_CONNECTED;

-	uvcg_video_enable(&uvc->video, 0);
+	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
 	wake_up_interruptible(&uvc->func_connected_queue);
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 1619f9664748..c3e8c48f46a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,31 +493,43 @@ static void uvcg_video_pump(struct work_struct *work)
 }

 /*
- * Enable or disable the video stream.
+ * Disable the video stream
  */
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
 {
-	int ret;
 	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
-			  "Video enable failed, device is uninitialized.\n");
+			  "Video disable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);

-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}

-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
+/*
+ * Enable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+	int ret;
+
+	if (video->ep == NULL) {
+		uvcg_info(&video->uvc->func,
+			  "Video enable failed, device is uninitialized.\n");
+		return -ENODEV;
 	}

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
index 03adeefa343b..8ef6259741f1 100644
--- a/drivers/usb/gadget/function/uvc_video.h
+++ b/drivers/usb/gadget/function/uvc_video.h
@@ -14,7 +14,8 @@

 struct uvc_video;

-int uvcg_video_enable(struct uvc_video *video, int enable);
+int uvcg_video_enable(struct uvc_video *video);
+int uvcg_video_disable(struct uvc_video *video);

 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);

--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-11-02 20:19       ` [PATCH v11 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-11-02 20:19       ` Avichal Rakesh
  2023-11-08 14:15         ` Dan Scally
  2 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-02 20:19 UTC (permalink / raw)
  To: arakesh, dan.scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT, and fixed deadlock reported in
             https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : re-rebase to ToT & moved to a uvc_video level lock
             as discussed in
             https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4  -> v5  : Address review comments. Add Reviewed-by & Tested-by.
v5  -> v6  : Added another patch before this one to make uvcg_video_disable
             easier to review.
v6  -> v7  : Fix warning reported in
             https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : No change.
v9  -> v10 : Address review comments. Rebase to ToT (usb-next)
v10 -> v11 : Address review comments

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  10 +-
 drivers/usb/gadget/function/uvc_video.c | 130 ++++++++++++++++++++----
 3 files changed, 112 insertions(+), 29 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 904dd283cbf7..c7e5fa4f29e0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c3e8c48f46a9..164bdeb7f2a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,10 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Callers must take care to hold req_lock when this function may be called
+ * from multiple threads. For example, when frames are streaming to the host.
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +275,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvcg_video_disable() ensures
+		 * that in-flight uvc_buffers are returned, so we can
+		 * safely call free_request without worrying about
+		 * last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +316,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -392,20 +422,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -487,9 +519,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -498,7 +532,11 @@ static void uvcg_video_pump(struct work_struct *work)
 int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -506,15 +544,58 @@ uvcg_video_disable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_requests from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -532,6 +613,14 @@ int uvcg_video_enable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -557,6 +646,7 @@ int uvcg_video_enable(struct uvc_video *video)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-02 13:29             ` Dan Scally
@ 2023-11-02 20:39               ` Avichal Rakesh
  2023-11-07 21:15                 ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-02 20:39 UTC (permalink / raw)
  To: Dan Scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hey Dan, thank you for taking the time to review the patch!

Sent out v11 with some comments addressed:
https://lore.kernel.org/all/20231102201939.4171214-1-arakesh@google.com/

On 11/2/23 06:29, Dan Scally wrote:
> Hi Avichal
> 
> On 30/10/2023 20:56, Avichal Rakesh wrote:
>> Thank you for taking a look Dan!
>>
>> On 10/28/23 13:56, Dan Scally wrote:
>>> Hi Avichal
>>>
>>> On 27/10/2023 21:19, Avichal Rakesh wrote:
>>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>>> and deallocates them all when the video stream stops. This includes
>>>> de-allocating all the usb_requests associated with those uvc_requests.
>>>> This can lead to use-after-free issues if any of those de-allocated
>>>> usb_requests were still owned by the usb controller.
>>>>
>>>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>>>> flag to uvc_video to track when frames and requests should be flowing.
>>>> When disabling the video stream, the flag is tripped and, instead
>>>> of de-allocating all uvc_requests and usb_requests, the gadget
>>>> driver only de-allocates those usb_requests that are currently
>>>> owned by it (as present in req_free). Other usb_requests are left
>>>> untouched until their completion handler is called which takes care
>>>> of freeing the usb_request and its corresponding uvc_request.
>>>>
>>>> Now that uvc_video does not depends on uvc->state, this patch removes
>>>> unnecessary upates to uvc->state that were made to accommodate uvc_video
>>>> logic. This should ensure that uvc gadget driver never accidentally
>>>> de-allocates a usb_request that it doesn't own.
>>>>
>>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>>> ---
>>>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>>>             https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>>>> v2 -> v3: Fix email threading goof-up
>>>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>>>             as discussed in
>>>>             https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>>>> v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
>>>> v5 -> v6: Added another patch before this one to make uvcg_video_disable
>>>>             easier to review.
>>>> v6 -> v7: Fix warning reported in
>>>>             https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
>>>> v7 -> v8: No change. Getting back in review queue
>>>> v8 -> v9: No change.
>>>>
>>>>    drivers/usb/gadget/function/uvc.h       |   1 +
>>>>    drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
>>>>    drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
>>>>    3 files changed, 111 insertions(+), 30 deletions(-)
>>>>
>>>> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>>>> index 993694da0bbc..be0d012aa244 100644
>>>> --- a/drivers/usb/gadget/function/uvc.h
>>>> +++ b/drivers/usb/gadget/function/uvc.h
>>>> @@ -102,6 +102,7 @@ struct uvc_video {
>>>>        unsigned int uvc_num_requests;
>>>>
>>>>        /* Requests */
>>>> +    bool is_enabled; /* tracks whether video stream is enabled */
>>>>        unsigned int req_size;
>>>>        struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>>>>        struct list_head req_free;
>>>> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>>>> index 904dd283cbf7..2f8634e05612 100644
>>>> --- a/drivers/usb/gadget/function/uvc_v4l2.c
>>>> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
>>>> @@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>>>>         * Complete the alternate setting selection setup phase now that
>>>>         * userspace is ready to provide video frames.
>>>>         */
>>>> -    uvc_function_setup_continue(uvc, 0);
>>>>        uvc->state = UVC_STATE_STREAMING;
>>>> +    uvc_function_setup_continue(uvc, 0);
>>>>
>>>>        return 0;
>>>>    }
>>>> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>>>>        if (type != video->queue.queue.type)
>>>>            return -EINVAL;
>>>>
>>>> -    uvc->state = UVC_STATE_CONNECTED;
>>>>        ret = uvcg_video_disable(video);
>>>>        if (ret < 0)
>>>>            return ret;
>>>>
>>>> +    uvc->state = UVC_STATE_CONNECTED;
>>>>        uvc_function_setup_continue(uvc, 1);
>>>>        return 0;
>>>>    }
>>>
>>> I'm not sure I understand what these re-orderings are for...can you explain please?
>> This specific one was a leftover from testing, removed this hunk.
>> But the ones below are undoing the change in patch 1, which is
>> flawed in its use of uvc->state without any memory guarantees.
>>
>> So from patch 1 to patch 4, we shuffle the code around a bit,
>> but this makes patch 1 somewhat complete and functional even if
>> patch 4 were to be reverted.
> 
> 
> Okedokey - that's fine (and good)
> 
>>
>>>> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>>>>    static void uvc_v4l2_disable(struct uvc_device *uvc)
>>>>    {
>>>>        uvc_function_disconnect(uvc);
>>>> -    /*
>>>> -     * Drop uvc->state to CONNECTED if it was streaming before.
>>>> -     * This ensures that the usb_requests are no longer queued
>>>> -     * to the controller.
>>>> -     */
>>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>>> -        uvc->state = UVC_STATE_CONNECTED;
>>>> -
>>>>        uvcg_video_disable(&uvc->video);
>>>>        uvcg_free_buffers(&uvc->video.queue);
>>>>        uvc->func_connected = false;
>>>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>>>> index 1081dd790fd6..8f330ce696ec 100644
>>>> --- a/drivers/usb/gadget/function/uvc_video.c
>>>> +++ b/drivers/usb/gadget/function/uvc_video.c
>>>> @@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>>>     * Request handling
>>>>     */
>>>>
>>>> +/*
>>>> + * Must be called with req_lock held as it modifies the list ureq is held in
>>>> + */
>>>
>>>
>>> This comment probably belongs in patch #2. And in that case, shouldn't uvc_video_free_requests() hold the lock in that patch?
>> Patch 2 doesn't change any existing locking semantics. The current
>> code does not enforce any locking on freeing the requests, and neither
>> does patch 2.
>>
>> Patch 4 introduces another call site for uvc_video_free_request, so
>> some synchronization guarantees are needed (and hence the addition
>> of this comment).
>>
>> As for uvc_video_free_requests not holding the lock, it is safe because
>> uvc_video_free_requests is only called if request initialization fails.
>> So uvc_video_free_requests should be the thread safe, as no other thread
>> is processing requests when it is called.
>>
>> I did add a comment in uvcg_video_enable mentioning why it is safe to
>> not hold req_free even though it accesses request related fields.
> 
> 
> I understand, but in that case I think the comment is a little confusing - it's not the fact that the function modifies the list ureq is held in that requires it to be locked but that there're potentially multiple threads doing so. Can we go for something like "Callers must take care to hold req_lock when using this function outside of a single thread"? Feel free to re-word that however you like, as long as it's clear that it's only necessary when multiple threads could be active.

I see. I think having all the context made it obvious in my mind.
Updated the comment to be more explicit!

> 
>>
>> Happy to add another comment to uvc_video_free_requests if that makes it
>> clearer!
>>
>>>>    static void
>>>>    uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>>>>    {
>>>> @@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>>        struct uvc_request *ureq = req->context;
>>>>        struct uvc_video *video = ureq->video;
>>>>        struct uvc_video_queue *queue = &video->queue;
>>>> -    struct uvc_device *uvc = video->uvc;
>>>> +    struct uvc_buffer *last_buf = NULL;
> 
> 
> This initialisation's unnecessary since it's unconditionally set below.
> 
>>>>        unsigned long flags;
>>>>
>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>> +    if (!video->is_enabled) {
>>>> +        /*
>>>> +         * When is_enabled is false, uvc_video_disable ensures that
>>> s/uvc_video_disable/uvc_video_disable()
>> Done!
>>
>>>> +         * in-flight uvc_buffers are returned, so we can safely
>>>> +         * call free_request without worrying about last_buf.
>>>> +         */
>>>> +        uvc_video_free_request(ureq, ep);
>>> Now I understand the conditional in this function in patch 2 :)
>>>> +        spin_unlock_irqrestore(&video->req_lock, flags);
>>>> +        return;
>>>> +    }
>>>> +
>>>> +    last_buf = ureq->last_buf;
>>>> +    ureq->last_buf = NULL;
>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>
>>> I'm not a huge fan of this locking, unlocking and relocking the same spinlock within the same function. Can we just hold the lock for the duration? if not, can there be an explanatory comment as to why?
>> I agree that this is a little unfortunate, and it'd be nice if we
>> only had a single driver level lock. However, as it stands, if
>> we hold req_lock for the entirety of completion handler, we risk
>> two things:
>>
>> 1. Adding dependencies between queue->irqlock and video->reqlock
>> 2. Starving the video_pump thread.
>>
>> As of this patch, uvc_video_complete follows the same pattern as
>> video_pump function:
>> 1. Acquire req_lock
>> 2. Fetch/Query usb_request
>> 3. Drop req_lock
>>
>> 4. Acquire queue->irqlock
>> 5. Buffer ops (encode/free/stop)
>> 6. Drop queue->irqlock
>>
>> 7. Acquire req_lock
>> 8. usb_request cleanup/handling
>> 9. Drop req_lock
>>
>> (7), (8), and (9) are optional for video_pump, while
>> (4), (5), and (6) are optional for uvc_video_complete.
>>
>> We can short-circuit uvc_video_complete with only one lock
>> on the happy path, but this would have to be the flow for
>> non-happy paths unless we want to hold the two locks at
>> the same time (which isn't the worst idea, but comes with
>> its own set of concerns).
>>
> 
> Yeah. Sorry - I had missed that uvcg_video_pump() follows the same pattern too. Alright, I think leave this as-is.
> 
>>>> +
>>>>        switch (req->status) {
>>>>        case 0:
>>>>            break;
>>>> @@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>>            uvcg_queue_cancel(queue, 0);
>>>>        }
>>>>
>>>> -    if (ureq->last_buf) {
>>>> -        uvcg_complete_buffer(&video->queue, ureq->last_buf);
>>>> -        ureq->last_buf = NULL;
>>>> +    if (last_buf) {
>>>> +        spin_lock_irqsave(&queue->irqlock, flags);
>>>> +        uvcg_complete_buffer(&video->queue, last_buf);
>>>> +        spin_unlock_irqrestore(&queue->irqlock, flags);
>>>
>>>
>>> I think it's right to take the irqlock here but it probably should have always been held, so this probably ought to go in its own commit with a Fixes:
>> The lock here wasn't required before, because uvcg_complete_buffer was
>> only ever called by the completion handler, which is synchronized by
>> the usb controller. This is the reason we never saw an issue despite
>> not holding the lock.
> 
> 
> Ah - I was misled by the "called with &queue_irqlock held..." comment on uvc_complete_buffer()...I assumed that meant "you must call this function with &queue->irqlock held", but that turns out to be something of a hangover - originally it was placed there by 95faf82bd3ea6 because the callers of (at the time uvc_queue_next_buffer(), later renamed to) uvcg_complete_buffer() already held the irqlock. Clearly it needed locking then, but the function at the time manipulated queue->irqqueue and now it no longer does - that part has been stripped out to encode_bulk/encode_isoc/encode_isoc_sg(). So, probably that comment ought to have been removed at some point.
> 
>> This patch introduces another call site in uvcg_video_disable, so to
>> protect memory consistency, we need to make sure calls to
>> uvcg_complete_buffer are synchronized on something other than
>> the usb controller.
> 
> If it's simply to prevent double calling uvcg_complete_buffer() for a buffer, is holding the irqlock necessary? Both uvc_video_complete() and uvc_video_disable() conditionally call uvcg_complete_buffer() based on whether ureq->last_buf is set or not, and both functions assess that whilst holding req_lock, so unless I'm missing something that situation is already guarded against.

Even without holding queue->irqlock, we guaratee that uvcg_complete_buffer()
is only called once per buffer (by querying last_buf with req_lock held).
However, uvcg_complete_buffer modifies queue->flags which in honesty
will not cause any problems, but is promlematic from theoretical
correctness standpoint. 

Either way, this shouldn't be too much overhead, as
this is only called once per frame, and most requests don't
have last_buf set anyway. Let me know if you feel strongly
about not having the locks there, and I can drop it.

> 
> 
>>
>>>>        }
>>>>
>>>>        spin_lock_irqsave(&video->req_lock, flags);
>>>> -    list_add_tail(&req->list, &video->req_free);
>>>> -    spin_unlock_irqrestore(&video->req_lock, flags);
>>>> -
>>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>>> +    /*
>>>> +     * Video stream might have been disabled while we were
>>>> +     * processing the current usb_request. So make sure
>>>> +     * we're still streaming before queueing the usb_request
>>>> +     * back to req_free
>>>> +     */
>>>> +    if (video->is_enabled) {
>>>> +        list_add_tail(&req->list, &video->req_free);
>>>>            queue_work(video->async_wq, &video->pump);
>>>> +    } else {
>>>> +        uvc_video_free_request(ureq, ep);
>>>> +    }
>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>    }
>>>>
>>>>    static int
>>>> @@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>        struct uvc_video_queue *queue = &video->queue;
>>>>        /* video->max_payload_size is only set when using bulk transfer */
>>>>        bool is_bulk = video->max_payload_size;
>>>> -    struct uvc_device *uvc = video->uvc;
>>>>        struct usb_request *req = NULL;
>>>>        struct uvc_buffer *buf;
>>>>        unsigned long flags;
>>>>        bool buf_done;
>>>>        int ret;
>>>>
>>>> -    while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>>>> +    while (true) {
>>>> +        if (!video->ep->enabled)
>>>> +            return;
>>>> +
>>>>            /*
>>>> -         * Retrieve the first available USB request, protected by the
>>>> -         * request lock.
>>>> +         * Check is_enabled and retrieve the first available USB
>>>> +         * request, protected by the request lock.
>>>>             */
>>>>            spin_lock_irqsave(&video->req_lock, flags);
>>>> -        if (list_empty(&video->req_free)) {
>>>> +        if (!video->is_enabled || list_empty(&video->req_free)) {
>>>>                spin_unlock_irqrestore(&video->req_lock, flags);
>>>>                return;
>>>>            }
>>>> @@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>            return;
>>>>
>>>>        spin_lock_irqsave(&video->req_lock, flags);
>>>> -    list_add_tail(&req->list, &video->req_free);
>>>> +    if (video->is_enabled)
>>>> +        list_add_tail(&req->list, &video->req_free);
>>>> +    else
>>>> +        uvc_video_free_request(req->context, video->ep);
>>>>        spin_unlock_irqrestore(&video->req_lock, flags);
>>>> -    return;
>>>>    }
>>>>
>>>>    /*
>>>> @@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>    int
>>>>    uvcg_video_disable(struct uvc_video *video)
>>>>    {
>>>> -    struct uvc_request *ureq;
>>>> +    unsigned long flags;
>>>> +    struct list_head inflight_bufs;
>>>> +    struct usb_request *req, *temp;
>>>> +    struct uvc_buffer *buf, *btemp;
>>>> +    struct uvc_request *ureq, *utemp;
>>>>
>>>>        if (video->ep == NULL) {
>>>>            uvcg_info(&video->uvc->func,
>>>> @@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
>>>>            return -ENODEV;
>>>>        }
>>>>
>>>> +    INIT_LIST_HEAD(&inflight_bufs);
>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>> +    video->is_enabled = false;
>>>> +
>>>> +    /*
>>>> +     * Remove any in-flight buffers from the uvc_requests
>>>> +     * because we want to return them before cancelling the
>>>> +     * queue. This ensures that we aren't stuck waiting for
>>>> +     * all complete callbacks to come through before disabling
>>>> +     * vb2 queue.
>>>> +     */
>>>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>>>> +        if (ureq->last_buf) {
>>>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>>>> +            ureq->last_buf = NULL;
>>>> +        }
>>>> +    }
>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>> +
>>>>        cancel_work_sync(&video->pump);
>>>>        uvcg_queue_cancel(&video->queue, 0);
>>>>
>>>> -    list_for_each_entry(ureq, &video->ureqs, list) {
>>>> -        if (ureq->req)
>>>> -            usb_ep_dequeue(video->ep, ureq->req);
>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>> +    /*
>>>> +     * Remove all uvc_reqeusts from ureqs with list_del_init
> s/uvc_reqeusts/uvc_requests
>>>> +     * This lets uvc_video_free_request correctly identify
>>>> +     * if the uvc_request is attached to a list or not when freeing
>>>> +     * memory.
>>>> +     */
>>>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>>>> +        list_del_init(&ureq->list);
>>>> +
>>>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>>>> +        list_del(&req->list);
>>>> +        uvc_video_free_request(req->context, video->ep);
>>>>        }
>>>>
>>>> -    uvc_video_free_requests(video);
>>>> +    INIT_LIST_HEAD(&video->ureqs);
>>>> +    INIT_LIST_HEAD(&video->req_free);
>>>> +    video->req_size = 0;
>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>> +
>>>> +    /*
>>>> +     * Return all the video buffers before disabling the queue.
>>>> +     */
>>>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>>>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>>>> +        list_del(&buf->queue);
>>>> +        uvcg_complete_buffer(&video->queue, buf);
>>>> +    }
>>>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>>>> +
>>>>        uvcg_queue_enable(&video->queue, 0);
>>>>        return 0;
>>>>    }
>>>> @@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
>>>>            return -ENODEV;
>>>>        }
>>>>
>>>> +    /*
>>>> +     * Safe to access request related fields without req_lock because
>>>> +     * this is the only thread currently active, and no other
>>>> +     * request handling thread will become active until this function
>>>> +     * returns.
>>>> +     */
>>>> +    video->is_enabled = true;
>>>> +
>>>>        if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>>>>            return ret;
>>>>
>>>> @@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
>>>>     */
>>>>    int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>>>>    {
>>>> +    video->is_enabled = false;
>>>>        INIT_LIST_HEAD(&video->ureqs);
>>>>        INIT_LIST_HEAD(&video->req_free);
>>>>        spin_lock_init(&video->req_lock);
>>>> -- 
>>>> 2.42.0.820.g83a721a137-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-02 20:39               ` Avichal Rakesh
@ 2023-11-07 21:15                 ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-07 21:15 UTC (permalink / raw)
  To: Dan Scally
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik



On 11/2/23 13:39, Avichal Rakesh wrote:
> Hey Dan, thank you for taking the time to review the patch!
> 
> Sent out v11 with some comments addressed:
> https://lore.kernel.org/all/20231102201939.4171214-1-arakesh@google.com/
> 
> On 11/2/23 06:29, Dan Scally wrote:
>> Hi Avichal
>>
>> On 30/10/2023 20:56, Avichal Rakesh wrote:
>>> Thank you for taking a look Dan!
>>>
>>> On 10/28/23 13:56, Dan Scally wrote:
>>>> Hi Avichal
>>>>
>>>> On 27/10/2023 21:19, Avichal Rakesh wrote:
>>>>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>>>>> and deallocates them all when the video stream stops. This includes
>>>>> de-allocating all the usb_requests associated with those uvc_requests.
>>>>> This can lead to use-after-free issues if any of those de-allocated
>>>>> usb_requests were still owned by the usb controller.
>>>>>
>>>>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>>>>> flag to uvc_video to track when frames and requests should be flowing.
>>>>> When disabling the video stream, the flag is tripped and, instead
>>>>> of de-allocating all uvc_requests and usb_requests, the gadget
>>>>> driver only de-allocates those usb_requests that are currently
>>>>> owned by it (as present in req_free). Other usb_requests are left
>>>>> untouched until their completion handler is called which takes care
>>>>> of freeing the usb_request and its corresponding uvc_request.
>>>>>
>>>>> Now that uvc_video does not depends on uvc->state, this patch removes
>>>>> unnecessary upates to uvc->state that were made to accommodate uvc_video
>>>>> logic. This should ensure that uvc gadget driver never accidentally
>>>>> de-allocates a usb_request that it doesn't own.
>>>>>
>>>>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>>>>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>>>>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>>>>> ---
>>>>> v1 -> v2: Rebased to ToT, and fixed deadlock reported in
>>>>>             https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
>>>>> v2 -> v3: Fix email threading goof-up
>>>>> v3 -> v4: re-rebase to ToT & moved to a uvc_video level lock
>>>>>             as discussed in
>>>>>             https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
>>>>> v4 -> v5: Address review comments. Add Reviewed-by & Tested-by.
>>>>> v5 -> v6: Added another patch before this one to make uvcg_video_disable
>>>>>             easier to review.
>>>>> v6 -> v7: Fix warning reported in
>>>>>             https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
>>>>> v7 -> v8: No change. Getting back in review queue
>>>>> v8 -> v9: No change.
>>>>>
>>>>>    drivers/usb/gadget/function/uvc.h       |   1 +
>>>>>    drivers/usb/gadget/function/uvc_v4l2.c  |  12 +--
>>>>>    drivers/usb/gadget/function/uvc_video.c | 128 ++++++++++++++++++++----
>>>>>    3 files changed, 111 insertions(+), 30 deletions(-)
>>>>>
>>>>> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
>>>>> index 993694da0bbc..be0d012aa244 100644
>>>>> --- a/drivers/usb/gadget/function/uvc.h
>>>>> +++ b/drivers/usb/gadget/function/uvc.h
>>>>> @@ -102,6 +102,7 @@ struct uvc_video {
>>>>>        unsigned int uvc_num_requests;
>>>>>
>>>>>        /* Requests */
>>>>> +    bool is_enabled; /* tracks whether video stream is enabled */
>>>>>        unsigned int req_size;
>>>>>        struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>>>>>        struct list_head req_free;
>>>>> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
>>>>> index 904dd283cbf7..2f8634e05612 100644
>>>>> --- a/drivers/usb/gadget/function/uvc_v4l2.c
>>>>> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
>>>>> @@ -451,8 +451,8 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>>>>>         * Complete the alternate setting selection setup phase now that
>>>>>         * userspace is ready to provide video frames.
>>>>>         */
>>>>> -    uvc_function_setup_continue(uvc, 0);
>>>>>        uvc->state = UVC_STATE_STREAMING;
>>>>> +    uvc_function_setup_continue(uvc, 0);
>>>>>
>>>>>        return 0;
>>>>>    }
>>>>> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>>>>>        if (type != video->queue.queue.type)
>>>>>            return -EINVAL;
>>>>>
>>>>> -    uvc->state = UVC_STATE_CONNECTED;
>>>>>        ret = uvcg_video_disable(video);
>>>>>        if (ret < 0)
>>>>>            return ret;
>>>>>
>>>>> +    uvc->state = UVC_STATE_CONNECTED;
>>>>>        uvc_function_setup_continue(uvc, 1);
>>>>>        return 0;
>>>>>    }
>>>>
>>>> I'm not sure I understand what these re-orderings are for...can you explain please?
>>> This specific one was a leftover from testing, removed this hunk.
>>> But the ones below are undoing the change in patch 1, which is
>>> flawed in its use of uvc->state without any memory guarantees.
>>>
>>> So from patch 1 to patch 4, we shuffle the code around a bit,
>>> but this makes patch 1 somewhat complete and functional even if
>>> patch 4 were to be reverted.
>>
>>
>> Okedokey - that's fine (and good)
>>
>>>
>>>>> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>>>>>    static void uvc_v4l2_disable(struct uvc_device *uvc)
>>>>>    {
>>>>>        uvc_function_disconnect(uvc);
>>>>> -    /*
>>>>> -     * Drop uvc->state to CONNECTED if it was streaming before.
>>>>> -     * This ensures that the usb_requests are no longer queued
>>>>> -     * to the controller.
>>>>> -     */
>>>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>>>> -        uvc->state = UVC_STATE_CONNECTED;
>>>>> -
>>>>>        uvcg_video_disable(&uvc->video);
>>>>>        uvcg_free_buffers(&uvc->video.queue);
>>>>>        uvc->func_connected = false;
>>>>> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
>>>>> index 1081dd790fd6..8f330ce696ec 100644
>>>>> --- a/drivers/usb/gadget/function/uvc_video.c
>>>>> +++ b/drivers/usb/gadget/function/uvc_video.c
>>>>> @@ -227,6 +227,9 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>>>>>     * Request handling
>>>>>     */
>>>>>
>>>>> +/*
>>>>> + * Must be called with req_lock held as it modifies the list ureq is held in
>>>>> + */
>>>>
>>>>
>>>> This comment probably belongs in patch #2. And in that case, shouldn't uvc_video_free_requests() hold the lock in that patch?
>>> Patch 2 doesn't change any existing locking semantics. The current
>>> code does not enforce any locking on freeing the requests, and neither
>>> does patch 2.
>>>
>>> Patch 4 introduces another call site for uvc_video_free_request, so
>>> some synchronization guarantees are needed (and hence the addition
>>> of this comment).
>>>
>>> As for uvc_video_free_requests not holding the lock, it is safe because
>>> uvc_video_free_requests is only called if request initialization fails.
>>> So uvc_video_free_requests should be the thread safe, as no other thread
>>> is processing requests when it is called.
>>>
>>> I did add a comment in uvcg_video_enable mentioning why it is safe to
>>> not hold req_free even though it accesses request related fields.
>>
>>
>> I understand, but in that case I think the comment is a little confusing - it's not the fact that the function modifies the list ureq is held in that requires it to be locked but that there're potentially multiple threads doing so. Can we go for something like "Callers must take care to hold req_lock when using this function outside of a single thread"? Feel free to re-word that however you like, as long as it's clear that it's only necessary when multiple threads could be active.
> 
> I see. I think having all the context made it obvious in my mind.
> Updated the comment to be more explicit!
> 
>>
>>>
>>> Happy to add another comment to uvc_video_free_requests if that makes it
>>> clearer!
>>>
>>>>>    static void
>>>>>    uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>>>>>    {
>>>>> @@ -271,9 +274,25 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>>>        struct uvc_request *ureq = req->context;
>>>>>        struct uvc_video *video = ureq->video;
>>>>>        struct uvc_video_queue *queue = &video->queue;
>>>>> -    struct uvc_device *uvc = video->uvc;
>>>>> +    struct uvc_buffer *last_buf = NULL;
>>
>>
>> This initialisation's unnecessary since it's unconditionally set below.
>>
>>>>>        unsigned long flags;
>>>>>
>>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>>> +    if (!video->is_enabled) {
>>>>> +        /*
>>>>> +         * When is_enabled is false, uvc_video_disable ensures that
>>>> s/uvc_video_disable/uvc_video_disable()
>>> Done!
>>>
>>>>> +         * in-flight uvc_buffers are returned, so we can safely
>>>>> +         * call free_request without worrying about last_buf.
>>>>> +         */
>>>>> +        uvc_video_free_request(ureq, ep);
>>>> Now I understand the conditional in this function in patch 2 :)
>>>>> +        spin_unlock_irqrestore(&video->req_lock, flags);
>>>>> +        return;
>>>>> +    }
>>>>> +
>>>>> +    last_buf = ureq->last_buf;
>>>>> +    ureq->last_buf = NULL;
>>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>
>>>> I'm not a huge fan of this locking, unlocking and relocking the same spinlock within the same function. Can we just hold the lock for the duration? if not, can there be an explanatory comment as to why?
>>> I agree that this is a little unfortunate, and it'd be nice if we
>>> only had a single driver level lock. However, as it stands, if
>>> we hold req_lock for the entirety of completion handler, we risk
>>> two things:
>>>
>>> 1. Adding dependencies between queue->irqlock and video->reqlock
>>> 2. Starving the video_pump thread.
>>>
>>> As of this patch, uvc_video_complete follows the same pattern as
>>> video_pump function:
>>> 1. Acquire req_lock
>>> 2. Fetch/Query usb_request
>>> 3. Drop req_lock
>>>
>>> 4. Acquire queue->irqlock
>>> 5. Buffer ops (encode/free/stop)
>>> 6. Drop queue->irqlock
>>>
>>> 7. Acquire req_lock
>>> 8. usb_request cleanup/handling
>>> 9. Drop req_lock
>>>
>>> (7), (8), and (9) are optional for video_pump, while
>>> (4), (5), and (6) are optional for uvc_video_complete.
>>>
>>> We can short-circuit uvc_video_complete with only one lock
>>> on the happy path, but this would have to be the flow for
>>> non-happy paths unless we want to hold the two locks at
>>> the same time (which isn't the worst idea, but comes with
>>> its own set of concerns).
>>>
>>
>> Yeah. Sorry - I had missed that uvcg_video_pump() follows the same pattern too. Alright, I think leave this as-is.
>>
>>>>> +
>>>>>        switch (req->status) {
>>>>>        case 0:
>>>>>            break;
>>>>> @@ -295,17 +314,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>>>>>            uvcg_queue_cancel(queue, 0);
>>>>>        }
>>>>>
>>>>> -    if (ureq->last_buf) {
>>>>> -        uvcg_complete_buffer(&video->queue, ureq->last_buf);
>>>>> -        ureq->last_buf = NULL;
>>>>> +    if (last_buf) {
>>>>> +        spin_lock_irqsave(&queue->irqlock, flags);
>>>>> +        uvcg_complete_buffer(&video->queue, last_buf);
>>>>> +        spin_unlock_irqrestore(&queue->irqlock, flags);
>>>>
>>>>
>>>> I think it's right to take the irqlock here but it probably should have always been held, so this probably ought to go in its own commit with a Fixes:
>>> The lock here wasn't required before, because uvcg_complete_buffer was
>>> only ever called by the completion handler, which is synchronized by
>>> the usb controller. This is the reason we never saw an issue despite
>>> not holding the lock.
>>
>>
>> Ah - I was misled by the "called with &queue_irqlock held..." comment on uvc_complete_buffer()...I assumed that meant "you must call this function with &queue->irqlock held", but that turns out to be something of a hangover - originally it was placed there by 95faf82bd3ea6 because the callers of (at the time uvc_queue_next_buffer(), later renamed to) uvcg_complete_buffer() already held the irqlock. Clearly it needed locking then, but the function at the time manipulated queue->irqqueue and now it no longer does - that part has been stripped out to encode_bulk/encode_isoc/encode_isoc_sg(). So, probably that comment ought to have been removed at some point.
>>
>>> This patch introduces another call site in uvcg_video_disable, so to
>>> protect memory consistency, we need to make sure calls to
>>> uvcg_complete_buffer are synchronized on something other than
>>> the usb controller.
>>
>> If it's simply to prevent double calling uvcg_complete_buffer() for a buffer, is holding the irqlock necessary? Both uvc_video_complete() and uvc_video_disable() conditionally call uvcg_complete_buffer() based on whether ureq->last_buf is set or not, and both functions assess that whilst holding req_lock, so unless I'm missing something that situation is already guarded against.
> 
> Even without holding queue->irqlock, we guaratee that uvcg_complete_buffer()
> is only called once per buffer (by querying last_buf with req_lock held).
> However, uvcg_complete_buffer modifies queue->flags which in honesty
> will not cause any problems, but is promlematic from theoretical
> correctness standpoint. 
> 
> Either way, this shouldn't be too much overhead, as
> this is only called once per frame, and most requests don't
> have last_buf set anyway. Let me know if you feel strongly
> about not having the locks there, and I can drop it.
> 
>>
>>
>>>
>>>>>        }
>>>>>
>>>>>        spin_lock_irqsave(&video->req_lock, flags);
>>>>> -    list_add_tail(&req->list, &video->req_free);
>>>>> -    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>> -
>>>>> -    if (uvc->state == UVC_STATE_STREAMING)
>>>>> +    /*
>>>>> +     * Video stream might have been disabled while we were
>>>>> +     * processing the current usb_request. So make sure
>>>>> +     * we're still streaming before queueing the usb_request
>>>>> +     * back to req_free
>>>>> +     */
>>>>> +    if (video->is_enabled) {
>>>>> +        list_add_tail(&req->list, &video->req_free);
>>>>>            queue_work(video->async_wq, &video->pump);
>>>>> +    } else {
>>>>> +        uvc_video_free_request(ureq, ep);
>>>>> +    }
>>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>>    }
>>>>>
>>>>>    static int
>>>>> @@ -393,20 +421,22 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>>        struct uvc_video_queue *queue = &video->queue;
>>>>>        /* video->max_payload_size is only set when using bulk transfer */
>>>>>        bool is_bulk = video->max_payload_size;
>>>>> -    struct uvc_device *uvc = video->uvc;
>>>>>        struct usb_request *req = NULL;
>>>>>        struct uvc_buffer *buf;
>>>>>        unsigned long flags;
>>>>>        bool buf_done;
>>>>>        int ret;
>>>>>
>>>>> -    while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>>>>> +    while (true) {
>>>>> +        if (!video->ep->enabled)
>>>>> +            return;
>>>>> +
>>>>>            /*
>>>>> -         * Retrieve the first available USB request, protected by the
>>>>> -         * request lock.
>>>>> +         * Check is_enabled and retrieve the first available USB
>>>>> +         * request, protected by the request lock.
>>>>>             */
>>>>>            spin_lock_irqsave(&video->req_lock, flags);
>>>>> -        if (list_empty(&video->req_free)) {
>>>>> +        if (!video->is_enabled || list_empty(&video->req_free)) {
>>>>>                spin_unlock_irqrestore(&video->req_lock, flags);
>>>>>                return;
>>>>>            }
>>>>> @@ -488,9 +518,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>>            return;
>>>>>
>>>>>        spin_lock_irqsave(&video->req_lock, flags);
>>>>> -    list_add_tail(&req->list, &video->req_free);
>>>>> +    if (video->is_enabled)
>>>>> +        list_add_tail(&req->list, &video->req_free);
>>>>> +    else
>>>>> +        uvc_video_free_request(req->context, video->ep);
>>>>>        spin_unlock_irqrestore(&video->req_lock, flags);
>>>>> -    return;
>>>>>    }
>>>>>
>>>>>    /*
>>>>> @@ -499,7 +531,11 @@ static void uvcg_video_pump(struct work_struct *work)
>>>>>    int
>>>>>    uvcg_video_disable(struct uvc_video *video)
>>>>>    {
>>>>> -    struct uvc_request *ureq;
>>>>> +    unsigned long flags;
>>>>> +    struct list_head inflight_bufs;
>>>>> +    struct usb_request *req, *temp;
>>>>> +    struct uvc_buffer *buf, *btemp;
>>>>> +    struct uvc_request *ureq, *utemp;
>>>>>
>>>>>        if (video->ep == NULL) {
>>>>>            uvcg_info(&video->uvc->func,
>>>>> @@ -507,15 +543,58 @@ uvcg_video_disable(struct uvc_video *video)
>>>>>            return -ENODEV;
>>>>>        }
>>>>>
>>>>> +    INIT_LIST_HEAD(&inflight_bufs);
>>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>>> +    video->is_enabled = false;
>>>>> +
>>>>> +    /*
>>>>> +     * Remove any in-flight buffers from the uvc_requests
>>>>> +     * because we want to return them before cancelling the
>>>>> +     * queue. This ensures that we aren't stuck waiting for
>>>>> +     * all complete callbacks to come through before disabling
>>>>> +     * vb2 queue.
>>>>> +     */
>>>>> +    list_for_each_entry(ureq, &video->ureqs, list) {
>>>>> +        if (ureq->last_buf) {
>>>>> +            list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
>>>>> +            ureq->last_buf = NULL;
>>>>> +        }
>>>>> +    }
>>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>> +
>>>>>        cancel_work_sync(&video->pump);
>>>>>        uvcg_queue_cancel(&video->queue, 0);
>>>>>
>>>>> -    list_for_each_entry(ureq, &video->ureqs, list) {
>>>>> -        if (ureq->req)
>>>>> -            usb_ep_dequeue(video->ep, ureq->req);
>>>>> +    spin_lock_irqsave(&video->req_lock, flags);
>>>>> +    /*
>>>>> +     * Remove all uvc_reqeusts from ureqs with list_del_init
>> s/uvc_reqeusts/uvc_requests
>>>>> +     * This lets uvc_video_free_request correctly identify
>>>>> +     * if the uvc_request is attached to a list or not when freeing
>>>>> +     * memory.
>>>>> +     */
>>>>> +    list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
>>>>> +        list_del_init(&ureq->list);
>>>>> +
>>>>> +    list_for_each_entry_safe(req, temp, &video->req_free, list) {
>>>>> +        list_del(&req->list);
>>>>> +        uvc_video_free_request(req->context, video->ep);
>>>>>        }
>>>>>
>>>>> -    uvc_video_free_requests(video);
>>>>> +    INIT_LIST_HEAD(&video->ureqs);
>>>>> +    INIT_LIST_HEAD(&video->req_free);
>>>>> +    video->req_size = 0;
>>>>> +    spin_unlock_irqrestore(&video->req_lock, flags);
>>>>> +
>>>>> +    /*
>>>>> +     * Return all the video buffers before disabling the queue.
>>>>> +     */
>>>>> +    spin_lock_irqsave(&video->queue.irqlock, flags);
>>>>> +    list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
>>>>> +        list_del(&buf->queue);
>>>>> +        uvcg_complete_buffer(&video->queue, buf);
>>>>> +    }
>>>>> +    spin_unlock_irqrestore(&video->queue.irqlock, flags);
>>>>> +
>>>>>        uvcg_queue_enable(&video->queue, 0);
>>>>>        return 0;
>>>>>    }
>>>>> @@ -533,6 +612,14 @@ int uvcg_video_enable(struct uvc_video *video)
>>>>>            return -ENODEV;
>>>>>        }
>>>>>
>>>>> +    /*
>>>>> +     * Safe to access request related fields without req_lock because
>>>>> +     * this is the only thread currently active, and no other
>>>>> +     * request handling thread will become active until this function
>>>>> +     * returns.
>>>>> +     */
>>>>> +    video->is_enabled = true;
>>>>> +
>>>>>        if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>>>>>            return ret;
>>>>>
>>>>> @@ -558,6 +645,7 @@ int uvcg_video_enable(struct uvc_video *video)
>>>>>     */
>>>>>    int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>>>>>    {
>>>>> +    video->is_enabled = false;
>>>>>        INIT_LIST_HEAD(&video->ureqs);
>>>>>        INIT_LIST_HEAD(&video->req_free);
>>>>>        spin_lock_init(&video->req_lock);
>>>>> -- 
>>>>> 2.42.0.820.g83a721a137-goog

Hey Dan, let me know if Patch v11 looks good.

Thank you!
- Avi.

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-02 20:19       ` [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-11-08 14:15         ` Dan Scally
  2023-11-09  1:00           ` Avichal Rakesh
  0 siblings, 1 reply; 94+ messages in thread
From: Dan Scally @ 2023-11-08 14:15 UTC (permalink / raw)
  To: Avichal Rakesh
  Cc: etalvala, gregkh, jchowdhary, laurent.pinchart, linux-kernel,
	linux-usb, m.grzeschik

Hi Avichal

On 02/11/2023 20:19, Avichal Rakesh wrote:
> Currently, the uvc gadget driver allocates all uvc_requests as one array
> and deallocates them all when the video stream stops. This includes
> de-allocating all the usb_requests associated with those uvc_requests.
> This can lead to use-after-free issues if any of those de-allocated
> usb_requests were still owned by the usb controller.
>
> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
> flag to uvc_video to track when frames and requests should be flowing.
> When disabling the video stream, the flag is tripped and, instead
> of de-allocating all uvc_requests and usb_requests, the gadget
> driver only de-allocates those usb_requests that are currently
> owned by it (as present in req_free). Other usb_requests are left
> untouched until their completion handler is called which takes care
> of freeing the usb_request and its corresponding uvc_request.
>
> Now that uvc_video does not depends on uvc->state, this patch removes
> unnecessary upates to uvc->state that were made to accommodate uvc_video
> logic. This should ensure that uvc gadget driver never accidentally
> de-allocates a usb_request that it doesn't own.
>
> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---


Thanks for the update. Let's leave the locking as it is; I think albeit not strictly necessary on 
that occasion it certainly is necessary to take the lock to protect the flags elsewhere, and 
probably better to be consistent with it.


Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

> v1  -> v2  : Rebased to ToT, and fixed deadlock reported in
>               https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
> v2  -> v3  : Fix email threading goof-up
> v3  -> v4  : re-rebase to ToT & moved to a uvc_video level lock
>               as discussed in
>               https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
> v4  -> v5  : Address review comments. Add Reviewed-by & Tested-by.
> v5  -> v6  : Added another patch before this one to make uvcg_video_disable
>               easier to review.
> v6  -> v7  : Fix warning reported in
>               https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
> v7  -> v8  : No change. Getting back in review queue
> v8  -> v9  : No change.
> v9  -> v10 : Address review comments. Rebase to ToT (usb-next)
> v10 -> v11 : Address review comments
>
>   drivers/usb/gadget/function/uvc.h       |   1 +
>   drivers/usb/gadget/function/uvc_v4l2.c  |  10 +-
>   drivers/usb/gadget/function/uvc_video.c | 130 ++++++++++++++++++++----
>   3 files changed, 112 insertions(+), 29 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 993694da0bbc..be0d012aa244 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -102,6 +102,7 @@ struct uvc_video {
>   	unsigned int uvc_num_requests;
>
>   	/* Requests */
> +	bool is_enabled; /* tracks whether video stream is enabled */
>   	unsigned int req_size;
>   	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
>   	struct list_head req_free;
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 904dd283cbf7..c7e5fa4f29e0 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>   	if (type != video->queue.queue.type)
>   		return -EINVAL;
>
> -	uvc->state = UVC_STATE_CONNECTED;
>   	ret = uvcg_video_disable(video);
>   	if (ret < 0)
>   		return ret;
>
> +	uvc->state = UVC_STATE_CONNECTED;
>   	uvc_function_setup_continue(uvc, 1);
>   	return 0;
>   }
> @@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>   static void uvc_v4l2_disable(struct uvc_device *uvc)
>   {
>   	uvc_function_disconnect(uvc);
> -	/*
> -	 * Drop uvc->state to CONNECTED if it was streaming before.
> -	 * This ensures that the usb_requests are no longer queued
> -	 * to the controller.
> -	 */
> -	if (uvc->state == UVC_STATE_STREAMING)
> -		uvc->state = UVC_STATE_CONNECTED;
> -
>   	uvcg_video_disable(&uvc->video);
>   	uvcg_free_buffers(&uvc->video.queue);
>   	uvc->func_connected = false;
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index c3e8c48f46a9..164bdeb7f2a9 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -227,6 +227,10 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
>    * Request handling
>    */
>
> +/*
> + * Callers must take care to hold req_lock when this function may be called
> + * from multiple threads. For example, when frames are streaming to the host.
> + */
>   static void
>   uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
>   {
> @@ -271,9 +275,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   	struct uvc_request *ureq = req->context;
>   	struct uvc_video *video = ureq->video;
>   	struct uvc_video_queue *queue = &video->queue;
> -	struct uvc_device *uvc = video->uvc;
> +	struct uvc_buffer *last_buf;
>   	unsigned long flags;
>
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	if (!video->is_enabled) {
> +		/*
> +		 * When is_enabled is false, uvcg_video_disable() ensures
> +		 * that in-flight uvc_buffers are returned, so we can
> +		 * safely call free_request without worrying about
> +		 * last_buf.
> +		 */
> +		uvc_video_free_request(ureq, ep);
> +		spin_unlock_irqrestore(&video->req_lock, flags);
> +		return;
> +	}
> +
> +	last_buf = ureq->last_buf;
> +	ureq->last_buf = NULL;
> +	spin_unlock_irqrestore(&video->req_lock, flags);
> +
>   	switch (req->status) {
>   	case 0:
>   		break;
> @@ -295,17 +316,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>   		uvcg_queue_cancel(queue, 0);
>   	}
>
> -	if (ureq->last_buf) {
> -		uvcg_complete_buffer(&video->queue, ureq->last_buf);
> -		ureq->last_buf = NULL;
> +	if (last_buf) {
> +		spin_lock_irqsave(&queue->irqlock, flags);
> +		uvcg_complete_buffer(queue, last_buf);
> +		spin_unlock_irqrestore(&queue->irqlock, flags);
>   	}
>
>   	spin_lock_irqsave(&video->req_lock, flags);
> -	list_add_tail(&req->list, &video->req_free);
> -	spin_unlock_irqrestore(&video->req_lock, flags);
> -
> -	if (uvc->state == UVC_STATE_STREAMING)
> +	/*
> +	 * Video stream might have been disabled while we were
> +	 * processing the current usb_request. So make sure
> +	 * we're still streaming before queueing the usb_request
> +	 * back to req_free
> +	 */
> +	if (video->is_enabled) {
> +		list_add_tail(&req->list, &video->req_free);
>   		queue_work(video->async_wq, &video->pump);
> +	} else {
> +		uvc_video_free_request(ureq, ep);
> +	}
> +	spin_unlock_irqrestore(&video->req_lock, flags);
>   }
>
>   static int
> @@ -392,20 +422,22 @@ static void uvcg_video_pump(struct work_struct *work)
>   	struct uvc_video_queue *queue = &video->queue;
>   	/* video->max_payload_size is only set when using bulk transfer */
>   	bool is_bulk = video->max_payload_size;
> -	struct uvc_device *uvc = video->uvc;
>   	struct usb_request *req = NULL;
>   	struct uvc_buffer *buf;
>   	unsigned long flags;
>   	bool buf_done;
>   	int ret;
>
> -	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
> +	while (true) {
> +		if (!video->ep->enabled)
> +			return;
> +
>   		/*
> -		 * Retrieve the first available USB request, protected by the
> -		 * request lock.
> +		 * Check is_enabled and retrieve the first available USB
> +		 * request, protected by the request lock.
>   		 */
>   		spin_lock_irqsave(&video->req_lock, flags);
> -		if (list_empty(&video->req_free)) {
> +		if (!video->is_enabled || list_empty(&video->req_free)) {
>   			spin_unlock_irqrestore(&video->req_lock, flags);
>   			return;
>   		}
> @@ -487,9 +519,11 @@ static void uvcg_video_pump(struct work_struct *work)
>   		return;
>
>   	spin_lock_irqsave(&video->req_lock, flags);
> -	list_add_tail(&req->list, &video->req_free);
> +	if (video->is_enabled)
> +		list_add_tail(&req->list, &video->req_free);
> +	else
> +		uvc_video_free_request(req->context, video->ep);
>   	spin_unlock_irqrestore(&video->req_lock, flags);
> -	return;
>   }
>
>   /*
> @@ -498,7 +532,11 @@ static void uvcg_video_pump(struct work_struct *work)
>   int
>   uvcg_video_disable(struct uvc_video *video)
>   {
> -	struct uvc_request *ureq;
> +	unsigned long flags;
> +	struct list_head inflight_bufs;
> +	struct usb_request *req, *temp;
> +	struct uvc_buffer *buf, *btemp;
> +	struct uvc_request *ureq, *utemp;
>
>   	if (video->ep == NULL) {
>   		uvcg_info(&video->uvc->func,
> @@ -506,15 +544,58 @@ uvcg_video_disable(struct uvc_video *video)
>   		return -ENODEV;
>   	}
>
> +	INIT_LIST_HEAD(&inflight_bufs);
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	video->is_enabled = false;
> +
> +	/*
> +	 * Remove any in-flight buffers from the uvc_requests
> +	 * because we want to return them before cancelling the
> +	 * queue. This ensures that we aren't stuck waiting for
> +	 * all complete callbacks to come through before disabling
> +	 * vb2 queue.
> +	 */
> +	list_for_each_entry(ureq, &video->ureqs, list) {
> +		if (ureq->last_buf) {
> +			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
> +			ureq->last_buf = NULL;
> +		}
> +	}
> +	spin_unlock_irqrestore(&video->req_lock, flags);
> +
>   	cancel_work_sync(&video->pump);
>   	uvcg_queue_cancel(&video->queue, 0);
>
> -	list_for_each_entry(ureq, &video->ureqs, list) {
> -		if (ureq->req)
> -			usb_ep_dequeue(video->ep, ureq->req);
> +	spin_lock_irqsave(&video->req_lock, flags);
> +	/*
> +	 * Remove all uvc_requests from ureqs with list_del_init
> +	 * This lets uvc_video_free_request correctly identify
> +	 * if the uvc_request is attached to a list or not when freeing
> +	 * memory.
> +	 */
> +	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
> +		list_del_init(&ureq->list);
> +
> +	list_for_each_entry_safe(req, temp, &video->req_free, list) {
> +		list_del(&req->list);
> +		uvc_video_free_request(req->context, video->ep);
>   	}
>
> -	uvc_video_free_requests(video);
> +	INIT_LIST_HEAD(&video->ureqs);
> +	INIT_LIST_HEAD(&video->req_free);
> +	video->req_size = 0;
> +	spin_unlock_irqrestore(&video->req_lock, flags);
> +
> +	/*
> +	 * Return all the video buffers before disabling the queue.
> +	 */
> +	spin_lock_irqsave(&video->queue.irqlock, flags);
> +	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
> +		list_del(&buf->queue);
> +		uvcg_complete_buffer(&video->queue, buf);
> +	}
> +	spin_unlock_irqrestore(&video->queue.irqlock, flags);
> +
>   	uvcg_queue_enable(&video->queue, 0);
>   	return 0;
>   }
> @@ -532,6 +613,14 @@ int uvcg_video_enable(struct uvc_video *video)
>   		return -ENODEV;
>   	}
>
> +	/*
> +	 * Safe to access request related fields without req_lock because
> +	 * this is the only thread currently active, and no other
> +	 * request handling thread will become active until this function
> +	 * returns.
> +	 */
> +	video->is_enabled = true;
> +
>   	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
>   		return ret;
>
> @@ -557,6 +646,7 @@ int uvcg_video_enable(struct uvc_video *video)
>    */
>   int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>   {
> +	video->is_enabled = false;
>   	INIT_LIST_HEAD(&video->ureqs);
>   	INIT_LIST_HEAD(&video->req_free);
>   	spin_lock_init(&video->req_lock);
> --
> 2.42.0.869.gea05f2083d-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

* [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
                       ` (2 preceding siblings ...)
  2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-11-09  0:41     ` Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
                         ` (3 more replies)
  3 siblings, 4 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-09  0:41 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik

Currently the set_alt callback immediately disables the endpoint and queues
the v4l2 streamoff event. However, as the streamoff event is processed
asynchronously, it is possible that the video_pump thread attempts to queue
requests to an already disabled endpoint.

This change moves disabling usb endpoint to the end of streamoff event
callback. As the endpoint's state can no longer be used, video_pump is
now guarded by uvc->state as well. To be consistent with the actual
streaming state, uvc->state is now toggled between CONNECTED and STREAMING
from the v4l2 event callback only.

Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT and reworded commit message.
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : Address review comments & re-rebase to ToT
v4  -> v5  : Add Reviewed-by & Tested-by
v5  -> v6  : No change
v6  -> v7  : No change
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Fix typo. No functional change.
v9  -> v10 : Rebase to ToT (usb-next)
v10 -> v11 : No change
v11 -> v12 : Rebase to ToT (usb-next)

 drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
 drivers/usb/gadget/function/f_uvc.h     |  2 +-
 drivers/usb/gadget/function/uvc.h       |  2 +-
 drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
 drivers/usb/gadget/function/uvc_video.c |  3 ++-
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 786379f1b7b7..77999ed53d33 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	return 0;
 }

-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;

+	if (disable_ep && uvc->video.ep)
+		usb_ep_disable(uvc->video.ep);
+
 	usb_composite_setup_continue(cdev);
 }

@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;

-		if (uvc->video.ep)
-			usb_ep_disable(uvc->video.ep);
-
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);

-		uvc->state = UVC_STATE_CONNECTED;
-		return 0;
+		return USB_GADGET_DELAYED_STATUS;

 	case 1:
 		if (uvc->state != UVC_STATE_CONNECTED)
diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
index 1db972d4beeb..083aef0c65c6 100644
--- a/drivers/usb/gadget/function/f_uvc.h
+++ b/drivers/usb/gadget/function/f_uvc.h
@@ -11,7 +11,7 @@

 struct uvc_device;

-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);

 void uvc_function_connect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 6751de8b63ad..989bc6b4e93d 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -177,7 +177,7 @@ struct uvc_file_handle {
  * Functions
  */

-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 3f0a9795c0d4..7cb8d027ff0c 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 	 * Complete the alternate setting selection setup phase now that
 	 * userspace is ready to provide video frames.
 	 */
-	uvc_function_setup_continue(uvc);
+	uvc_function_setup_continue(uvc, 0);
 	uvc->state = UVC_STATE_STREAMING;

 	return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	int ret = 0;

 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	return uvcg_video_enable(video, 0);
+	uvc->state = UVC_STATE_CONNECTED;
+	ret = uvcg_video_enable(video, 0);
+	if (ret < 0)
+		return ret;
+
+	uvc_function_setup_continue(uvc, 1);
+	return 0;
 }

 static int
@@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
+	/*
+	 * Drop uvc->state to CONNECTED if it was streaming before.
+	 * This ensures that the usb_requests are no longer queued
+	 * to the controller.
+	 */
+	if (uvc->state == UVC_STATE_STREAMING)
+		uvc->state = UVC_STATE_CONNECTED;
+
 	uvcg_video_enable(&uvc->video, 0);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
@@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
 	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0d4..c334802ac0a4 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
+	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (video->ep->enabled) {
+	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
 		/*
 		 * Retrieve the first available USB request, protected by the
 		 * request lock.
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v12 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
@ 2023-11-09  0:41       ` Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-09  0:41 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This patch is 1 of 2 patches addressing the use-after-free issue.
Instead of bulk allocating all uvc_requests as an array, this patch
allocates uvc_requests one at a time, which should allows for similar
granularity when deallocating the uvc_requests. This patch has no
functional changes other than allocating each uvc_request separately,
and similarly freeing each of them separately.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : Address review comments & re-rebase to ToT
v4  -> v5  : Address more review comments. Add Reviewed-by & Tested-by.
v5  -> v6  : No change
v6  -> v7  : No change
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Address review comments.
v9  -> v10 : Address review comments; remove BUG_ON(&video->reqs);
             Rebase to ToT (usb-next)
v10 -> v11 : Add Reviewed-by
v11 -> v12 : Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc.h       |  3 +-
 drivers/usb/gadget/function/uvc_video.c | 88 ++++++++++++++-----------
 2 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 989bc6b4e93d..993694da0bbc 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -81,6 +81,7 @@ struct uvc_request {
 	struct sg_table sgt;
 	u8 header[UVCG_REQUEST_HEADER_LEN];
 	struct uvc_buffer *last_buf;
+	struct list_head list;
 };

 struct uvc_video {
@@ -102,7 +103,7 @@ struct uvc_video {

 	/* Requests */
 	unsigned int req_size;
-	struct uvc_request *ureq;
+	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
 	spinlock_t req_lock;

diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c334802ac0a4..1619f9664748 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,24 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+	sg_free_table(&ureq->sgt);
+	if (ureq->req && ep) {
+		usb_ep_free_request(ep, ureq->req);
+		ureq->req = NULL;
+	}
+
+	kfree(ureq->req_buffer);
+	ureq->req_buffer = NULL;
+
+	if (!list_empty(&ureq->list))
+		list_del_init(&ureq->list);
+
+	kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
 	int ret;
@@ -293,27 +311,12 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-	unsigned int i;
-
-	if (video->ureq) {
-		for (i = 0; i < video->uvc_num_requests; ++i) {
-			sg_free_table(&video->ureq[i].sgt);
+	struct uvc_request *ureq, *temp;

-			if (video->ureq[i].req) {
-				usb_ep_free_request(video->ep, video->ureq[i].req);
-				video->ureq[i].req = NULL;
-			}
-
-			if (video->ureq[i].req_buffer) {
-				kfree(video->ureq[i].req_buffer);
-				video->ureq[i].req_buffer = NULL;
-			}
-		}
-
-		kfree(video->ureq);
-		video->ureq = NULL;
-	}
+	list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+		uvc_video_free_request(ureq, video->ep);

+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	video->req_size = 0;
 	return 0;
@@ -322,6 +325,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+	struct uvc_request *ureq;
 	unsigned int req_size;
 	unsigned int i;
 	int ret = -ENOMEM;
@@ -332,29 +336,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
 		 * max_t(unsigned int, video->ep->maxburst, 1)
 		 * (video->ep->mult);

-	video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-	if (video->ureq == NULL)
-		return -ENOMEM;
+	for (i = 0; i < video->uvc_num_requests; i++) {
+		ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+		if (ureq == NULL)
+			goto error;
+
+		INIT_LIST_HEAD(&ureq->list);
+
+		list_add_tail(&ureq->list, &video->ureqs);

-	for (i = 0; i < video->uvc_num_requests; ++i) {
-		video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-		if (video->ureq[i].req_buffer == NULL)
+		ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+		if (ureq->req_buffer == NULL)
 			goto error;

-		video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-		if (video->ureq[i].req == NULL)
+		ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (ureq->req == NULL)
 			goto error;

-		video->ureq[i].req->buf = video->ureq[i].req_buffer;
-		video->ureq[i].req->length = 0;
-		video->ureq[i].req->complete = uvc_video_complete;
-		video->ureq[i].req->context = &video->ureq[i];
-		video->ureq[i].video = video;
-		video->ureq[i].last_buf = NULL;
+		ureq->req->buf = ureq->req_buffer;
+		ureq->req->length = 0;
+		ureq->req->complete = uvc_video_complete;
+		ureq->req->context = ureq;
+		ureq->video = video;
+		ureq->last_buf = NULL;

-		list_add_tail(&video->ureq[i].req->list, &video->req_free);
+		list_add_tail(&ureq->req->list, &video->req_free);
 		/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-		sg_alloc_table(&video->ureq[i].sgt,
+		sg_alloc_table(&ureq->sgt,
 			       DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
 					    PAGE_SIZE) + 2, GFP_KERNEL);
 	}
@@ -489,8 +497,8 @@ static void uvcg_video_pump(struct work_struct *work)
  */
 int uvcg_video_enable(struct uvc_video *video, int enable)
 {
-	unsigned int i;
 	int ret;
+	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -502,9 +510,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 		cancel_work_sync(&video->pump);
 		uvcg_queue_cancel(&video->queue, 0);

-		for (i = 0; i < video->uvc_num_requests; ++i)
-			if (video->ureq && video->ureq[i].req)
-				usb_ep_dequeue(video->ep, video->ureq[i].req);
+		list_for_each_entry(ureq, &video->ureqs, list) {
+			if (ureq->req)
+				usb_ep_dequeue(video->ep, ureq->req);
+		}

 		uvc_video_free_requests(video);
 		uvcg_queue_enable(&video->queue, 0);
@@ -536,6 +545,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v12 3/4] usb: gadget: uvc: move video disable logic to its own function
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
@ 2023-11-09  0:41       ` Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
  2023-11-14 21:04       ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-09  0:41 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik

This patch refactors the video disable logic in uvcg_video_enable
into its own separate function 'uvcg_video_disable'. This function
is now used anywhere uvcg_video_enable(video, 0) was used.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
XX  -> v6  : Introduced this patch to make the next one easier to review
v6  -> v7  : Add Suggested-by
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : Call uvcg_video_disable directly instead of uvcg_video_enable(video, 0)
v9  -> v10 : Rebase to ToT (usb-next)
v10 -> v11 : No change
v11 -> v12 : Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc_v4l2.c  |  6 ++--
 drivers/usb/gadget/function/uvc_video.c | 40 ++++++++++++++++---------
 drivers/usb/gadget/function/uvc_video.h |  3 +-
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7cb8d027ff0c..904dd283cbf7 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	/* Enable UVC video. */
-	ret = uvcg_video_enable(video, 1);
+	ret = uvcg_video_enable(video);
 	if (ret < 0)
 		return ret;

@@ -469,7 +469,7 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 		return -EINVAL;

 	uvc->state = UVC_STATE_CONNECTED;
-	ret = uvcg_video_enable(video, 0);
+	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

@@ -515,7 +515,7 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
 	if (uvc->state == UVC_STATE_STREAMING)
 		uvc->state = UVC_STATE_CONNECTED;

-	uvcg_video_enable(&uvc->video, 0);
+	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
 	wake_up_interruptible(&uvc->func_connected_queue);
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 1619f9664748..c3e8c48f46a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -493,31 +493,43 @@ static void uvcg_video_pump(struct work_struct *work)
 }

 /*
- * Enable or disable the video stream.
+ * Disable the video stream
  */
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
 {
-	int ret;
 	struct uvc_request *ureq;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
-			  "Video enable failed, device is uninitialized.\n");
+			  "Video disable failed, device is uninitialized.\n");
 		return -ENODEV;
 	}

-	if (!enable) {
-		cancel_work_sync(&video->pump);
-		uvcg_queue_cancel(&video->queue, 0);
+	cancel_work_sync(&video->pump);
+	uvcg_queue_cancel(&video->queue, 0);

-		list_for_each_entry(ureq, &video->ureqs, list) {
-			if (ureq->req)
-				usb_ep_dequeue(video->ep, ureq->req);
-		}
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->req)
+			usb_ep_dequeue(video->ep, ureq->req);
+	}

-		uvc_video_free_requests(video);
-		uvcg_queue_enable(&video->queue, 0);
-		return 0;
+	uvc_video_free_requests(video);
+	uvcg_queue_enable(&video->queue, 0);
+	return 0;
+}
+
+/*
+ * Enable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+	int ret;
+
+	if (video->ep == NULL) {
+		uvcg_info(&video->uvc->func,
+			  "Video enable failed, device is uninitialized.\n");
+		return -ENODEV;
 	}

 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
index 03adeefa343b..8ef6259741f1 100644
--- a/drivers/usb/gadget/function/uvc_video.h
+++ b/drivers/usb/gadget/function/uvc_video.h
@@ -14,7 +14,8 @@

 struct uvc_video;

-int uvcg_video_enable(struct uvc_video *video, int enable);
+int uvcg_video_enable(struct uvc_video *video);
+int uvcg_video_disable(struct uvc_video *video);

 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);

--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* [PATCH v12 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
  2023-11-09  0:41       ` [PATCH v12 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
@ 2023-11-09  0:41       ` Avichal Rakesh
  2023-11-14 21:04       ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-09  0:41 UTC (permalink / raw)
  To: arakesh, dan.scally, gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik

Currently, the uvc gadget driver allocates all uvc_requests as one array
and deallocates them all when the video stream stops. This includes
de-allocating all the usb_requests associated with those uvc_requests.
This can lead to use-after-free issues if any of those de-allocated
usb_requests were still owned by the usb controller.

This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
flag to uvc_video to track when frames and requests should be flowing.
When disabling the video stream, the flag is tripped and, instead
of de-allocating all uvc_requests and usb_requests, the gadget
driver only de-allocates those usb_requests that are currently
owned by it (as present in req_free). Other usb_requests are left
untouched until their completion handler is called which takes care
of freeing the usb_request and its corresponding uvc_request.

Now that uvc_video does not depends on uvc->state, this patch removes
unnecessary upates to uvc->state that were made to accommodate uvc_video
logic. This should ensure that uvc gadget driver never accidentally
de-allocates a usb_request that it doesn't own.

Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Avichal Rakesh <arakesh@google.com>
---
v1  -> v2  : Rebased to ToT, and fixed deadlock reported in
             https://lore.kernel.org/all/ZRv2UnKztgyqk2pt@pengutronix.de/
v2  -> v3  : Fix email threading goof-up
v3  -> v4  : re-rebase to ToT & moved to a uvc_video level lock
             as discussed in
             https://lore.kernel.org/b14b296f-2e08-4edf-aeea-1c5b621e2d0c@google.com/
v4  -> v5  : Address review comments. Add Reviewed-by & Tested-by.
v5  -> v6  : Added another patch before this one to make uvcg_video_disable
             easier to review.
v6  -> v7  : Fix warning reported in
             https://lore.kernel.org/202310200457.GwPPFuHX-lkp@intel.com/
v7  -> v8  : No change. Getting back in review queue
v8  -> v9  : No change.
v9  -> v10 : Address review comments. Rebase to ToT (usb-next)
v10 -> v11 : Address review comments
v11 -> v12 : Add Reviewed-by; Rebase to ToT (usb-next)

 drivers/usb/gadget/function/uvc.h       |   1 +
 drivers/usb/gadget/function/uvc_v4l2.c  |  10 +-
 drivers/usb/gadget/function/uvc_video.c | 130 ++++++++++++++++++++----
 3 files changed, 112 insertions(+), 29 deletions(-)

diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 993694da0bbc..be0d012aa244 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -102,6 +102,7 @@ struct uvc_video {
 	unsigned int uvc_num_requests;

 	/* Requests */
+	bool is_enabled; /* tracks whether video stream is enabled */
 	unsigned int req_size;
 	struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
 	struct list_head req_free;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 904dd283cbf7..c7e5fa4f29e0 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -468,11 +468,11 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
 	if (type != video->queue.queue.type)
 		return -EINVAL;

-	uvc->state = UVC_STATE_CONNECTED;
 	ret = uvcg_video_disable(video);
 	if (ret < 0)
 		return ret;

+	uvc->state = UVC_STATE_CONNECTED;
 	uvc_function_setup_continue(uvc, 1);
 	return 0;
 }
@@ -507,14 +507,6 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
 	uvc_function_disconnect(uvc);
-	/*
-	 * Drop uvc->state to CONNECTED if it was streaming before.
-	 * This ensures that the usb_requests are no longer queued
-	 * to the controller.
-	 */
-	if (uvc->state == UVC_STATE_STREAMING)
-		uvc->state = UVC_STATE_CONNECTED;
-
 	uvcg_video_disable(&uvc->video);
 	uvcg_free_buffers(&uvc->video.queue);
 	uvc->func_connected = false;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index c3e8c48f46a9..164bdeb7f2a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -227,6 +227,10 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */

+/*
+ * Callers must take care to hold req_lock when this function may be called
+ * from multiple threads. For example, when frames are streaming to the host.
+ */
 static void
 uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
 {
@@ -271,9 +275,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 	struct uvc_request *ureq = req->context;
 	struct uvc_video *video = ureq->video;
 	struct uvc_video_queue *queue = &video->queue;
-	struct uvc_device *uvc = video->uvc;
+	struct uvc_buffer *last_buf;
 	unsigned long flags;

+	spin_lock_irqsave(&video->req_lock, flags);
+	if (!video->is_enabled) {
+		/*
+		 * When is_enabled is false, uvcg_video_disable() ensures
+		 * that in-flight uvc_buffers are returned, so we can
+		 * safely call free_request without worrying about
+		 * last_buf.
+		 */
+		uvc_video_free_request(ureq, ep);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+		return;
+	}
+
+	last_buf = ureq->last_buf;
+	ureq->last_buf = NULL;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	switch (req->status) {
 	case 0:
 		break;
@@ -295,17 +316,26 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		uvcg_queue_cancel(queue, 0);
 	}

-	if (ureq->last_buf) {
-		uvcg_complete_buffer(&video->queue, ureq->last_buf);
-		ureq->last_buf = NULL;
+	if (last_buf) {
+		spin_lock_irqsave(&queue->irqlock, flags);
+		uvcg_complete_buffer(queue, last_buf);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
 	}

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
-	spin_unlock_irqrestore(&video->req_lock, flags);
-
-	if (uvc->state == UVC_STATE_STREAMING)
+	/*
+	 * Video stream might have been disabled while we were
+	 * processing the current usb_request. So make sure
+	 * we're still streaming before queueing the usb_request
+	 * back to req_free
+	 */
+	if (video->is_enabled) {
+		list_add_tail(&req->list, &video->req_free);
 		queue_work(video->async_wq, &video->pump);
+	} else {
+		uvc_video_free_request(ureq, ep);
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
 }

 static int
@@ -392,20 +422,22 @@ static void uvcg_video_pump(struct work_struct *work)
 	struct uvc_video_queue *queue = &video->queue;
 	/* video->max_payload_size is only set when using bulk transfer */
 	bool is_bulk = video->max_payload_size;
-	struct uvc_device *uvc = video->uvc;
 	struct usb_request *req = NULL;
 	struct uvc_buffer *buf;
 	unsigned long flags;
 	bool buf_done;
 	int ret;

-	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
+	while (true) {
+		if (!video->ep->enabled)
+			return;
+
 		/*
-		 * Retrieve the first available USB request, protected by the
-		 * request lock.
+		 * Check is_enabled and retrieve the first available USB
+		 * request, protected by the request lock.
 		 */
 		spin_lock_irqsave(&video->req_lock, flags);
-		if (list_empty(&video->req_free)) {
+		if (!video->is_enabled || list_empty(&video->req_free)) {
 			spin_unlock_irqrestore(&video->req_lock, flags);
 			return;
 		}
@@ -487,9 +519,11 @@ static void uvcg_video_pump(struct work_struct *work)
 		return;

 	spin_lock_irqsave(&video->req_lock, flags);
-	list_add_tail(&req->list, &video->req_free);
+	if (video->is_enabled)
+		list_add_tail(&req->list, &video->req_free);
+	else
+		uvc_video_free_request(req->context, video->ep);
 	spin_unlock_irqrestore(&video->req_lock, flags);
-	return;
 }

 /*
@@ -498,7 +532,11 @@ static void uvcg_video_pump(struct work_struct *work)
 int
 uvcg_video_disable(struct uvc_video *video)
 {
-	struct uvc_request *ureq;
+	unsigned long flags;
+	struct list_head inflight_bufs;
+	struct usb_request *req, *temp;
+	struct uvc_buffer *buf, *btemp;
+	struct uvc_request *ureq, *utemp;

 	if (video->ep == NULL) {
 		uvcg_info(&video->uvc->func,
@@ -506,15 +544,58 @@ uvcg_video_disable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	INIT_LIST_HEAD(&inflight_bufs);
+	spin_lock_irqsave(&video->req_lock, flags);
+	video->is_enabled = false;
+
+	/*
+	 * Remove any in-flight buffers from the uvc_requests
+	 * because we want to return them before cancelling the
+	 * queue. This ensures that we aren't stuck waiting for
+	 * all complete callbacks to come through before disabling
+	 * vb2 queue.
+	 */
+	list_for_each_entry(ureq, &video->ureqs, list) {
+		if (ureq->last_buf) {
+			list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+			ureq->last_buf = NULL;
+		}
+	}
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
 	cancel_work_sync(&video->pump);
 	uvcg_queue_cancel(&video->queue, 0);

-	list_for_each_entry(ureq, &video->ureqs, list) {
-		if (ureq->req)
-			usb_ep_dequeue(video->ep, ureq->req);
+	spin_lock_irqsave(&video->req_lock, flags);
+	/*
+	 * Remove all uvc_requests from ureqs with list_del_init
+	 * This lets uvc_video_free_request correctly identify
+	 * if the uvc_request is attached to a list or not when freeing
+	 * memory.
+	 */
+	list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+		list_del_init(&ureq->list);
+
+	list_for_each_entry_safe(req, temp, &video->req_free, list) {
+		list_del(&req->list);
+		uvc_video_free_request(req->context, video->ep);
 	}

-	uvc_video_free_requests(video);
+	INIT_LIST_HEAD(&video->ureqs);
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	spin_unlock_irqrestore(&video->req_lock, flags);
+
+	/*
+	 * Return all the video buffers before disabling the queue.
+	 */
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+		list_del(&buf->queue);
+		uvcg_complete_buffer(&video->queue, buf);
+	}
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
 	uvcg_queue_enable(&video->queue, 0);
 	return 0;
 }
@@ -532,6 +613,14 @@ int uvcg_video_enable(struct uvc_video *video)
 		return -ENODEV;
 	}

+	/*
+	 * Safe to access request related fields without req_lock because
+	 * this is the only thread currently active, and no other
+	 * request handling thread will become active until this function
+	 * returns.
+	 */
+	video->is_enabled = true;
+
 	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
 		return ret;

@@ -557,6 +646,7 @@ int uvcg_video_enable(struct uvc_video *video)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	video->is_enabled = false;
 	INIT_LIST_HEAD(&video->ureqs);
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
--
2.42.0.869.gea05f2083d-goog

^ permalink raw reply related	[flat|nested] 94+ messages in thread

* Re: [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests
  2023-11-08 14:15         ` Dan Scally
@ 2023-11-09  1:00           ` Avichal Rakesh
  0 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-09  1:00 UTC (permalink / raw)
  To: Dan Scally, gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik



On 11/8/23 06:15, Dan Scally wrote:
> Hi Avichal
> 
> On 02/11/2023 20:19, Avichal Rakesh wrote:
>> Currently, the uvc gadget driver allocates all uvc_requests as one array
>> and deallocates them all when the video stream stops. This includes
>> de-allocating all the usb_requests associated with those uvc_requests.
>> This can lead to use-after-free issues if any of those de-allocated
>> usb_requests were still owned by the usb controller.
>>
>> This is patch 2 of 2 in fixing the use-after-free issue. It adds a new
>> flag to uvc_video to track when frames and requests should be flowing.
>> When disabling the video stream, the flag is tripped and, instead
>> of de-allocating all uvc_requests and usb_requests, the gadget
>> driver only de-allocates those usb_requests that are currently
>> owned by it (as present in req_free). Other usb_requests are left
>> untouched until their completion handler is called which takes care
>> of freeing the usb_request and its corresponding uvc_request.
>>
>> Now that uvc_video does not depends on uvc->state, this patch removes
>> unnecessary upates to uvc->state that were made to accommodate uvc_video
>> logic. This should ensure that uvc gadget driver never accidentally
>> de-allocates a usb_request that it doesn't own.
>>
>> Link: https://lore.kernel.org/7cd81649-2795-45b6-8c10-b7df1055020d@google.com
>> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Suggested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
>> Signed-off-by: Avichal Rakesh <arakesh@google.com>
>> ---
> 
> 
> Thanks for the update. Let's leave the locking as it is; I think albeit not strictly necessary on that occasion it certainly is necessary to take the lock to protect the flags elsewhere, and probably better to be consistent with it.
> 
> 
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

Thank you for reviewing, Dan!

Greg, I just sent out v12 with the Reviewed-by tag:
https://lore.kernel.org/all/20231109004104.3467968-1-arakesh@google.com/ 
They should be ready to submit now. Thank you!

Regards,
Avi.

> 
>> <snip>

^ permalink raw reply	[flat|nested] 94+ messages in thread

* Re: [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint
  2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
                         ` (2 preceding siblings ...)
  2023-11-09  0:41       ` [PATCH v12 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
@ 2023-11-14 21:04       ` Avichal Rakesh
  3 siblings, 0 replies; 94+ messages in thread
From: Avichal Rakesh @ 2023-11-14 21:04 UTC (permalink / raw)
  To: gregkh
  Cc: etalvala, jchowdhary, laurent.pinchart, linux-kernel, linux-usb,
	m.grzeschik, dan.scally

Hey Greg, I think this patchset is ready for submission. 
Let me know if I am missing something and something else
needs to be done.

Thank you!
- Avi

On 11/8/23 16:41, Avichal Rakesh wrote:
> Currently the set_alt callback immediately disables the endpoint and queues
> the v4l2 streamoff event. However, as the streamoff event is processed
> asynchronously, it is possible that the video_pump thread attempts to queue
> requests to an already disabled endpoint.
> 
> This change moves disabling usb endpoint to the end of streamoff event
> callback. As the endpoint's state can no longer be used, video_pump is
> now guarded by uvc->state as well. To be consistent with the actual
> streaming state, uvc->state is now toggled between CONNECTED and STREAMING
> from the v4l2 event callback only.
> 
> Link: https://lore.kernel.org/20230615171558.GK741@pendragon.ideasonboard.com/
> Link: https://lore.kernel.org/20230531085544.253363-1-dan.scally@ideasonboard.com/
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Reviewed-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Tested-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> Signed-off-by: Avichal Rakesh <arakesh@google.com>
> ---
> v1  -> v2  : Rebased to ToT and reworded commit message.
> v2  -> v3  : Fix email threading goof-up
> v3  -> v4  : Address review comments & re-rebase to ToT
> v4  -> v5  : Add Reviewed-by & Tested-by
> v5  -> v6  : No change
> v6  -> v7  : No change
> v7  -> v8  : No change. Getting back in review queue
> v8  -> v9  : Fix typo. No functional change.
> v9  -> v10 : Rebase to ToT (usb-next)
> v10 -> v11 : No change
> v11 -> v12 : Rebase to ToT (usb-next)
> 
>  drivers/usb/gadget/function/f_uvc.c     | 11 +++++------
>  drivers/usb/gadget/function/f_uvc.h     |  2 +-
>  drivers/usb/gadget/function/uvc.h       |  2 +-
>  drivers/usb/gadget/function/uvc_v4l2.c  | 20 +++++++++++++++++---
>  drivers/usb/gadget/function/uvc_video.c |  3 ++-
>  5 files changed, 26 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index 786379f1b7b7..77999ed53d33 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
>  	return 0;
>  }
> 
> -void uvc_function_setup_continue(struct uvc_device *uvc)
> +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
>  {
>  	struct usb_composite_dev *cdev = uvc->func.config->cdev;
> 
> +	if (disable_ep && uvc->video.ep)
> +		usb_ep_disable(uvc->video.ep);
> +
>  	usb_composite_setup_continue(cdev);
>  }
> 
> @@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
>  		if (uvc->state != UVC_STATE_STREAMING)
>  			return 0;
> 
> -		if (uvc->video.ep)
> -			usb_ep_disable(uvc->video.ep);
> -
>  		memset(&v4l2_event, 0, sizeof(v4l2_event));
>  		v4l2_event.type = UVC_EVENT_STREAMOFF;
>  		v4l2_event_queue(&uvc->vdev, &v4l2_event);
> 
> -		uvc->state = UVC_STATE_CONNECTED;
> -		return 0;
> +		return USB_GADGET_DELAYED_STATUS;
> 
>  	case 1:
>  		if (uvc->state != UVC_STATE_CONNECTED)
> diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h
> index 1db972d4beeb..083aef0c65c6 100644
> --- a/drivers/usb/gadget/function/f_uvc.h
> +++ b/drivers/usb/gadget/function/f_uvc.h
> @@ -11,7 +11,7 @@
> 
>  struct uvc_device;
> 
> -void uvc_function_setup_continue(struct uvc_device *uvc);
> +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
> 
>  void uvc_function_connect(struct uvc_device *uvc);
> 
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 6751de8b63ad..989bc6b4e93d 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -177,7 +177,7 @@ struct uvc_file_handle {
>   * Functions
>   */
> 
> -extern void uvc_function_setup_continue(struct uvc_device *uvc);
> +extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
>  extern void uvc_function_connect(struct uvc_device *uvc);
>  extern void uvc_function_disconnect(struct uvc_device *uvc);
> 
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 3f0a9795c0d4..7cb8d027ff0c 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
>  	 * Complete the alternate setting selection setup phase now that
>  	 * userspace is ready to provide video frames.
>  	 */
> -	uvc_function_setup_continue(uvc);
> +	uvc_function_setup_continue(uvc, 0);
>  	uvc->state = UVC_STATE_STREAMING;
> 
>  	return 0;
> @@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
>  	struct video_device *vdev = video_devdata(file);
>  	struct uvc_device *uvc = video_get_drvdata(vdev);
>  	struct uvc_video *video = &uvc->video;
> +	int ret = 0;
> 
>  	if (type != video->queue.queue.type)
>  		return -EINVAL;
> 
> -	return uvcg_video_enable(video, 0);
> +	uvc->state = UVC_STATE_CONNECTED;
> +	ret = uvcg_video_enable(video, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	uvc_function_setup_continue(uvc, 1);
> +	return 0;
>  }
> 
>  static int
> @@ -500,6 +507,14 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
>  static void uvc_v4l2_disable(struct uvc_device *uvc)
>  {
>  	uvc_function_disconnect(uvc);
> +	/*
> +	 * Drop uvc->state to CONNECTED if it was streaming before.
> +	 * This ensures that the usb_requests are no longer queued
> +	 * to the controller.
> +	 */
> +	if (uvc->state == UVC_STATE_STREAMING)
> +		uvc->state = UVC_STATE_CONNECTED;
> +
>  	uvcg_video_enable(&uvc->video, 0);
>  	uvcg_free_buffers(&uvc->video.queue);
>  	uvc->func_connected = false;
> @@ -647,4 +662,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
>  	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
>  #endif
>  };
> -
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index 91af3b1ef0d4..c334802ac0a4 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -384,13 +384,14 @@ static void uvcg_video_pump(struct work_struct *work)
>  	struct uvc_video_queue *queue = &video->queue;
>  	/* video->max_payload_size is only set when using bulk transfer */
>  	bool is_bulk = video->max_payload_size;
> +	struct uvc_device *uvc = video->uvc;
>  	struct usb_request *req = NULL;
>  	struct uvc_buffer *buf;
>  	unsigned long flags;
>  	bool buf_done;
>  	int ret;
> 
> -	while (video->ep->enabled) {
> +	while (uvc->state == UVC_STATE_STREAMING && video->ep->enabled) {
>  		/*
>  		 * Retrieve the first available USB request, protected by the
>  		 * request lock.
> --
> 2.42.0.869.gea05f2083d-goog

^ permalink raw reply	[flat|nested] 94+ messages in thread

end of thread, other threads:[~2023-11-14 21:04 UTC | newest]

Thread overview: 94+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-09-30 18:48 [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
2023-09-30 18:48 ` [PATCH v1 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-03 23:18   ` [PATCH v2 " Avichal Rakesh
2023-09-30 18:48 ` [PATCH v1 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-03 23:19   ` [PATCH v2 " Avichal Rakesh
2023-09-30 18:48 ` [PATCH v1 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-03 23:21   ` [PATCH v2 " Avichal Rakesh
2023-10-03 11:09 ` [PATCH v1 0/3] usb: gadget: uvc: stability fixes on STREAMOFF Michael Grzeschik
2023-10-03 23:16   ` Avichal Rakesh
2023-10-05  7:11     ` Greg Kroah-Hartman
2023-10-05 18:09       ` Avichal Rakesh
2023-10-05  8:23   ` Laurent Pinchart
2023-10-05 10:14     ` Michael Grzeschik
2023-10-05 18:30       ` Avichal Rakesh
2023-10-05 22:05         ` Michael Grzeschik
2023-10-06 17:00           ` Avichal Rakesh
2023-10-06 22:53             ` Michael Grzeschik
2023-10-06 23:48               ` Avichal Rakesh
2023-10-08 19:48                 ` Michael Grzeschik
2023-10-12  0:33                   ` Avichal Rakesh
2023-10-05 18:08 ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-05 18:08   ` [PATCH v3 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-06 22:11     ` Michael Grzeschik
2023-10-05 18:08   ` [PATCH v3 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-06 22:04   ` [PATCH v3 1/3] usb: gadget: uvc: prevent use of disabled endpoint Michael Grzeschik
2023-10-12  0:24 ` [PATCH v4 " Avichal Rakesh
2023-10-12  0:24   ` [PATCH v4 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-18 13:03     ` Michael Grzeschik
2023-10-18 19:53       ` Avichal Rakesh
2023-10-12  0:24   ` [PATCH v4 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-12  0:42     ` Avichal Rakesh
2023-10-18 13:10     ` Michael Grzeschik
2023-10-18 21:50       ` Avichal Rakesh
2023-10-18 22:06         ` Michael Grzeschik
2023-10-19 18:54           ` Avichal Rakesh
2023-10-18 19:46 ` [PATCH v5 1/3] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-18 19:46   ` [PATCH v5 2/3] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-18 19:46   ` [PATCH v5 3/3] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-19 18:53 ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
2023-10-19 18:53   ` [PATCH v6 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-19 18:53   ` [PATCH v6 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-19 18:53   ` [PATCH v6 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-10-19 18:53   ` [PATCH v6 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-19 20:32     ` kernel test robot
2023-10-19 22:30       ` Avichal Rakesh
2023-10-21 10:05         ` Greg KH
2023-10-23 21:25           ` Avichal Rakesh
2023-10-24  9:27             ` Greg KH
2023-10-24 20:00               ` Avichal Rakesh
2023-10-19 18:59   ` [PATCH v6 0/4] usb: gadget: uvc: stability fixes on STREAMOFF Avichal Rakesh
2023-10-27 20:19     ` [PATCH v9 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-27 20:19       ` [PATCH v9 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-28 10:31         ` Greg KH
2023-10-28 20:13         ` Dan Scally
2023-10-30 20:26           ` Avichal Rakesh
2023-10-27 20:19       ` [PATCH v9 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-10-28 20:16         ` Dan Scally
2023-10-27 20:19       ` [PATCH v9 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-28 20:56         ` Dan Scally
2023-10-30 20:56           ` Avichal Rakesh
2023-11-02 13:29             ` Dan Scally
2023-11-02 20:39               ` Avichal Rakesh
2023-11-07 21:15                 ` Avichal Rakesh
2023-10-30 20:22     ` [PATCH v10 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-30 20:22       ` [PATCH v10 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-11-01 11:06         ` Dan Scally
2023-11-01 22:13           ` Avichal Rakesh
2023-11-02 11:38             ` Dan Scally
2023-10-30 20:22       ` [PATCH v10 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-10-30 20:22       ` [PATCH v10 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-11-02 20:19     ` [PATCH v11 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-11-02 20:19       ` [PATCH v11 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-11-02 20:19       ` [PATCH v11 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-11-02 20:19       ` [PATCH v11 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-11-08 14:15         ` Dan Scally
2023-11-09  1:00           ` Avichal Rakesh
2023-11-09  0:41     ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-11-09  0:41       ` [PATCH v12 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-11-09  0:41       ` [PATCH v12 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-11-09  0:41       ` [PATCH v12 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-11-14 21:04       ` [PATCH v12 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-20 17:36   ` [PATCH v7 " Avichal Rakesh
2023-10-20 17:36     ` [PATCH v7 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
2023-10-20 17:36     ` [PATCH v7 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-10-20 17:36     ` [PATCH v7 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-24 18:36   ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-24 18:36     ` [PATCH v8 2/4] usb: gadget: uvc: Allocate uvc_requests one at a time Avichal Rakesh
     [not found]       ` <421d1996-8544-45ac-9f31-551ef597546c@ideasonboard.com>
2023-10-27 20:31         ` Avichal Rakesh
2023-10-28  5:30           ` Greg KH
2023-10-24 18:36     ` [PATCH v8 3/4] usb: gadget: uvc: move video disable logic to its own function Avichal Rakesh
2023-10-24 18:36     ` [PATCH v8 4/4] usb: gadget: uvc: Fix use-after-free for inflight usb_requests Avichal Rakesh
2023-10-26 20:23     ` [PATCH v8 1/4] usb: gadget: uvc: prevent use of disabled endpoint Avichal Rakesh
2023-10-27 10:51       ` Greg KH
2023-10-27 10:52         ` Dan Scally

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).