CVE-2021-38204 (C, published 2021-08-08T20:15Z)
Description: value: drivers/usb/host/max3421-hcd.c in the Linux kernel before 5.13.6 allows physically proximate attackers to cause a denial of service (use-after-free and panic) by removing a MAX-3421 USB device in certain situations.
Severity: MEDIUM (CVSS v3 base score: 6.8)
Weakness: CWE-416 \u2014 Use After Free
The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
Repository: https://github.com/torvalds/linux
Files changed:
drivers/usb/host/max3421-hcd.c
Commit message: usb: max-3421: Prevent corruption of freed memory

The MAX-3421 USB driver remembers the state of the USB toggles for a
device/endpoint. To save SPI writes, this was only done when a new
device/endpoint was being used. Unfortunately, if the old device was
removed, this would cause writes to freed memory.

To fix this, a simpler scheme is used. The toggles are read from
hardware when a URB is completed, and the toggles are always written to
hardware when any URB transaction is started. This will cause a few more
SPI transactions, but no causes kernel panics.

Fixes: 2d53139f3162 ("Add support for using a MAX3421E chip as a host driver.")
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Mark Tomlinson <mark.tomlinson@alliedtelesis.co.nz>
Link: https://lore.kernel.org/r/20210625031456.8632-1-mark.tomlinson@alliedtelesis.co.nz
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>


Vulnerable code:
	struct usb_device *loaded_dev;	/* dev that's loaded into the chip */
	int loaded_epnum;		/* epnum whose toggles are loaded */
max3421_set_address(struct usb_hcd *hcd, struct usb_device *dev, int epnum,
		    int force_toggles)
	struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd);
	int old_epnum, same_ep, rcvtog, sndtog;
	struct usb_device *old_dev;
	old_dev = max3421_hcd->loaded_dev;
	old_epnum = max3421_hcd->loaded_epnum;

	same_ep = (dev == old_dev && epnum == old_epnum);
	if (same_ep && !force_toggles)
		return;

	if (old_dev && !same_ep) {
		/* save the old end-points toggles: */
		u8 hrsl = spi_rd8(hcd, MAX3421_REG_HRSL);

		rcvtog = (hrsl >> MAX3421_HRSL_RCVTOGRD_BIT) & 1;
		sndtog = (hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1;

		/* no locking: HCD (i.e., we) own toggles, don't we? */
		usb_settoggle(old_dev, old_epnum, 0, rcvtog);
		usb_settoggle(old_dev, old_epnum, 1, sndtog);
	}
	max3421_hcd->loaded_epnum = epnum;
	max3421_hcd->loaded_dev = dev;
	int epnum, force_toggles = 0;
			force_toggles = 1;
	max3421_set_address(hcd, urb->dev, epnum, force_toggles);

Fixed code:
max3421_set_address(struct usb_hcd *hcd, struct usb_device *dev, int epnum)
	int rcvtog, sndtog;
	int epnum;
	max3421_set_address(hcd, urb->dev, epnum);
		/* save the old end-points toggles: */
		u8 hrsl = spi_rd8(hcd, MAX3421_REG_HRSL);
		int rcvtog = (hrsl >> MAX3421_HRSL_RCVTOGRD_BIT) & 1;
		int sndtog = (hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1;
		int epnum = usb_endpoint_num(&urb->ep->desc);

		/* no locking: HCD (i.e., we) own toggles, don't we? */
		usb_settoggle(urb->dev, epnum, 0, rcvtog);
		usb_settoggle(urb->dev, epnum, 1, sndtog);