diff mbox series

usb: gadget: u_serial: Add null pointer check in gserial_resume

Message ID 1675864487-18620-1-git-send-email-quic_prashk@quicinc.com (mailing list archive)
State Superseded
Headers show
Series usb: gadget: u_serial: Add null pointer check in gserial_resume | expand

Commit Message

Prashanth K Feb. 8, 2023, 1:54 p.m. UTC
Consider a case where gserial_disconnect has already cleared
gser->ioport. And if a wakeup interrupt triggers afterwards,
gserial_resume gets called, which will lead to accessing of
gserial->port and thus causing null pointer dereference.Add
a null pointer check to prevent this.

Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
---
 drivers/usb/gadget/function/u_serial.c | 3 +++
 1 file changed, 3 insertions(+)

Comments

Greg KH Feb. 8, 2023, 2:54 p.m. UTC | #1
On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
> Consider a case where gserial_disconnect has already cleared
> gser->ioport. And if a wakeup interrupt triggers afterwards,
> gserial_resume gets called, which will lead to accessing of
> gserial->port and thus causing null pointer dereference.Add
> a null pointer check to prevent this.
> 
> Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")

Nit, and our tools will complain, no " " before the "usb:" string here,
right?



> Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
> ---
>  drivers/usb/gadget/function/u_serial.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
> index 840626e..98be2b8 100644
> --- a/drivers/usb/gadget/function/u_serial.c
> +++ b/drivers/usb/gadget/function/u_serial.c
> @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
>  	struct gs_port *port = gser->ioport;
>  	unsigned long	flags;
>  
> +	if (!port)
> +		return;
> +

What prevents port from going to NULL right after this check?

thanks,

greg k-h
Prashanth K Feb. 8, 2023, 3:45 p.m. UTC | #2
On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
> On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
>> Consider a case where gserial_disconnect has already cleared
>> gser->ioport. And if a wakeup interrupt triggers afterwards,
>> gserial_resume gets called, which will lead to accessing of
>> gserial->port and thus causing null pointer dereference.Add
>> a null pointer check to prevent this.
>>
>> Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
> 
> Nit, and our tools will complain, no " " before the "usb:" string here,
> right?
> 
Will fix it in next patch.
> 
> 
>> Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
>> ---
>>   drivers/usb/gadget/function/u_serial.c | 3 +++
>>   1 file changed, 3 insertions(+)
>>
>> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
>> index 840626e..98be2b8 100644
>> --- a/drivers/usb/gadget/function/u_serial.c
>> +++ b/drivers/usb/gadget/function/u_serial.c
>> @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
>>   	struct gs_port *port = gser->ioport;
>>   	unsigned long	flags;
>>   
>> +	if (!port)
>> +		return;
>> +
> 
> What prevents port from going to NULL right after this check?
In our case we got a null pointer de-reference while performing USB 
compliance tests, as the gser->port was null. Because in gserial_resume, 
spinlock_irq_save(&port->port_lock) accesses a null-pointer as port was 
already marked null by gserial_disconnect.

And after gserial_resume acquires the spinlock, gserial_disconnect cant 
mark it null until the spinlock is released. We need to check if the 
port->lock is valid before accessing it, otherwise it can lead to the 
above mentioned scenario

Issue Type: kernel panic issue
Issue AutoSignature:
pc : do_raw_spin_lock
lr : _raw_spin_lock_irqsave
Call trace:
do_raw_spin_lock
_raw_spin_lock_irqsave
gserial_resume
acm_resume
composite_resume
configfs_composite_resume
dwc3_process_event_entry
dwc3_process_event_buf
dwc3_thread_interrupt
irq_thread_fn
irq_thread
kthread
ret_from_fork

Thanks in advance,
Prashanth
> 
> thanks,
> 
> greg k-h
Alan Stern Feb. 8, 2023, 8:21 p.m. UTC | #3
On Wed, Feb 08, 2023 at 09:15:54PM +0530, Prashanth K wrote:
> 
> 
> On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
> > On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
> > > Consider a case where gserial_disconnect has already cleared
> > > gser->ioport. And if a wakeup interrupt triggers afterwards,
> > > gserial_resume gets called, which will lead to accessing of
> > > gserial->port and thus causing null pointer dereference.Add
> > > a null pointer check to prevent this.
> > > 
> > > Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
> > 
> > Nit, and our tools will complain, no " " before the "usb:" string here,
> > right?
> > 
> Will fix it in next patch.
> > 
> > 
> > > Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
> > > ---
> > >   drivers/usb/gadget/function/u_serial.c | 3 +++
> > >   1 file changed, 3 insertions(+)
> > > 
> > > diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
> > > index 840626e..98be2b8 100644
> > > --- a/drivers/usb/gadget/function/u_serial.c
> > > +++ b/drivers/usb/gadget/function/u_serial.c
> > > @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
> > >   	struct gs_port *port = gser->ioport;
> > >   	unsigned long	flags;
> > > +	if (!port)
> > > +		return;
> > > +
> > 
> > What prevents port from going to NULL right after this check?
> In our case we got a null pointer de-reference while performing USB
> compliance tests, as the gser->port was null. Because in gserial_resume,
> spinlock_irq_save(&port->port_lock) accesses a null-pointer as port was
> already marked null by gserial_disconnect.
> 
> And after gserial_resume acquires the spinlock, gserial_disconnect cant mark
> it null until the spinlock is released. We need to check if the port->lock
> is valid before accessing it, otherwise it can lead to the above mentioned
> scenario

What happens if gserial_disconnect sets gser->port to NULL immediately 
after your new check occurs, but before 
spinlock_irq_save(&port->port_lock) gets called?

You may need to add a static spinlock to prevent this from happening.

Alan Stern
Prashanth K Feb. 9, 2023, 5:01 a.m. UTC | #4
On 09-02-23 01:51 am, Alan Stern wrote:
> On Wed, Feb 08, 2023 at 09:15:54PM +0530, Prashanth K wrote:
>>
>>
>> On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
>>> On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
>>>> Consider a case where gserial_disconnect has already cleared
>>>> gser->ioport. And if a wakeup interrupt triggers afterwards,
>>>> gserial_resume gets called, which will lead to accessing of
>>>> gserial->port and thus causing null pointer dereference.Add
>>>> a null pointer check to prevent this.
>>>>
>>>> Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
>>>
>>> Nit, and our tools will complain, no " " before the "usb:" string here,
>>> right?
>>>
>> Will fix it in next patch.
>>>
>>>
>>>> Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
>>>> ---
>>>>    drivers/usb/gadget/function/u_serial.c | 3 +++
>>>>    1 file changed, 3 insertions(+)
>>>>
>>>> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
>>>> index 840626e..98be2b8 100644
>>>> --- a/drivers/usb/gadget/function/u_serial.c
>>>> +++ b/drivers/usb/gadget/function/u_serial.c
>>>> @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
>>>>    	struct gs_port *port = gser->ioport;
>>>>    	unsigned long	flags;
>>>> +	if (!port)
>>>> +		return;
>>>> +
>>>
>>> What prevents port from going to NULL right after this check?
>> In our case we got a null pointer de-reference while performing USB
>> compliance tests, as the gser->port was null. Because in gserial_resume,
>> spinlock_irq_save(&port->port_lock) accesses a null-pointer as port was
>> already marked null by gserial_disconnect.
>>
>> And after gserial_resume acquires the spinlock, gserial_disconnect cant mark
>> it null until the spinlock is released. We need to check if the port->lock
>> is valid before accessing it, otherwise it can lead to the above mentioned
>> scenario
> 
> What happens if gserial_disconnect sets gser->port to NULL immediately
> after your new check occurs, but before
> spinlock_irq_save(&port->port_lock) gets called?
> 
> You may need to add a static spinlock to prevent this from happening.
> 
> Alan Stern
In that case i guess we have to make port_lock a global variable and 
take it out of gs_port structure.

+ static DEFINE_SPINLOCK(port_lock);

struct gs_port {
	struct tty_port port;
-	spinlock_t port_lock;

This will require us to change all the spinlock(port->port_lock) used in 
u_serial.c, what do you suggest?
Greg KH Feb. 9, 2023, 7:01 a.m. UTC | #5
On Thu, Feb 09, 2023 at 10:31:50AM +0530, Prashanth K wrote:
> 
> 
> On 09-02-23 01:51 am, Alan Stern wrote:
> > On Wed, Feb 08, 2023 at 09:15:54PM +0530, Prashanth K wrote:
> > > 
> > > 
> > > On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
> > > > On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
> > > > > Consider a case where gserial_disconnect has already cleared
> > > > > gser->ioport. And if a wakeup interrupt triggers afterwards,
> > > > > gserial_resume gets called, which will lead to accessing of
> > > > > gserial->port and thus causing null pointer dereference.Add
> > > > > a null pointer check to prevent this.
> > > > > 
> > > > > Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
> > > > 
> > > > Nit, and our tools will complain, no " " before the "usb:" string here,
> > > > right?
> > > > 
> > > Will fix it in next patch.
> > > > 
> > > > 
> > > > > Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
> > > > > ---
> > > > >    drivers/usb/gadget/function/u_serial.c | 3 +++
> > > > >    1 file changed, 3 insertions(+)
> > > > > 
> > > > > diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
> > > > > index 840626e..98be2b8 100644
> > > > > --- a/drivers/usb/gadget/function/u_serial.c
> > > > > +++ b/drivers/usb/gadget/function/u_serial.c
> > > > > @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
> > > > >    	struct gs_port *port = gser->ioport;
> > > > >    	unsigned long	flags;
> > > > > +	if (!port)
> > > > > +		return;
> > > > > +
> > > > 
> > > > What prevents port from going to NULL right after this check?
> > > In our case we got a null pointer de-reference while performing USB
> > > compliance tests, as the gser->port was null. Because in gserial_resume,
> > > spinlock_irq_save(&port->port_lock) accesses a null-pointer as port was
> > > already marked null by gserial_disconnect.
> > > 
> > > And after gserial_resume acquires the spinlock, gserial_disconnect cant mark
> > > it null until the spinlock is released. We need to check if the port->lock
> > > is valid before accessing it, otherwise it can lead to the above mentioned
> > > scenario
> > 
> > What happens if gserial_disconnect sets gser->port to NULL immediately
> > after your new check occurs, but before
> > spinlock_irq_save(&port->port_lock) gets called?
> > 
> > You may need to add a static spinlock to prevent this from happening.
> > 
> > Alan Stern
> In that case i guess we have to make port_lock a global variable and take it
> out of gs_port structure.
> 
> + static DEFINE_SPINLOCK(port_lock);
> 
> struct gs_port {
> 	struct tty_port port;
> -	spinlock_t port_lock;
> 
> This will require us to change all the spinlock(port->port_lock) used in
> u_serial.c, what do you suggest?

Yes, that would be the correct thing to do.
Prashanth K Feb. 9, 2023, 7:03 a.m. UTC | #6
On 09-02-23 12:31 pm, Greg Kroah-Hartman wrote:
> On Thu, Feb 09, 2023 at 10:31:50AM +0530, Prashanth K wrote:
>>
>>
>> On 09-02-23 01:51 am, Alan Stern wrote:
>>> On Wed, Feb 08, 2023 at 09:15:54PM +0530, Prashanth K wrote:
>>>>
>>>>
>>>> On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
>>>>> On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
>>>>>> Consider a case where gserial_disconnect has already cleared
>>>>>> gser->ioport. And if a wakeup interrupt triggers afterwards,
>>>>>> gserial_resume gets called, which will lead to accessing of
>>>>>> gserial->port and thus causing null pointer dereference.Add
>>>>>> a null pointer check to prevent this.
>>>>>>
>>>>>> Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume callbacks")
>>>>>
>>>>> Nit, and our tools will complain, no " " before the "usb:" string here,
>>>>> right?
>>>>>
>>>> Will fix it in next patch.
>>>>>
>>>>>
>>>>>> Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
>>>>>> ---
>>>>>>     drivers/usb/gadget/function/u_serial.c | 3 +++
>>>>>>     1 file changed, 3 insertions(+)
>>>>>>
>>>>>> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
>>>>>> index 840626e..98be2b8 100644
>>>>>> --- a/drivers/usb/gadget/function/u_serial.c
>>>>>> +++ b/drivers/usb/gadget/function/u_serial.c
>>>>>> @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
>>>>>>     	struct gs_port *port = gser->ioport;
>>>>>>     	unsigned long	flags;
>>>>>> +	if (!port)
>>>>>> +		return;
>>>>>> +
>>>>>
>>>>> What prevents port from going to NULL right after this check?
>>>> In our case we got a null pointer de-reference while performing USB
>>>> compliance tests, as the gser->port was null. Because in gserial_resume,
>>>> spinlock_irq_save(&port->port_lock) accesses a null-pointer as port was
>>>> already marked null by gserial_disconnect.
>>>>
>>>> And after gserial_resume acquires the spinlock, gserial_disconnect cant mark
>>>> it null until the spinlock is released. We need to check if the port->lock
>>>> is valid before accessing it, otherwise it can lead to the above mentioned
>>>> scenario
>>>
>>> What happens if gserial_disconnect sets gser->port to NULL immediately
>>> after your new check occurs, but before
>>> spinlock_irq_save(&port->port_lock) gets called?
>>>
>>> You may need to add a static spinlock to prevent this from happening.
>>>
>>> Alan Stern
>> In that case i guess we have to make port_lock a global variable and take it
>> out of gs_port structure.
>>
>> + static DEFINE_SPINLOCK(port_lock);
>>
>> struct gs_port {
>> 	struct tty_port port;
>> -	spinlock_t port_lock;
>>
>> This will require us to change all the spinlock(port->port_lock) used in
>> u_serial.c, what do you suggest?
> 
> Yes, that would be the correct thing to do.
will do it and share next patch

Thanks for the suggestions
Prashanth K
Prashanth K Feb. 9, 2023, 2:07 p.m. UTC | #7
On 09-02-23 12:33 pm, Prashanth K wrote:
> 
> 
> On 09-02-23 12:31 pm, Greg Kroah-Hartman wrote:
>> On Thu, Feb 09, 2023 at 10:31:50AM +0530, Prashanth K wrote:
>>>
>>>
>>> On 09-02-23 01:51 am, Alan Stern wrote:
>>>> On Wed, Feb 08, 2023 at 09:15:54PM +0530, Prashanth K wrote:
>>>>>
>>>>>
>>>>> On 08-02-23 08:24 pm, Greg Kroah-Hartman wrote:
>>>>>> On Wed, Feb 08, 2023 at 07:24:47PM +0530, Prashanth K wrote:
>>>>>>> Consider a case where gserial_disconnect has already cleared
>>>>>>> gser->ioport. And if a wakeup interrupt triggers afterwards,
>>>>>>> gserial_resume gets called, which will lead to accessing of
>>>>>>> gserial->port and thus causing null pointer dereference.Add
>>>>>>> a null pointer check to prevent this.
>>>>>>>
>>>>>>> Fixes: aba3a8d01d62 (" usb: gadget: u_serial: add suspend resume 
>>>>>>> callbacks")
>>>>>>
>>>>>> Nit, and our tools will complain, no " " before the "usb:" string 
>>>>>> here,
>>>>>> right?
>>>>>>
>>>>> Will fix it in next patch.
>>>>>>
>>>>>>
>>>>>>> Signed-off-by: Prashanth K <quic_prashk@quicinc.com>
>>>>>>> ---
>>>>>>>     drivers/usb/gadget/function/u_serial.c | 3 +++
>>>>>>>     1 file changed, 3 insertions(+)
>>>>>>>
>>>>>>> diff --git a/drivers/usb/gadget/function/u_serial.c 
>>>>>>> b/drivers/usb/gadget/function/u_serial.c
>>>>>>> index 840626e..98be2b8 100644
>>>>>>> --- a/drivers/usb/gadget/function/u_serial.c
>>>>>>> +++ b/drivers/usb/gadget/function/u_serial.c
>>>>>>> @@ -1428,6 +1428,9 @@ void gserial_resume(struct gserial *gser)
>>>>>>>         struct gs_port *port = gser->ioport;
>>>>>>>         unsigned long    flags;
>>>>>>> +    if (!port)
>>>>>>> +        return;
>>>>>>> +
>>>>>>
>>>>>> What prevents port from going to NULL right after this check?
>>>>> In our case we got a null pointer de-reference while performing USB
>>>>> compliance tests, as the gser->port was null. Because in 
>>>>> gserial_resume,
>>>>> spinlock_irq_save(&port->port_lock) accesses a null-pointer as port 
>>>>> was
>>>>> already marked null by gserial_disconnect.
>>>>>
>>>>> And after gserial_resume acquires the spinlock, gserial_disconnect 
>>>>> cant mark
>>>>> it null until the spinlock is released. We need to check if the 
>>>>> port->lock
>>>>> is valid before accessing it, otherwise it can lead to the above 
>>>>> mentioned
>>>>> scenario
>>>>
>>>> What happens if gserial_disconnect sets gser->port to NULL immediately
>>>> after your new check occurs, but before
>>>> spinlock_irq_save(&port->port_lock) gets called?
>>>>
>>>> You may need to add a static spinlock to prevent this from happening.
>>>>
>>>> Alan Stern
>>> In that case i guess we have to make port_lock a global variable and 
>>> take it
>>> out of gs_port structure.
>>>
>>> + static DEFINE_SPINLOCK(port_lock);
>>>
>>> struct gs_port {
>>>     struct tty_port port;
>>> -    spinlock_t port_lock;
>>>
>>> This will require us to change all the spinlock(port->port_lock) used in
>>> u_serial.c, what do you suggest?
>>
>> Yes, that would be the correct thing to do.
Hi Greg/Alan, One general doubt, if we make the spinlock static/global, 
wouldn't that be a problem when there are multiple instances, and also 
multiple interfaces can use u_serial at same time. Asking this because 
u_serial can be used by f_serial (gser) as well as f_acm (acm).

Thanks
Prahanth K

> will do it and share next patch
> 
> Thanks for the suggestions
> Prashanth K
Alan Stern Feb. 9, 2023, 3:09 p.m. UTC | #8
On Thu, Feb 09, 2023 at 07:37:01PM +0530, Prashanth K wrote:
> 
> 
> On 09-02-23 12:33 pm, Prashanth K wrote:
> > 
> > 
> > On 09-02-23 12:31 pm, Greg Kroah-Hartman wrote:
> > > On Thu, Feb 09, 2023 at 10:31:50AM +0530, Prashanth K wrote:
> > > > In that case i guess we have to make port_lock a global variable
> > > > and take it
> > > > out of gs_port structure.
> > > > 
> > > > + static DEFINE_SPINLOCK(port_lock);
> > > > 
> > > > struct gs_port {
> > > >     struct tty_port port;
> > > > -    spinlock_t port_lock;
> > > > 
> > > > This will require us to change all the spinlock(port->port_lock) used in
> > > > u_serial.c, what do you suggest?
> > > 
> > > Yes, that would be the correct thing to do.
> Hi Greg/Alan, One general doubt, if we make the spinlock static/global,
> wouldn't that be a problem when there are multiple instances, and also
> multiple interfaces can use u_serial at same time. Asking this because
> u_serial can be used by f_serial (gser) as well as f_acm (acm).

You should consider having _two_ spinlocks: One in the gs_port structure 
(the way it is now) and a separate global lock.  The first would be used 
in situations where you know you have a valid pointer.  The second would 
be used in situations where you don't know if the pointer is non-NULL 
or where you are changing the pointer's value.

Alan Stern
Prashanth K Feb. 9, 2023, 3:43 p.m. UTC | #9
On 09-02-23 08:39 pm, Alan Stern wrote:
> On Thu, Feb 09, 2023 at 07:37:01PM +0530, Prashanth K wrote:
>>
>>
>> On 09-02-23 12:33 pm, Prashanth K wrote:
>>>
>>>
>>> On 09-02-23 12:31 pm, Greg Kroah-Hartman wrote:
>>>> On Thu, Feb 09, 2023 at 10:31:50AM +0530, Prashanth K wrote:
>>>>> In that case i guess we have to make port_lock a global variable
>>>>> and take it
>>>>> out of gs_port structure.
>>>>>
>>>>> + static DEFINE_SPINLOCK(port_lock);
>>>>>
>>>>> struct gs_port {
>>>>>      struct tty_port port;
>>>>> -    spinlock_t port_lock;
>>>>>
>>>>> This will require us to change all the spinlock(port->port_lock) used in
>>>>> u_serial.c, what do you suggest?
>>>>
>>>> Yes, that would be the correct thing to do.
>> Hi Greg/Alan, One general doubt, if we make the spinlock static/global,
>> wouldn't that be a problem when there are multiple instances, and also
>> multiple interfaces can use u_serial at same time. Asking this because
>> u_serial can be used by f_serial (gser) as well as f_acm (acm).
> 
> You should consider having _two_ spinlocks: One in the gs_port structure
> (the way it is now) and a separate global lock.  The first would be used
> in situations where you know you have a valid pointer.  The second would
> be used in situations where you don't know if the pointer is non-NULL
> or where you are changing the pointer's value.
Lets say we replaced the existing spinlock in gserial_resume and 
gserial_disconnect with a new static spinlock, and kept the spinlocks in 
other functions unchanged. In that case, wouldn't it cause additional 
race conditions as we are using 2 different locks.

Thanks,
Prashanth K
> 
> Alan Stern
Alan Stern Feb. 9, 2023, 4:03 p.m. UTC | #10
On Thu, Feb 09, 2023 at 09:13:37PM +0530, Prashanth K wrote:
> 
> 
> On 09-02-23 08:39 pm, Alan Stern wrote:
> > You should consider having _two_ spinlocks: One in the gs_port structure
> > (the way it is now) and a separate global lock.  The first would be used
> > in situations where you know you have a valid pointer.  The second would
> > be used in situations where you don't know if the pointer is non-NULL
> > or where you are changing the pointer's value.
> Lets say we replaced the existing spinlock in gserial_resume and
> gserial_disconnect with a new static spinlock, and kept the spinlocks in
> other functions unchanged. In that case, wouldn't it cause additional race
> conditions as we are using 2 different locks.

Not race conditions, but possibilities for deadlock.

Indeed, you would have to be very careful about avoiding deadlock 
scenarios.  In particular, you would have to ensure that the code never 
tries to acquire the global spinlock while already holding one of the 
per-port spinlocks.

Alan Stern
Prashanth K Feb. 9, 2023, 6:27 p.m. UTC | #11
On 09-02-23 09:33 pm, Alan Stern wrote:
> On Thu, Feb 09, 2023 at 09:13:37PM +0530, Prashanth K wrote:
>>
>>
>> On 09-02-23 08:39 pm, Alan Stern wrote:
>>> You should consider having _two_ spinlocks: One in the gs_port structure
>>> (the way it is now) and a separate global lock.  The first would be used
>>> in situations where you know you have a valid pointer.  The second would
>>> be used in situations where you don't know if the pointer is non-NULL
>>> or where you are changing the pointer's value.
>> Lets say we replaced the existing spinlock in gserial_resume and
>> gserial_disconnect with a new static spinlock, and kept the spinlocks in
>> other functions unchanged. In that case, wouldn't it cause additional race
>> conditions as we are using 2 different locks.
> 
> Not race conditions, but possibilities for deadlock.
> 
> Indeed, you would have to be very careful about avoiding deadlock
> scenarios.  In particular, you would have to ensure that the code never
> tries to acquire the global spinlock while already holding one of the
> per-port spinlocks.
> 
> Alan Stern
Hi Alan, instead of doing these and causing potential regressions, can 
we just have the null pointer check which i suggested in the beginning? 
The major concern was that port might become null after the null pointer 
check. We mark gser->ioport as null pointer in gserial_disconnect, and 
in gserial_resume we copy the gser->ioport to *port in the beginning.

struct gs_port *port = gser->ioport;

And hence it wont cause null pointer deref after the check as we don't 
de-reference anything from gser->ioport afterwards. We only use the 
local pointer *port afterwards.

Thanks,
Prashanth K
Alan Stern Feb. 9, 2023, 9:05 p.m. UTC | #12
On Thu, Feb 09, 2023 at 11:57:17PM +0530, Prashanth K wrote:
> 
> 
> On 09-02-23 09:33 pm, Alan Stern wrote:
> > On Thu, Feb 09, 2023 at 09:13:37PM +0530, Prashanth K wrote:
> > > 
> > > 
> > > On 09-02-23 08:39 pm, Alan Stern wrote:
> > > > You should consider having _two_ spinlocks: One in the gs_port structure
> > > > (the way it is now) and a separate global lock.  The first would be used
> > > > in situations where you know you have a valid pointer.  The second would
> > > > be used in situations where you don't know if the pointer is non-NULL
> > > > or where you are changing the pointer's value.
> > > Lets say we replaced the existing spinlock in gserial_resume and
> > > gserial_disconnect with a new static spinlock, and kept the spinlocks in
> > > other functions unchanged. In that case, wouldn't it cause additional race
> > > conditions as we are using 2 different locks.
> > 
> > Not race conditions, but possibilities for deadlock.
> > 
> > Indeed, you would have to be very careful about avoiding deadlock
> > scenarios.  In particular, you would have to ensure that the code never
> > tries to acquire the global spinlock while already holding one of the
> > per-port spinlocks.
> > 
> > Alan Stern
> Hi Alan, instead of doing these and causing potential regressions, can we
> just have the null pointer check which i suggested in the beginning? The
> major concern was that port might become null after the null pointer check.

What you are describing is a data race: gserial_disconnect() can write 
to gser->ioport at the same time that gserial_resume() reads from it.  
Unless you're working on a fast path -- which this isn't -- you should 
strive to avoid data races by using proper locking.  That means adding 
the extra spinlock, or finding some other way to make these two accesses 
be mutually exclusive.

With a little care you can ensure there won't be any regressions.  Just 
do what I said above: Make sure the code never tries to acquire the 
global spinlock while already holding one of the per-port spinlocks.

> We mark gser->ioport as null pointer in gserial_disconnect, and in
> gserial_resume we copy the gser->ioport to *port in the beginning.
> 
> struct gs_port *port = gser->ioport;
> 
> And hence it wont cause null pointer deref after the check as we don't
> de-reference anything from gser->ioport afterwards. We only use the local
> pointer *port afterwards.

You cannot depend on this to work the way you want.  The compiler will 
optimize your source code, and one of the optimizations might be to 
eliminate the "port" variable entirely and replace it with gser->ioport.

Alan Stern
Prashanth K Feb. 10, 2023, 6:22 a.m. UTC | #13
On 10-02-23 02:35 am, Alan Stern wrote:
> On Thu, Feb 09, 2023 at 11:57:17PM +0530, Prashanth K wrote:
>>
>>
>> On 09-02-23 09:33 pm, Alan Stern wrote:
>>> On Thu, Feb 09, 2023 at 09:13:37PM +0530, Prashanth K wrote:
>>>>
>>>>
>>>> On 09-02-23 08:39 pm, Alan Stern wrote:
>>>>> You should consider having _two_ spinlocks: One in the gs_port structure
>>>>> (the way it is now) and a separate global lock.  The first would be used
>>>>> in situations where you know you have a valid pointer.  The second would
>>>>> be used in situations where you don't know if the pointer is non-NULL
>>>>> or where you are changing the pointer's value.
>>>> Lets say we replaced the existing spinlock in gserial_resume and
>>>> gserial_disconnect with a new static spinlock, and kept the spinlocks in
>>>> other functions unchanged. In that case, wouldn't it cause additional race
>>>> conditions as we are using 2 different locks.
>>>
>>> Not race conditions, but possibilities for deadlock.
>>>
>>> Indeed, you would have to be very careful about avoiding deadlock
>>> scenarios.  In particular, you would have to ensure that the code never
>>> tries to acquire the global spinlock while already holding one of the
>>> per-port spinlocks.
>>>
>>> Alan Stern
>> Hi Alan, instead of doing these and causing potential regressions, can we
>> just have the null pointer check which i suggested in the beginning? The
>> major concern was that port might become null after the null pointer check.
> 
> What you are describing is a data race: gserial_disconnect() can write
> to gser->ioport at the same time that gserial_resume() reads from it.
> Unless you're working on a fast path -- which this isn't -- you should
> strive to avoid data races by using proper locking.  That means adding
> the extra spinlock, or finding some other way to make these two accesses
> be mutually exclusive.
> 
> With a little care you can ensure there won't be any regressions.  Just
> do what I said above: Make sure the code never tries to acquire the
> global spinlock while already holding one of the per-port spinlocks.
> 
>> We mark gser->ioport as null pointer in gserial_disconnect, and in
>> gserial_resume we copy the gser->ioport to *port in the beginning.
>>
>> struct gs_port *port = gser->ioport;
>>
>> And hence it wont cause null pointer deref after the check as we don't
>> de-reference anything from gser->ioport afterwards. We only use the local
>> pointer *port afterwards.
> 
> You cannot depend on this to work the way you want.  The compiler will
> optimize your source code, and one of the optimizations might be to
> eliminate the "port" variable entirely and replace it with gser->ioport.
> 
> Alan Stern
Hi Alan, Thanks for the detailed info. I checked and included few cases 
here.

This would cause a deadlock if gserial_disconnect acquires port_lock and 
gserial_resume acquires static_lock.

gserial_disconnect {
	spin_lock(port)
	...
	spin_lock(static)

	gser->ioport = NULL;

	spin_unlock(static)
	...
	spin_unlock(port)
}

gserial_resume {
	struct gs_port *port = gser->ioport;

	spin_lock(static)
	if (!port)
		return
	spin_lock(port)
	spin_unlock(static)

	...
	spin_unlock(port)
}

------------------------------------------------------------------

This would cause additional races when gserial_disconnect releases 
port_lock and some other functions acquire it.

gserial_disconnect {
	spin_lock(port)
	...
	spin_unlock(port)
	spin_lock(static)

	gser->ioport = NULL;

	spin_unlock(static)
	spin_lock(port)
	...
	spin_unlock(port)
}

gserial_resume {
	struct gs_port *port = gser->ioport;

	spin_lock(static)
	if (!port)
		return
	spin_lock(port)
	spin_unlock(static)

	...
	spin_unlock(port)
}

------------------------------------------------------------------

And this seems like a viable option to me, what do you suggest?

gserial_disconnect {
	spin_lock(static)
	spin_lock(port)
	...
	gser->ioport = NULL;
	...	
	spin_lock(port)
	spin_unlock(static)

}

gserial_resume {
	struct gs_port *port = gser->ioport;

	spin_lock(static)
	if (!port)
		return
	spin_lock(port)

	...
	spin_unlock(port)
	spin_unlock(static)
}

Thanks,
Prashanth K
Prashanth K Feb. 10, 2023, 6:56 a.m. UTC | #14
> And this seems like a viable option to me, what do you suggest?
> 
> gserial_disconnect {
>      spin_lock(static)
>      spin_lock(port)
>      ...
>      gser->ioport = NULL;
>      ...
>      spin_lock(port)
>      spin_unlock(static)
> 
> }
> 
> gserial_resume {
>      struct gs_port *port = gser->ioport;
> 
>      spin_lock(static)
>      if (!port)
	   spin_unlock(static)
>          return
>      spin_lock(port)
> 
>      ...
>      spin_unlock(port)
>      spin_unlock(static)
> }
> 
> Thanks,
> Prashanth K
Small correction inlined.
Alan Stern Feb. 10, 2023, 3:47 p.m. UTC | #15
On Fri, Feb 10, 2023 at 12:26:52PM +0530, Prashanth K wrote:
> 
> 
> > And this seems like a viable option to me, what do you suggest?
> > 
> > gserial_disconnect {
> >      spin_lock(static)
> >      spin_lock(port)
> >      ...
> >      gser->ioport = NULL;
> >      ...
> >      spin_lock(port)
> >      spin_unlock(static)
> > 
> > }
> > 
> > gserial_resume {
> >      struct gs_port *port = gser->ioport;
> > 
> >      spin_lock(static)
> >      if (!port)
> 	   spin_unlock(static)
> >          return
> >      spin_lock(port)

If you want, you could move the spin_unlock(static) up to here.  It 
probably doesn't matter.

> > 
> >      ...
> >      spin_unlock(port)
> >      spin_unlock(static)
> > }

I agree, that should work fine.

Alan Stern
Prashanth K Feb. 11, 2023, 6:25 a.m. UTC | #16
On 10-02-23 09:17 pm, Alan Stern wrote:
> 
> I agree, that should work fine.
> 
> Alan Stern
Thanks Alan for the help! will create v2 patch.
diff mbox series

Patch

diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
index 840626e..98be2b8 100644
--- a/drivers/usb/gadget/function/u_serial.c
+++ b/drivers/usb/gadget/function/u_serial.c
@@ -1428,6 +1428,9 @@  void gserial_resume(struct gserial *gser)
 	struct gs_port *port = gser->ioport;
 	unsigned long	flags;
 
+	if (!port)
+		return;
+
 	spin_lock_irqsave(&port->port_lock, flags);
 	port->suspended = false;
 	if (!port->start_delayed) {