Tuesday, August 12, 2014

Controlling USB device access on Linux (e.g. BADusb defense)

So, there was a lot of fuzz about a recent talk by Karsten Nohl et al. at BlackHat about the the unsecurity of current USB implementations (on the computer side) which happily load drivers for all kinds of devices as soon as a (potentially malicious) USB stick is connected.

I completely agree that, as shipped, most computer systems will be susceptible to this attack, and assume that all of their attacks will work as advertised. What I don't agree with at all is their conclusion, which boils down that no effective defenses exist.

While I haven't made my homework to develop a defense, I want to at least show the mechanism in current Linux kernels to limit binding of potentially dangerous drivers to specific devices, so that e.g. a inserted USB-stick would only be mounted as a block device, and its malicious keyboard interface be ignored.

Binding of Drivers

A linux-module can claim ownership of certain usb vendor/device ids or device classes. Those are visible when looking at the modinfo of a kernel module, to facilitate autoloading of modules, but are also registered in internal kernel structures, so that drivers compiled in statically directly can be mated with the proper devices:

$ modinfo snd-usb-audio
filename:       /lib/modules/3.15.8-1-ARCH/kernel/sound/usb/snd-usb-audio.ko.gz
license:        GPL
description:    USB Audio
author:         Takashi Iwai
alias:          usb:v*p*d*dc*dsc*dp*ic01isc01ip*in*
alias:          usb:v0D8Cp0103d*dc*dsc*dp*ic*isc*ip*in*
alias:          usb:v*p*d*dc*dsc*dp*ic01isc03ip*in*
alias:          usb:v200Cp100Bd*dc*dsc*dp*ic*isc*ip*in*
This information currently comes from the MODULE_DEVICE_TABLE of sound/usb/card.c, stored in an array called usb_audio_ids. The bold one is the "default class", the others all are included in the "quirks table", which includes all exceptions from the default class.
static struct usb_device_id usb_audio_ids [] = {
#include "quirks-table.h"
       .bInterfaceClass = USB_CLASS_AUDIO,
       .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
     { }                                         /* Terminating entry */
MODULE_DEVICE_TABLE(usb, usb_audio_ids);
On connection of a USB device, the kernel will look through all its currently registered modules that claim to support USB devices (by registering those tables with the kernel) and bind devices to drivers (and if it doesn't succeed, it will call udev/hotplug to load a module that does). 

The Vulnerability

So, if you connect a particular USB headset, the electronics of which I have laying around... you get a new sound device in ALSA...
[root@optiplex devices]# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 1: Headset [Logitech USB Headset], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
But also a new keyboard! (the mute/volume buttons on the headset, but those could be arbitrary buttons entering text, too).
[root@optiplex devices]# xinput
⎡ Virtual core pointer                     id=2 [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer               id=4 [slave  pointer  (2)]
⎜   ↳ Logitech USB-PS/2 Optical Mouse         id=8 [slave  pointer  (2)]
⎣ Virtual core keyboard                   id=3 [master keyboard (2)]
    ↳ Virtual core XTEST keyboard             id=5 [slave  keyboard (3)]
    ↳ Power Button                             id=6 [slave  keyboard (3)]
    ↳ Power Button                             id=7 [slave  keyboard (3)]
    ↳ Das Keyboard                             id=9 [slave  keyboard (3)]
    ↳ Logitech Logitech USB Headset           id=10 [slave  keyboard (3)]
If you look in the sysfs filesystem, you can see, that the USB headset (usb device 5-1) has 4 distinct functions (.0 -- .3) on configuration :1.
[root@optiplex /sys/bus/usb/devices/5-1]# ls
5-1:1.0     bcdDevice    maxchild
5-1:1.1     bmAttributes   port
5-1:1.2     busnum    power
5-1:1.3     configuration  product
authorized     descriptors    quirks
avoid_reset_quirk    dev    removable
bConfigurationValue  devnum    remove
bDeviceClass     devpath    speed
bDeviceProtocol      driver    subsystem
bDeviceSubClass      ep_00    uevent
bMaxPacketSize0      idProduct    urbnum
bMaxPower     idVendor    version
bNumConfigurations   ltm_capable
bNumInterfaces     manufacturer
And those are described by the (very lengthy) lsusb -v output, which I'll not reproduce completely here, just try it for yourself on any USB device.

Bus 005 Device 006: ID 046d:0a0b Logitech, Inc. ClearChat Pro USB
Device Descriptor:
  bNumConfigurations      1
  Configuration Descriptor: (configuration #1 starts here)
    bLength                 9
    Interface Descriptor: (interface 1:0)
       bDescriptorType         4
       bInterfaceNumber        0

    bInterfaceClass         1 Audio
      bInterfaceSubClass      1 Control Device
      AudioControl Interface Descriptor: (detailed info about audio capabilities)
        bLength                10
    Interface Descriptor: (interface 1:3)
      bInterfaceNumber        3
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 

So, here you have it, one device that (legitimately) is both a soundcard and a keyboard/mouse (e.g. an human interface device). Given enough motiuvation, code could be written for the controller inside the soundcard that enters arbitary text to my computer. And that's basically the vulnerability presented in the Black Hat Presentation by Nohl et al.


But, you can easily turn off this automatic binding, at least on Linux, with one single command:
[root@optiplex ~]# echo 0 >/sys/bus/usb/drivers_autoprobe 
Now, whenever you connect a USB device to your computer, it will not automatically connect the usb-soundcard to the ALSA subsystem and the volume buttons to the hidraw/keyboard driver. If I now connect the aforementioned soundcard, I'll not get a new keyboard in xinput, nor a soundcard in ALSA (and also not a network- or block-device for a network card or USB disk).

The only thing I'll get in my dmesg is the message, that an USB device has been connected:
[13399.092113] usb 5-1: USB disconnect, device number 6
[13405.245389] usb 5-1: new full-speed USB device number 7 using uhci_hcd
And to manually bind this device, you first have to choose the appropriate USB configuration...
# echo 1 >/sys/bus/usb/devices/5-1/bConfigurationValue 
...and tell the usb audio driver to bind to that device.
# echo 5-1:1.0 >/sys/bus/usb/drivers/snd-usb-audio/bind
Now you have the USB audio interface, but not the keyboard, yet... If you wanted, you'd just bind usb-hid to interface 3.
# echo 5-1:1.3 >/sys/bus/usb/drivers/usbhid/bind 
To verify that your keyboard presses aren't registed from the connected device before, and are registered after binding, you can run "xev" and press the volume buttons (note: those, in principle, could enter arbitrary text to your computer, depending on the programming of the controller in the device).
KeyRelease event, serial 38, synthetic NO, window 0x3800001,
    root 0x1dc, subw 0x3800002, time 13880057, (41,58), root:(2384,618),
    state 0x10, keycode 123 (keysym 0x1008ff13, XF86AudioRaiseVolume), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False
The most often quoted use-case if obviously connection of a usb-storage device, e.g. an external harddrive or flash-stick.

[14567.888596] usb 6-1: new high-speed USB device number 4 using ehci-pci
# echo 1 >/sys/bus/usb/devices/6-1/bConfigurationValue
# echo 6-1:1.0 >/sys/bus/usb/drivers/usb-storage/bind
[14667.692717] usb-storage 6-1:1.0: USB Mass Storage device detected
[14667.692936] scsi7 : usb-storage 6-1:1.0
[14668.696482] scsi 7:0:0:0: Direct-Access     TOSHIBA  MK4309MAT        G5.0 PQ: 0 ANSI: 0 CCS
[14668.698461] sd 7:0:0:0: [sdd] 8452080 512-byte logical blocks: (4.32 GB/4.02 GiB)
(yes, it's a very, very crappy and old USB harddisk)

Proper user-interface, current lack thereof.

To convert all this sysfs poking into a proper user-friendly software, one will have to...

  • On computer startup, disable  autoprobe (probably there's a kernel command line for that).
  • Statically configure the minimal interfaces used (e.g. the main usb keyboard, trackpad/mouse, ...).
  • During normal operation, watch hotplug-events or poll the sysfs filesystem for newly inserted usb devices.
  • Verify presented configurations of USB devices and their interfaces with a policy (e.g. enable only usb-storage devices, e.g. "thumbdrivers"
  • Ask the user, if he's happy with this device.
  • On successful verification, only bind the single necessary driver (e.g. usb-storage) to the proper interface of the device.

Of course, for certain devices more sophisticated verification schemes could be envisioned, checking certificates, downloading firmware for verification (which, again, might be forged, ...).

The "Authorized" Mechanism

There's a second mechanism to disable/enable communication with USB device using the "authorized" property of USB controllers. It's described in the kernel documentation.


There seems to be a group policy for that... Local Computer PolicyComputer Configuration,Administrative TemplatesSystemDevice Installation, and Device Installation Restrictions


Unknown said...

Nice blog and very informative thank you for sharing us.
Thank you
Mcx Gold Tips

Anonymous said...

Hi Chris,

Thanks for posting this. I found it useful, as I had been trying to find out why Linux could not scan using my multi-function peripheral connected via USB, but can scan if I connect to the MFP via my home network instead. The problem is that the MFP has three interfaces in one USB device: 0 scanner; 1 printer; 2 mass storage. The kernel's usb-storage driver claims the mass storage interface in the USB device and stops the SANE scanner driver from accessing the scanner interface in the USB device. By toggling drivers_autoprobe I was able to prove this, and also enable scanner applications to access the scanner via the USB connection.

Anonymous said...

Regarding my previous post, I spoke too soon. Setting drivers_autoprobe to zero did indeed stop the kernel USB drivers from binding to the MFP, but the scanner application XSane was then using my home network connection to the MFP to scan, not a USB connection as I had thought. So I still need to find a way to enable scanner applications (libusb/SANE) to access the scanner interface of the USB device when auto-probing has been disabled.

Unknown said...
This comment has been removed by the author.
Unknown said...

@Max von Einstein: My guess is, that you would have to put some code in drivers/usb/core/usb.c's usb_init() function where the usb bus type is registered...

after bus_register() you probably could put in something along the lines of...

retval = bus_register(&usb_bus_type);
if (retval)
goto bus_register_failed;

Because right now drivers_autoprobe is fixed to 1 in drivers/base/bus.c's bus_register().

Probably cleaner would be to have a strcmp() in bus_register() and choose on what to set drivers_autoprobe to there.

Unknown said...
This comment has been removed by the author.
Unknown said...

Here's a quick and dirty patch:


(Sorry, previous comment was nonsensical.)