diff mbox series

[v4] usb: prevent duplicate bEndpointAddress by usb_ep_autoconfig_ss (bitmap).

Message ID 20230429154733.4429-1-wlodzimierz.lipert@gmail.com (mailing list archive)
State New, archived
Headers show
Series [v4] usb: prevent duplicate bEndpointAddress by usb_ep_autoconfig_ss (bitmap). | expand

Commit Message

Wlodzimierz Lipert April 29, 2023, 3:47 p.m. UTC
usb_ep_autoconfig_ss tries to use endpoint name or internal counters to generate
bEndpointAddress - this leads to duplicate addresses. Fix changes the
way in/out_epnum is used, now as bitmap which represents unavailable ep numbers.

v2: formatting fixes only (errors).
v3: refined autoconf logic (removed num_mask, switched to use ffz).
v4: formatting only changes (style).

Signed-off-by: Wlodzimierz Lipert <wlodzimierz.lipert@gmail.com>
---
 drivers/usb/gadget/epautoconf.c | 25 ++++++++++++++++---------
 include/linux/usb/gadget.h      |  4 ++--
 2 files changed, 18 insertions(+), 11 deletions(-)

Comments

Greg KH May 6, 2023, 12:44 a.m. UTC | #1
On Sat, Apr 29, 2023 at 05:47:33PM +0200, Wlodzimierz Lipert wrote:
> usb_ep_autoconfig_ss tries to use endpoint name or internal counters to generate
> bEndpointAddress - this leads to duplicate addresses. Fix changes the
> way in/out_epnum is used, now as bitmap which represents unavailable ep numbers.

Please properly wrap your changelog lines at 72 columns like your editor
asked you to.

> v2: formatting fixes only (errors).
> v3: refined autoconf logic (removed num_mask, switched to use ffz).
> v4: formatting only changes (style).

As per the documentation, this information about changes needs to go
below the --- line.



> 
> Signed-off-by: Wlodzimierz Lipert <wlodzimierz.lipert@gmail.com>
> ---
>  drivers/usb/gadget/epautoconf.c | 25 ++++++++++++++++---------
>  include/linux/usb/gadget.h      |  4 ++--
>  2 files changed, 18 insertions(+), 11 deletions(-)

What commit id does this fix?  Does it need to go to older stable
kernels?  If so, how far back?

> 
> diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c
> index 1eb4fa2e623f..f73a7fe3d7d7 100644
> --- a/drivers/usb/gadget/epautoconf.c
> +++ b/drivers/usb/gadget/epautoconf.c
> @@ -67,6 +67,8 @@ struct usb_ep *usb_ep_autoconfig_ss(
>  )
>  {
>  	struct usb_ep	*ep;
> +	unsigned	*epmap;

Why "unsigned"?  Didn't checkpatch complain about this?

> +	u8		num;
>  
>  	if (gadget->ops->match_ep) {
>  		ep = gadget->ops->match_ep(gadget, desc, ep_comp);
> @@ -93,19 +95,23 @@ struct usb_ep *usb_ep_autoconfig_ss(
>  
>  	/* report address */
>  	desc->bEndpointAddress &= USB_DIR_IN;
> +	epmap = usb_endpoint_dir_in(desc) ?
> +			&gadget->in_epnum : &gadget->out_epnum;

I hate ?: lines with a passion.  Please spell it out with a real if
statement as the generated code is the same and it's easier to actually
read by humans.

> +
>  	if (isdigit(ep->name[2])) {
> -		u8 num = simple_strtoul(&ep->name[2], NULL, 10);
> -		desc->bEndpointAddress |= num;
> -	} else if (desc->bEndpointAddress & USB_DIR_IN) {
> -		if (++gadget->in_epnum > 15)
> +		num = simple_strtoul(&ep->name[2], NULL, 10);
> +		WARN_ON(num == 0 || num > 15);

You just crashed the system if this happened if panic-on-warn is enabled :(

Please never do this, if there is an issue, fix it up and handle it
properly.

> +		if (*epmap & (1U << num))
>  			return NULL;
> -		desc->bEndpointAddress = USB_DIR_IN | gadget->in_epnum;
>  	} else {
> -		if (++gadget->out_epnum > 15)
> +		/* find first available ep number (not encoded in ep name) */
> +		num = ffz(*epmap);
> +		if (num > 15)
>  			return NULL;
> -		desc->bEndpointAddress |= gadget->out_epnum;
>  	}
>  
> +	*epmap |= 1U << num;

BIT()?

> +	desc->bEndpointAddress |= num;
>  	ep->address = desc->bEndpointAddress;
>  	ep->desc = NULL;
>  	ep->comp_desc = NULL;
> @@ -208,7 +214,8 @@ void usb_ep_autoconfig_reset (struct usb_gadget *gadget)
>  		ep->claimed = false;
>  		ep->driver_data = NULL;
>  	}
> -	gadget->in_epnum = 0;
> -	gadget->out_epnum = 0;
> +	/* ep num 0 is reserved: not available for auto configuration */
> +	gadget->in_epnum = 1U;
> +	gadget->out_epnum = 1U;
>  }
>  EXPORT_SYMBOL_GPL(usb_ep_autoconfig_reset);
> diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
> index 6a178177e4c9..1e00e22202bc 100644
> --- a/include/linux/usb/gadget.h
> +++ b/include/linux/usb/gadget.h
> @@ -343,8 +343,8 @@ struct usb_gadget_ops {
>   *	and sometimes configuration.
>   * @dev: Driver model state for this abstract device.
>   * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP
> - * @out_epnum: last used out ep number
> - * @in_epnum: last used in ep number
> + * @out_epnum: bitmap of allocated out ep numbers
> + * @in_epnum: bitmap of allocated in ep numbers

As you changed the functionality of these variables, why not rename them
at the same time to make it obvious?  'in_epnum_bitmap'?

thanks,

greg k-h
Alan Stern May 6, 2023, 12:19 p.m. UTC | #2
On Sat, May 06, 2023 at 09:44:17AM +0900, Greg KH wrote:
> On Sat, Apr 29, 2023 at 05:47:33PM +0200, Wlodzimierz Lipert wrote:

> > --- a/drivers/usb/gadget/epautoconf.c
> > +++ b/drivers/usb/gadget/epautoconf.c
> > @@ -67,6 +67,8 @@ struct usb_ep *usb_ep_autoconfig_ss(
> >  )
> >  {
> >  	struct usb_ep	*ep;
> > +	unsigned	*epmap;
> 
> Why "unsigned"?  Didn't checkpatch complain about this?

In other words, the preferred style in the kernel is to use "unsigned 
int" rather than just "unsigned".  (Although there's plenty of old code 
that still does it that way.)

> >  	if (isdigit(ep->name[2])) {
> > -		u8 num = simple_strtoul(&ep->name[2], NULL, 10);
> > -		desc->bEndpointAddress |= num;
> > -	} else if (desc->bEndpointAddress & USB_DIR_IN) {
> > -		if (++gadget->in_epnum > 15)
> > +		num = simple_strtoul(&ep->name[2], NULL, 10);
> > +		WARN_ON(num == 0 || num > 15);
> 
> You just crashed the system if this happened if panic-on-warn is enabled :(
> 
> Please never do this, if there is an issue, fix it up and handle it
> properly.

I disagree; I think this is an appropriate use of WARN_ON.  It's an 
example of "drivers should never do this, and if they do then weird, 
unpredictable errors will occur".  For these things we _want_ to have a 
very visible notice that a problem needs to be fixed.  (In this case it 
wouldn't hurt to also print out the bad endpoint name as well, along 
with the name of the UDC driver.)

If someone turns on panic-on-warn, it means they _do_ want their system 
to crash when issues like this crop up.  Like syzbot, for example.  If 
instead we just covered up the error then it would never get fixed.

Alan Stern
Greg KH May 6, 2023, 12:29 p.m. UTC | #3
On Sat, May 06, 2023 at 08:19:24AM -0400, Alan Stern wrote:
> On Sat, May 06, 2023 at 09:44:17AM +0900, Greg KH wrote:
> > On Sat, Apr 29, 2023 at 05:47:33PM +0200, Wlodzimierz Lipert wrote:
> 
> > > --- a/drivers/usb/gadget/epautoconf.c
> > > +++ b/drivers/usb/gadget/epautoconf.c
> > > @@ -67,6 +67,8 @@ struct usb_ep *usb_ep_autoconfig_ss(
> > >  )
> > >  {
> > >  	struct usb_ep	*ep;
> > > +	unsigned	*epmap;
> > 
> > Why "unsigned"?  Didn't checkpatch complain about this?
> 
> In other words, the preferred style in the kernel is to use "unsigned 
> int" rather than just "unsigned".  (Although there's plenty of old code 
> that still does it that way.)
> 
> > >  	if (isdigit(ep->name[2])) {
> > > -		u8 num = simple_strtoul(&ep->name[2], NULL, 10);
> > > -		desc->bEndpointAddress |= num;
> > > -	} else if (desc->bEndpointAddress & USB_DIR_IN) {
> > > -		if (++gadget->in_epnum > 15)
> > > +		num = simple_strtoul(&ep->name[2], NULL, 10);
> > > +		WARN_ON(num == 0 || num > 15);
> > 
> > You just crashed the system if this happened if panic-on-warn is enabled :(
> > 
> > Please never do this, if there is an issue, fix it up and handle it
> > properly.
> 
> I disagree; I think this is an appropriate use of WARN_ON.  It's an 
> example of "drivers should never do this, and if they do then weird, 
> unpredictable errors will occur".  For these things we _want_ to have a 
> very visible notice that a problem needs to be fixed.  (In this case it 
> wouldn't hurt to also print out the bad endpoint name as well, along 
> with the name of the UDC driver.)
> 
> If someone turns on panic-on-warn, it means they _do_ want their system 
> to crash when issues like this crop up.  Like syzbot, for example.  If 
> instead we just covered up the error then it would never get fixed.

So you want syzbot to constantly be triggered here by sending in bad
addresses?  If this is something that a user can trigger, it should
never have a WARN_ON().  Or is this only something that a badly written
driver can trigger?  It's hard to tell from the snippet above.

thanks,

greg k-h
Alan Stern May 6, 2023, 3:40 p.m. UTC | #4
On Sat, May 06, 2023 at 02:29:49PM +0200, Greg KH wrote:
> On Sat, May 06, 2023 at 08:19:24AM -0400, Alan Stern wrote:
> > On Sat, May 06, 2023 at 09:44:17AM +0900, Greg KH wrote:
> > > On Sat, Apr 29, 2023 at 05:47:33PM +0200, Wlodzimierz Lipert wrote:

> > > >  	if (isdigit(ep->name[2])) {
> > > > -		u8 num = simple_strtoul(&ep->name[2], NULL, 10);
> > > > -		desc->bEndpointAddress |= num;
> > > > -	} else if (desc->bEndpointAddress & USB_DIR_IN) {
> > > > -		if (++gadget->in_epnum > 15)
> > > > +		num = simple_strtoul(&ep->name[2], NULL, 10);
> > > > +		WARN_ON(num == 0 || num > 15);
> > > 
> > > You just crashed the system if this happened if panic-on-warn is enabled :(
> > > 
> > > Please never do this, if there is an issue, fix it up and handle it
> > > properly.
> > 
> > I disagree; I think this is an appropriate use of WARN_ON.  It's an 
> > example of "drivers should never do this, and if they do then weird, 
> > unpredictable errors will occur".  For these things we _want_ to have a 
> > very visible notice that a problem needs to be fixed.  (In this case it 
> > wouldn't hurt to also print out the bad endpoint name as well, along 
> > with the name of the UDC driver.)
> > 
> > If someone turns on panic-on-warn, it means they _do_ want their system 
> > to crash when issues like this crop up.  Like syzbot, for example.  If 
> > instead we just covered up the error then it would never get fixed.
> 
> So you want syzbot to constantly be triggered here by sending in bad
> addresses?

I don't know about the "constantly" part, but otherwise, yes.

>  If this is something that a user can trigger, it should
> never have a WARN_ON().  Or is this only something that a badly written
> driver can trigger?  It's hard to tell from the snippet above.

Only a badly written driver.  Or possibly (I'm not sure) a bad user of 
gadgetfs or functionfs -- but those things require superuser permission.

Alan Stern
diff mbox series

Patch

diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c
index 1eb4fa2e623f..f73a7fe3d7d7 100644
--- a/drivers/usb/gadget/epautoconf.c
+++ b/drivers/usb/gadget/epautoconf.c
@@ -67,6 +67,8 @@  struct usb_ep *usb_ep_autoconfig_ss(
 )
 {
 	struct usb_ep	*ep;
+	unsigned	*epmap;
+	u8		num;
 
 	if (gadget->ops->match_ep) {
 		ep = gadget->ops->match_ep(gadget, desc, ep_comp);
@@ -93,19 +95,23 @@  struct usb_ep *usb_ep_autoconfig_ss(
 
 	/* report address */
 	desc->bEndpointAddress &= USB_DIR_IN;
+	epmap = usb_endpoint_dir_in(desc) ?
+			&gadget->in_epnum : &gadget->out_epnum;
+
 	if (isdigit(ep->name[2])) {
-		u8 num = simple_strtoul(&ep->name[2], NULL, 10);
-		desc->bEndpointAddress |= num;
-	} else if (desc->bEndpointAddress & USB_DIR_IN) {
-		if (++gadget->in_epnum > 15)
+		num = simple_strtoul(&ep->name[2], NULL, 10);
+		WARN_ON(num == 0 || num > 15);
+		if (*epmap & (1U << num))
 			return NULL;
-		desc->bEndpointAddress = USB_DIR_IN | gadget->in_epnum;
 	} else {
-		if (++gadget->out_epnum > 15)
+		/* find first available ep number (not encoded in ep name) */
+		num = ffz(*epmap);
+		if (num > 15)
 			return NULL;
-		desc->bEndpointAddress |= gadget->out_epnum;
 	}
 
+	*epmap |= 1U << num;
+	desc->bEndpointAddress |= num;
 	ep->address = desc->bEndpointAddress;
 	ep->desc = NULL;
 	ep->comp_desc = NULL;
@@ -208,7 +214,8 @@  void usb_ep_autoconfig_reset (struct usb_gadget *gadget)
 		ep->claimed = false;
 		ep->driver_data = NULL;
 	}
-	gadget->in_epnum = 0;
-	gadget->out_epnum = 0;
+	/* ep num 0 is reserved: not available for auto configuration */
+	gadget->in_epnum = 1U;
+	gadget->out_epnum = 1U;
 }
 EXPORT_SYMBOL_GPL(usb_ep_autoconfig_reset);
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index 6a178177e4c9..1e00e22202bc 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -343,8 +343,8 @@  struct usb_gadget_ops {
  *	and sometimes configuration.
  * @dev: Driver model state for this abstract device.
  * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP
- * @out_epnum: last used out ep number
- * @in_epnum: last used in ep number
+ * @out_epnum: bitmap of allocated out ep numbers
+ * @in_epnum: bitmap of allocated in ep numbers
  * @mA: last set mA value
  * @otg_caps: OTG capabilities of this gadget.
  * @sg_supported: true if we can handle scatter-gather