All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
@ 2022-11-03  7:38 Udipto Goswami
  2022-11-03  9:30 ` John Keeping
  0 siblings, 1 reply; 8+ messages in thread
From: Udipto Goswami @ 2022-11-03  7:38 UTC (permalink / raw)
  To: Greg Kroah-Hartman, linux-usb, John Keeping
  Cc: Jack Pham, Pratham Pratap, Wesley Cheng, Udipto Goswami

While performing fast composition switch, there is a possibility that the
process of ffs_ep0_write/ffs_ep0_read get into a race condition
due to ep0req being freed up from functionfs_unbind.

Consider the scenario that the ffs_ep0_write calls the ffs_ep0_queue_wait
by taking a lock &ffs->ev.waitq.lock. However, the functionfs_unbind isn't
bounded so it can go ahead and mark the ep0req to NULL, and since there
is no NULL check in ffs_ep0_queue_wait we will end up in use-after-free.

Fix this by introducing a NULL check before any req operation.
Also to ensure the serialization, perform the ep0req ops inside
spinlock &ffs->ev.waitq.lock.

Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
---
 drivers/usb/gadget/function/f_fs.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 73dc10a77cde..39980b2bf285 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
 	struct usb_request *req = ffs->ep0req;
 	int ret;
 
+	if (!req)
+		return -EINVAL;
+	/*
+	 * Even if ep0req is freed won't be a problem since the local
+	 * copy of the request will be used here.
+	 */
+
 	req->zero     = len < le16_to_cpu(ffs->ev.setup.wLength);
 
 	spin_unlock_irq(&ffs->ev.waitq.lock);
@@ -1892,11 +1899,13 @@ static void functionfs_unbind(struct ffs_data *ffs)
 	ENTER();
 
 	if (!WARN_ON(!ffs->gadget)) {
+		spin_lock_irq(&ffs->ev.waitq.lock);
 		usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
 		ffs->ep0req = NULL;
 		ffs->gadget = NULL;
 		clear_bit(FFS_FL_BOUND, &ffs->flags);
 		ffs_data_put(ffs);
+		spin_unlock_irq(&ffs->ev.waitq.lock);
 	}
 }
 
-- 
2.17.1


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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-03  7:38 [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait Udipto Goswami
@ 2022-11-03  9:30 ` John Keeping
  2022-11-03 10:27   ` Udipto Goswami
  0 siblings, 1 reply; 8+ messages in thread
From: John Keeping @ 2022-11-03  9:30 UTC (permalink / raw)
  To: Udipto Goswami
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
> While performing fast composition switch, there is a possibility that the
> process of ffs_ep0_write/ffs_ep0_read get into a race condition
> due to ep0req being freed up from functionfs_unbind.
> 
> Consider the scenario that the ffs_ep0_write calls the ffs_ep0_queue_wait
> by taking a lock &ffs->ev.waitq.lock. However, the functionfs_unbind isn't
> bounded so it can go ahead and mark the ep0req to NULL, and since there
> is no NULL check in ffs_ep0_queue_wait we will end up in use-after-free.
> 
> Fix this by introducing a NULL check before any req operation.
> Also to ensure the serialization, perform the ep0req ops inside
> spinlock &ffs->ev.waitq.lock.
> 
> Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
> Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
> ---
>  drivers/usb/gadget/function/f_fs.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
> index 73dc10a77cde..39980b2bf285 100644
> --- a/drivers/usb/gadget/function/f_fs.c
> +++ b/drivers/usb/gadget/function/f_fs.c
> @@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
>  	struct usb_request *req = ffs->ep0req;
>  	int ret;
>  
> +	if (!req)
> +		return -EINVAL;
> +	/*
> +	 * Even if ep0req is freed won't be a problem since the local
> +	 * copy of the request will be used here.
> +	 */

This doesn't sound right - if we set ep0req to NULL then we've called
usb_ep_free_request() on it so the request is not longer valid.

>  	req->zero     = len < le16_to_cpu(ffs->ev.setup.wLength);
>  
>  	spin_unlock_irq(&ffs->ev.waitq.lock);
> @@ -1892,11 +1899,13 @@ static void functionfs_unbind(struct ffs_data *ffs)
>  	ENTER();
>  
>  	if (!WARN_ON(!ffs->gadget)) {
> +		spin_lock_irq(&ffs->ev.waitq.lock);
>  		usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
>  		ffs->ep0req = NULL;
>  		ffs->gadget = NULL;
>  		clear_bit(FFS_FL_BOUND, &ffs->flags);
>  		ffs_data_put(ffs);
> +		spin_unlock_irq(&ffs->ev.waitq.lock);

ffs may have been freed in ffs_data_put() so accessing the lock here is
unsafe.

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-03  9:30 ` John Keeping
@ 2022-11-03 10:27   ` Udipto Goswami
  2022-11-03 10:52     ` John Keeping
  0 siblings, 1 reply; 8+ messages in thread
From: Udipto Goswami @ 2022-11-03 10:27 UTC (permalink / raw)
  To: John Keeping
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

Hi John,

On 11/3/22 3:00 PM, John Keeping wrote:
> On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
>> While performing fast composition switch, there is a possibility that the
>> process of ffs_ep0_write/ffs_ep0_read get into a race condition
>> due to ep0req being freed up from functionfs_unbind.
>>
>> Consider the scenario that the ffs_ep0_write calls the ffs_ep0_queue_wait
>> by taking a lock &ffs->ev.waitq.lock. However, the functionfs_unbind isn't
>> bounded so it can go ahead and mark the ep0req to NULL, and since there
>> is no NULL check in ffs_ep0_queue_wait we will end up in use-after-free.
>>
>> Fix this by introducing a NULL check before any req operation.
>> Also to ensure the serialization, perform the ep0req ops inside
>> spinlock &ffs->ev.waitq.lock.
>>
>> Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
>> Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
>> ---
>>   drivers/usb/gadget/function/f_fs.c | 9 +++++++++
>>   1 file changed, 9 insertions(+)
>>
>> diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
>> index 73dc10a77cde..39980b2bf285 100644
>> --- a/drivers/usb/gadget/function/f_fs.c
>> +++ b/drivers/usb/gadget/function/f_fs.c
>> @@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
>>   	struct usb_request *req = ffs->ep0req;
>>   	int ret;
>>   
>> +	if (!req)
>> +		return -EINVAL;
>> +	/*
>> +	 * Even if ep0req is freed won't be a problem since the local
>> +	 * copy of the request will be used here.
>> +	 */
> 
> This doesn't sound right - if we set ep0req to NULL then we've called
> usb_ep_free_request() on it so the request is not longer valid.

Yes I agree as soon as we spin_unlock it the functionfs_unbind will 
execute and free_up the req, so performing and ep_queue after that even 
if it is a local copy could be fatal.

          ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
          if (unlikely(ret < 0))
                  return ret;

         spin_unlock_irq(&ffs->ev.waitq.lock);
  We can move the spin_unlock after to queue operation perhaps ?

> 
>>   	req->zero     = len < le16_to_cpu(ffs->ev.setup.wLength);
>>   
>>   	spin_unlock_irq(&ffs->ev.waitq.lock);
>> @@ -1892,11 +1899,13 @@ static void functionfs_unbind(struct ffs_data *ffs)
>>   	ENTER();
>>   
>>   	if (!WARN_ON(!ffs->gadget)) {
>> +		spin_lock_irq(&ffs->ev.waitq.lock);
>>   		usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
>>   		ffs->ep0req = NULL;
>>   		ffs->gadget = NULL;
>>   		clear_bit(FFS_FL_BOUND, &ffs->flags);
>>   		ffs_data_put(ffs);
>> +		spin_unlock_irq(&ffs->ev.waitq.lock);
> 
> ffs may have been freed in ffs_data_put() so accessing the lock here is
> unsafe.
maybe we can move it before data_put ?
    		clear_bit(FFS_FL_BOUND, &ffs->flags);
  +		spin_unlock_irq(&ffs->ev.waitq.lock);
    		ffs_data_put(ffs);

the intention here is to protect the ep0req only since the 
ep0_queue_wait is also doing the assignments after taking the lock.

Thanks,
-Udipto

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-03 10:27   ` Udipto Goswami
@ 2022-11-03 10:52     ` John Keeping
  2022-11-03 11:29       ` Udipto Goswami
  0 siblings, 1 reply; 8+ messages in thread
From: John Keeping @ 2022-11-03 10:52 UTC (permalink / raw)
  To: Udipto Goswami
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

On Thu, Nov 03, 2022 at 03:57:02PM +0530, Udipto Goswami wrote:
> On 11/3/22 3:00 PM, John Keeping wrote:
> > On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
> > > While performing fast composition switch, there is a possibility that the
> > > process of ffs_ep0_write/ffs_ep0_read get into a race condition
> > > due to ep0req being freed up from functionfs_unbind.
> > > 
> > > Consider the scenario that the ffs_ep0_write calls the ffs_ep0_queue_wait
> > > by taking a lock &ffs->ev.waitq.lock. However, the functionfs_unbind isn't
> > > bounded so it can go ahead and mark the ep0req to NULL, and since there
> > > is no NULL check in ffs_ep0_queue_wait we will end up in use-after-free.
> > > 
> > > Fix this by introducing a NULL check before any req operation.
> > > Also to ensure the serialization, perform the ep0req ops inside
> > > spinlock &ffs->ev.waitq.lock.
> > > 
> > > Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
> > > Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
> > > ---
> > >   drivers/usb/gadget/function/f_fs.c | 9 +++++++++
> > >   1 file changed, 9 insertions(+)
> > > 
> > > diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
> > > index 73dc10a77cde..39980b2bf285 100644
> > > --- a/drivers/usb/gadget/function/f_fs.c
> > > +++ b/drivers/usb/gadget/function/f_fs.c
> > > @@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
> > >   	struct usb_request *req = ffs->ep0req;
> > >   	int ret;
> > > +	if (!req)
> > > +		return -EINVAL;
> > > +	/*
> > > +	 * Even if ep0req is freed won't be a problem since the local
> > > +	 * copy of the request will be used here.
> > > +	 */
> > 
> > This doesn't sound right - if we set ep0req to NULL then we've called
> > usb_ep_free_request() on it so the request is not longer valid.
> 
> Yes I agree as soon as we spin_unlock it the functionfs_unbind will execute
> and free_up the req, so performing and ep_queue after that even if it is a
> local copy could be fatal.
> 
>          ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
>          if (unlikely(ret < 0))
>                  return ret;
> 
>         spin_unlock_irq(&ffs->ev.waitq.lock);
>  We can move the spin_unlock after to queue operation perhaps ?

I don't think it's that simple.  The documentation for
usb_ep_free_request() says:

	* Caller guarantees the request is not queued, and that it will
	* no longer be requeued (or otherwise used).

so some extra synchronisation is required here.

By the time we get to functionfs_unbind() everything should be disabled
by ffs_func_disable() and ffs_func_unbind() has drained the workqueue,
but none of that applies to ep0.

I think ffs_unbind() needs to dequeue the ep0 request.

In addition to that, I think we need a new ep0 status variable in struct
ffs_data so that req is not accessed after wait_for_completion() in
__ffs_ep0_queue_wait() and that status is set in ffs_ep0_complete().

With the spin_unlock_irq() moved to immediately before
wait_for_completion() in __ffs_ep0_queue_wait() it looks like everything
is then safe.

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-03 10:52     ` John Keeping
@ 2022-11-03 11:29       ` Udipto Goswami
  2022-11-04 10:14         ` Udipto Goswami
  0 siblings, 1 reply; 8+ messages in thread
From: Udipto Goswami @ 2022-11-03 11:29 UTC (permalink / raw)
  To: John Keeping
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

Hi John

On 11/3/22 4:22 PM, John Keeping wrote:
> On Thu, Nov 03, 2022 at 03:57:02PM +0530, Udipto Goswami wrote:
>> On 11/3/22 3:00 PM, John Keeping wrote:
>>> On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
>>>> While performing fast composition switch, there is a possibility that the
>>>> process of ffs_ep0_write/ffs_ep0_read get into a race condition
>>>> due to ep0req being freed up from functionfs_unbind.
>>>>
>>>> Consider the scenario that the ffs_ep0_write calls the ffs_ep0_queue_wait
>>>> by taking a lock &ffs->ev.waitq.lock. However, the functionfs_unbind isn't
>>>> bounded so it can go ahead and mark the ep0req to NULL, and since there
>>>> is no NULL check in ffs_ep0_queue_wait we will end up in use-after-free.
>>>>
>>>> Fix this by introducing a NULL check before any req operation.
>>>> Also to ensure the serialization, perform the ep0req ops inside
>>>> spinlock &ffs->ev.waitq.lock.
>>>>
>>>> Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
>>>> Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
>>>> ---
>>>>    drivers/usb/gadget/function/f_fs.c | 9 +++++++++
>>>>    1 file changed, 9 insertions(+)
>>>>
>>>> diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
>>>> index 73dc10a77cde..39980b2bf285 100644
>>>> --- a/drivers/usb/gadget/function/f_fs.c
>>>> +++ b/drivers/usb/gadget/function/f_fs.c
>>>> @@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
>>>>    	struct usb_request *req = ffs->ep0req;
>>>>    	int ret;
>>>> +	if (!req)
>>>> +		return -EINVAL;
>>>> +	/*
>>>> +	 * Even if ep0req is freed won't be a problem since the local
>>>> +	 * copy of the request will be used here.
>>>> +	 */
>>>
>>> This doesn't sound right - if we set ep0req to NULL then we've called
>>> usb_ep_free_request() on it so the request is not longer valid.
>>
>> Yes I agree as soon as we spin_unlock it the functionfs_unbind will execute
>> and free_up the req, so performing and ep_queue after that even if it is a
>> local copy could be fatal.
>>
>>           ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
>>           if (unlikely(ret < 0))
>>                   return ret;
>>
>>          spin_unlock_irq(&ffs->ev.waitq.lock);
>>   We can move the spin_unlock after to queue operation perhaps ?
> 
> I don't think it's that simple.  The documentation for
> usb_ep_free_request() says:
> 
> 	* Caller guarantees the request is not queued, and that it will
> 	* no longer be requeued (or otherwise used).
> 
> so some extra synchronisation is required here.
> 
> By the time we get to functionfs_unbind() everything should be disabled
> by ffs_func_disable() and ffs_func_unbind() has drained the workqueue,
> but none of that applies to ep0.
> 
> I think ffs_unbind() needs to dequeue the ep0 request.
> 
> In addition to that, I think we need a new ep0 status variable in struct
> ffs_data so that req is not accessed after wait_for_completion() in
> __ffs_ep0_queue_wait() and that status is set in ffs_ep0_complete().
> 
> With the spin_unlock_irq() moved to immediately before
> wait_for_completion() in __ffs_ep0_queue_wait() it looks like everything
> is then safe.

Thanks for the suggestions, let me try implementing it.

-Udipto

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-03 11:29       ` Udipto Goswami
@ 2022-11-04 10:14         ` Udipto Goswami
  2022-11-04 11:49           ` John Keeping
  0 siblings, 1 reply; 8+ messages in thread
From: Udipto Goswami @ 2022-11-04 10:14 UTC (permalink / raw)
  To: John Keeping
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

Hi John,

On 11/3/22 4:59 PM, Udipto Goswami wrote:
> Hi John
> 
> On 11/3/22 4:22 PM, John Keeping wrote:
>> On Thu, Nov 03, 2022 at 03:57:02PM +0530, Udipto Goswami wrote:
>>> On 11/3/22 3:00 PM, John Keeping wrote:
>>>> On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
>>>>> While performing fast composition switch, there is a possibility 
>>>>> that the
>>>>> process of ffs_ep0_write/ffs_ep0_read get into a race condition
>>>>> due to ep0req being freed up from functionfs_unbind.
>>>>>
>>>>> Consider the scenario that the ffs_ep0_write calls the 
>>>>> ffs_ep0_queue_wait
>>>>> by taking a lock &ffs->ev.waitq.lock. However, the 
>>>>> functionfs_unbind isn't
>>>>> bounded so it can go ahead and mark the ep0req to NULL, and since 
>>>>> there
>>>>> is no NULL check in ffs_ep0_queue_wait we will end up in 
>>>>> use-after-free.
>>>>>
>>>>> Fix this by introducing a NULL check before any req operation.
>>>>> Also to ensure the serialization, perform the ep0req ops inside
>>>>> spinlock &ffs->ev.waitq.lock.
>>>>>
>>>>> Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
>>>>> Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
>>>>> ---
>>>>>    drivers/usb/gadget/function/f_fs.c | 9 +++++++++
>>>>>    1 file changed, 9 insertions(+)
>>>>>
>>>>> diff --git a/drivers/usb/gadget/function/f_fs.c 
>>>>> b/drivers/usb/gadget/function/f_fs.c
>>>>> index 73dc10a77cde..39980b2bf285 100644
>>>>> --- a/drivers/usb/gadget/function/f_fs.c
>>>>> +++ b/drivers/usb/gadget/function/f_fs.c
>>>>> @@ -279,6 +279,13 @@ static int __ffs_ep0_queue_wait(struct 
>>>>> ffs_data *ffs, char *data, size_t len)
>>>>>        struct usb_request *req = ffs->ep0req;
>>>>>        int ret;
>>>>> +    if (!req)
>>>>> +        return -EINVAL;
>>>>> +    /*
>>>>> +     * Even if ep0req is freed won't be a problem since the local
>>>>> +     * copy of the request will be used here.
>>>>> +     */
>>>>
>>>> This doesn't sound right - if we set ep0req to NULL then we've called
>>>> usb_ep_free_request() on it so the request is not longer valid.
>>>
>>> Yes I agree as soon as we spin_unlock it the functionfs_unbind will 
>>> execute
>>> and free_up the req, so performing and ep_queue after that even if it 
>>> is a
>>> local copy could be fatal.
>>>
>>>           ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
>>>           if (unlikely(ret < 0))
>>>                   return ret;
>>>
>>>          spin_unlock_irq(&ffs->ev.waitq.lock);
>>>   We can move the spin_unlock after to queue operation perhaps ?
>>
>> I don't think it's that simple.  The documentation for
>> usb_ep_free_request() says:
>>
>>     * Caller guarantees the request is not queued, and that it will
>>     * no longer be requeued (or otherwise used).
>>
>> so some extra synchronisation is required here.
>>
>> By the time we get to functionfs_unbind() everything should be disabled
>> by ffs_func_disable() and ffs_func_unbind() has drained the workqueue,
>> but none of that applies to ep0.
>>
>> I think ffs_unbind() needs to dequeue the ep0 request.
>>
>> In addition to that, I think we need a new ep0 status variable in struct
>> ffs_data so that req is not accessed after wait_for_completion() in
>> __ffs_ep0_queue_wait() and that status is set in ffs_ep0_complete().
>>
>> With the spin_unlock_irq() moved to immediately before
>> wait_for_completion() in __ffs_ep0_queue_wait() it looks like everything
>> is then safe.
> 
> Thanks for the suggestions, let me try implementing it.
> 
Just curious because i saw __ffs_ep0_queue_wait will only be called from 
  ffs_ep0_read & ffs_ep0_write, both using a mutex_lock(&ffs->mutex)

So if we protect the functionfs_unbind with this mutex, it will be 
better right?

@@ -1889,12 +1889,13 @@ static int functionfs_bind(struct ffs_data *ffs, 
struct usb_composite_dev *cdev)
  static void functionfs_unbind(struct ffs_data *ffs)
  {
         ENTER();

         if (!WARN_ON(!ffs->gadget)) {
+               ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
                 usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
                 ffs->ep0req = NULL;
                 ffs->gadget = NULL;
                 clear_bit(FFS_FL_BOUND, &ffs->flags);
+               mutex_unlock(&ffs->mutex);
                 ffs_data_put(ffs);
         }
  }

Perhaps we don't have to take care of the the serialization in that case 
since it will exit the function __ffs_ep0_queue_wait only after 
everything is done and hence functionfs_unbind will get the control 
after the ep0_write/read has completed?

What do you think ?

-Udipto

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-04 10:14         ` Udipto Goswami
@ 2022-11-04 11:49           ` John Keeping
  2022-11-07  4:10             ` Udipto Goswami
  0 siblings, 1 reply; 8+ messages in thread
From: John Keeping @ 2022-11-04 11:49 UTC (permalink / raw)
  To: Udipto Goswami
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

On Fri, Nov 04, 2022 at 03:44:09PM +0530, Udipto Goswami wrote:
> On 11/3/22 4:59 PM, Udipto Goswami wrote:
> > On 11/3/22 4:22 PM, John Keeping wrote:
> > > On Thu, Nov 03, 2022 at 03:57:02PM +0530, Udipto Goswami wrote:
> > > > On 11/3/22 3:00 PM, John Keeping wrote:
> > > > > On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
> > > > > > While performing fast composition switch, there is a
> > > > > > possibility that the
> > > > > > process of ffs_ep0_write/ffs_ep0_read get into a race condition
> > > > > > due to ep0req being freed up from functionfs_unbind.
> > > > > > 
> > > > > > Consider the scenario that the ffs_ep0_write calls the
> > > > > > ffs_ep0_queue_wait
> > > > > > by taking a lock &ffs->ev.waitq.lock. However, the
> > > > > > functionfs_unbind isn't
> > > > > > bounded so it can go ahead and mark the ep0req to NULL,
> > > > > > and since there
> > > > > > is no NULL check in ffs_ep0_queue_wait we will end up in
> > > > > > use-after-free.
> > > > > > 
> > > > > > Fix this by introducing a NULL check before any req operation.
> > > > > > Also to ensure the serialization, perform the ep0req ops inside
> > > > > > spinlock &ffs->ev.waitq.lock.
> > > > > > 
> > > > > > Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
> > > > > > Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
> > > > > > ---
> > > > > >    drivers/usb/gadget/function/f_fs.c | 9 +++++++++
> > > > > >    1 file changed, 9 insertions(+)
> > > > > > 
> > > > > > diff --git a/drivers/usb/gadget/function/f_fs.c
> > > > > > b/drivers/usb/gadget/function/f_fs.c
> > > > > > index 73dc10a77cde..39980b2bf285 100644
> > > > > > --- a/drivers/usb/gadget/function/f_fs.c
> > > > > > +++ b/drivers/usb/gadget/function/f_fs.c
> > > > > > @@ -279,6 +279,13 @@ static int
> > > > > > __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data,
> > > > > > size_t len)
> > > > > >        struct usb_request *req = ffs->ep0req;
> > > > > >        int ret;
> > > > > > +    if (!req)
> > > > > > +        return -EINVAL;
> > > > > > +    /*
> > > > > > +     * Even if ep0req is freed won't be a problem since the local
> > > > > > +     * copy of the request will be used here.
> > > > > > +     */
> > > > > 
> > > > > This doesn't sound right - if we set ep0req to NULL then we've called
> > > > > usb_ep_free_request() on it so the request is not longer valid.
> > > > 
> > > > Yes I agree as soon as we spin_unlock it the functionfs_unbind
> > > > will execute
> > > > and free_up the req, so performing and ep_queue after that even
> > > > if it is a
> > > > local copy could be fatal.
> > > > 
> > > >           ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
> > > >           if (unlikely(ret < 0))
> > > >                   return ret;
> > > > 
> > > >          spin_unlock_irq(&ffs->ev.waitq.lock);
> > > >   We can move the spin_unlock after to queue operation perhaps ?
> > > 
> > > I don't think it's that simple.  The documentation for
> > > usb_ep_free_request() says:
> > > 
> > >     * Caller guarantees the request is not queued, and that it will
> > >     * no longer be requeued (or otherwise used).
> > > 
> > > so some extra synchronisation is required here.
> > > 
> > > By the time we get to functionfs_unbind() everything should be disabled
> > > by ffs_func_disable() and ffs_func_unbind() has drained the workqueue,
> > > but none of that applies to ep0.
> > > 
> > > I think ffs_unbind() needs to dequeue the ep0 request.
> > > 
> > > In addition to that, I think we need a new ep0 status variable in struct
> > > ffs_data so that req is not accessed after wait_for_completion() in
> > > __ffs_ep0_queue_wait() and that status is set in ffs_ep0_complete().
> > > 
> > > With the spin_unlock_irq() moved to immediately before
> > > wait_for_completion() in __ffs_ep0_queue_wait() it looks like everything
> > > is then safe.
> > 
> > Thanks for the suggestions, let me try implementing it.
> > 
> Just curious because i saw __ffs_ep0_queue_wait will only be called from
> ffs_ep0_read & ffs_ep0_write, both using a mutex_lock(&ffs->mutex)
> 
> So if we protect the functionfs_unbind with this mutex, it will be better
> right?
> 
> @@ -1889,12 +1889,13 @@ static int functionfs_bind(struct ffs_data *ffs,
> struct usb_composite_dev *cdev)
>  static void functionfs_unbind(struct ffs_data *ffs)
>  {
>         ENTER();
> 
>         if (!WARN_ON(!ffs->gadget)) {
> +               ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
>                 usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
>                 ffs->ep0req = NULL;
>                 ffs->gadget = NULL;
>                 clear_bit(FFS_FL_BOUND, &ffs->flags);
> +               mutex_unlock(&ffs->mutex);
>                 ffs_data_put(ffs);
>         }
>  }
> 
> Perhaps we don't have to take care of the the serialization in that case
> since it will exit the function __ffs_ep0_queue_wait only after everything
> is done and hence functionfs_unbind will get the control after the
> ep0_write/read has completed?
> 
> What do you think ?

The documentation does say it protects ep0req so this might make sense.

But I think you need to ensure ep0req is dequeued before locking the
mutex in order to avoid a deadlock as nothing else is going to complete
an outstanding request at this point.

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

* Re: [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait
  2022-11-04 11:49           ` John Keeping
@ 2022-11-07  4:10             ` Udipto Goswami
  0 siblings, 0 replies; 8+ messages in thread
From: Udipto Goswami @ 2022-11-07  4:10 UTC (permalink / raw)
  To: John Keeping
  Cc: Greg Kroah-Hartman, linux-usb, Jack Pham, Pratham Pratap, Wesley Cheng

Hi John,

On 11/4/22 5:19 PM, John Keeping wrote:
> On Fri, Nov 04, 2022 at 03:44:09PM +0530, Udipto Goswami wrote:
>> On 11/3/22 4:59 PM, Udipto Goswami wrote:
>>> On 11/3/22 4:22 PM, John Keeping wrote:
>>>> On Thu, Nov 03, 2022 at 03:57:02PM +0530, Udipto Goswami wrote:
>>>>> On 11/3/22 3:00 PM, John Keeping wrote:
>>>>>> On Thu, Nov 03, 2022 at 01:08:21PM +0530, Udipto Goswami wrote:
>>>>>>> While performing fast composition switch, there is a
>>>>>>> possibility that the
>>>>>>> process of ffs_ep0_write/ffs_ep0_read get into a race condition
>>>>>>> due to ep0req being freed up from functionfs_unbind.
>>>>>>>
>>>>>>> Consider the scenario that the ffs_ep0_write calls the
>>>>>>> ffs_ep0_queue_wait
>>>>>>> by taking a lock &ffs->ev.waitq.lock. However, the
>>>>>>> functionfs_unbind isn't
>>>>>>> bounded so it can go ahead and mark the ep0req to NULL,
>>>>>>> and since there
>>>>>>> is no NULL check in ffs_ep0_queue_wait we will end up in
>>>>>>> use-after-free.
>>>>>>>
>>>>>>> Fix this by introducing a NULL check before any req operation.
>>>>>>> Also to ensure the serialization, perform the ep0req ops inside
>>>>>>> spinlock &ffs->ev.waitq.lock.
>>>>>>>
>>>>>>> Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver")
>>>>>>> Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com>
>>>>>>> ---
>>>>>>>     drivers/usb/gadget/function/f_fs.c | 9 +++++++++
>>>>>>>     1 file changed, 9 insertions(+)
>>>>>>>
>>>>>>> diff --git a/drivers/usb/gadget/function/f_fs.c
>>>>>>> b/drivers/usb/gadget/function/f_fs.c
>>>>>>> index 73dc10a77cde..39980b2bf285 100644
>>>>>>> --- a/drivers/usb/gadget/function/f_fs.c
>>>>>>> +++ b/drivers/usb/gadget/function/f_fs.c
>>>>>>> @@ -279,6 +279,13 @@ static int
>>>>>>> __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data,
>>>>>>> size_t len)
>>>>>>>         struct usb_request *req = ffs->ep0req;
>>>>>>>         int ret;
>>>>>>> +    if (!req)
>>>>>>> +        return -EINVAL;
>>>>>>> +    /*
>>>>>>> +     * Even if ep0req is freed won't be a problem since the local
>>>>>>> +     * copy of the request will be used here.
>>>>>>> +     */
>>>>>>
>>>>>> This doesn't sound right - if we set ep0req to NULL then we've called
>>>>>> usb_ep_free_request() on it so the request is not longer valid.
>>>>>
>>>>> Yes I agree as soon as we spin_unlock it the functionfs_unbind
>>>>> will execute
>>>>> and free_up the req, so performing and ep_queue after that even
>>>>> if it is a
>>>>> local copy could be fatal.
>>>>>
>>>>>            ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
>>>>>            if (unlikely(ret < 0))
>>>>>                    return ret;
>>>>>
>>>>>           spin_unlock_irq(&ffs->ev.waitq.lock);
>>>>>    We can move the spin_unlock after to queue operation perhaps ?
>>>>
>>>> I don't think it's that simple.  The documentation for
>>>> usb_ep_free_request() says:
>>>>
>>>>      * Caller guarantees the request is not queued, and that it will
>>>>      * no longer be requeued (or otherwise used).
>>>>
>>>> so some extra synchronisation is required here.
>>>>
>>>> By the time we get to functionfs_unbind() everything should be disabled
>>>> by ffs_func_disable() and ffs_func_unbind() has drained the workqueue,
>>>> but none of that applies to ep0.
>>>>
>>>> I think ffs_unbind() needs to dequeue the ep0 request.
>>>>
>>>> In addition to that, I think we need a new ep0 status variable in struct
>>>> ffs_data so that req is not accessed after wait_for_completion() in
>>>> __ffs_ep0_queue_wait() and that status is set in ffs_ep0_complete().
>>>>
>>>> With the spin_unlock_irq() moved to immediately before
>>>> wait_for_completion() in __ffs_ep0_queue_wait() it looks like everything
>>>> is then safe.
>>>
>>> Thanks for the suggestions, let me try implementing it.
>>>
>> Just curious because i saw __ffs_ep0_queue_wait will only be called from
>> ffs_ep0_read & ffs_ep0_write, both using a mutex_lock(&ffs->mutex)
>>
>> So if we protect the functionfs_unbind with this mutex, it will be better
>> right?
>>
>> @@ -1889,12 +1889,13 @@ static int functionfs_bind(struct ffs_data *ffs,
>> struct usb_composite_dev *cdev)
>>   static void functionfs_unbind(struct ffs_data *ffs)
>>   {
>>          ENTER();
>>
>>          if (!WARN_ON(!ffs->gadget)) {
>> +               ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
>>                  usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
>>                  ffs->ep0req = NULL;
>>                  ffs->gadget = NULL;
>>                  clear_bit(FFS_FL_BOUND, &ffs->flags);
>> +               mutex_unlock(&ffs->mutex);
>>                  ffs_data_put(ffs);
>>          }
>>   }
>>
>> Perhaps we don't have to take care of the the serialization in that case
>> since it will exit the function __ffs_ep0_queue_wait only after everything
>> is done and hence functionfs_unbind will get the control after the
>> ep0_write/read has completed?
>>
>> What do you think ?
> 
> The documentation does say it protects ep0req so this might make sense.
> 
> But I think you need to ensure ep0req is dequeued before locking the
> mutex in order to avoid a deadlock as nothing else is going to complete
> an outstanding request at this point.
Got it, thanks for clarification, i'll make sure to dequeue it  to avoid 
any deadlocks in v2.

-Udipto

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

end of thread, other threads:[~2022-11-07  4:11 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-03  7:38 [PATCH] usb: gadget: f_fs: Prevent race between functionfs_unbind & ffs_ep0_queue_wait Udipto Goswami
2022-11-03  9:30 ` John Keeping
2022-11-03 10:27   ` Udipto Goswami
2022-11-03 10:52     ` John Keeping
2022-11-03 11:29       ` Udipto Goswami
2022-11-04 10:14         ` Udipto Goswami
2022-11-04 11:49           ` John Keeping
2022-11-07  4:10             ` Udipto Goswami

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.