Linux USB Whitelist

Updated 2nd June 2019.

Table of Contents
  1. Whitelisting Devices
    1. Step 1: Viewing USBs
    2. Step 2: Getting USB Attributes
    3. Step 3: Making the Whitelist

Using a USB whitelist that only allows certain devices helps prevent physical attacks done with equipment similar to the USB Rubber Ducky, or any malicious USB HID. To create a USB whitelist in Linux, we can use udev and add custom rules to only allow some devices.

Note that this technique uses USB product and vendor IDs to determine whether the device should be whitelisted. While IDs can be spoofed, it would need to be a targeted attack with prior knowledge of USB peripheral IDs to bypass the whitelist filters.

However, we can also whitelist by the USB interface, so each ID is restricted to what the USB type is. This way, even if an attacker knows the product and vendor ID of a mouse, they wouldn't be able to spoof a keyboard USB HID with that ID since the whitelist is expecting the device to only be a mouse.

Whitelisting Devices

Follow these steps to create whitelist rules for your devices. There is also a script usb-gen-whitelist to automate this process at the bottom.

Step 1: Viewing USBs

Simple USB listing for finding device information:

lsusb -v 2>/dev/null | egrep '(^Bus|bDeviceClass|bInterfaceClass)' | uniq

Example output:

Bus 002 Device 001: ID 0123:4567 Linux Foundation 3.0 root hub
  bDeviceClass            9 Hub
      bInterfaceClass         9 Hub
Bus 001 Device 004: ID 89ab:cdef Network
  bDeviceClass          224 Wireless
      bInterfaceClass       224 Wireless
Bus 001 Device 007: ID 9abc:def0 Mouse
  bDeviceClass            0 (Defined at Interface level)
      bInterfaceClass         3 Human Interface Device
Bus 001 Device 001: ID 2345:6789 Linux Foundation 2.0 root hub
  bDeviceClass            9 Hub
      bInterfaceClass         9 Hub

Step 2: Getting USB Attributes

Using the Bus/Device information from Step 1, we can now get detailed information on each device.

Detailed USB info (attributes):

udevadm info -a /dev/bus/usb/001/007

Example output:

  looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-1':
    KERNEL=="1-1"
    SUBSYSTEM=="usb"
    DRIVER=="usb"
    ATTR{authorized}=="1"
    .....
    ATTR{bDeviceClass}=="00"
    ATTR{bDeviceProtocol}=="00"
    ATTR{bDeviceSubClass}=="00"
    ATTR{bMaxPacketSize0}=="8"
    ATTR{bMaxPower}=="100mA"
    ATTR{bNumConfigurations}=="1"
    ATTR{bNumInterfaces}==" 1"
    ATTR{bcdDevice}=="0100"
    ATTR{bmAttributes}=="a0"
    ATTR{busnum}=="1"
    ATTR{configuration}=="HID-compliant MOUSE"
    ATTR{devnum}=="7"
    ATTR{devpath}=="1"
    ATTR{idProduct}=="def0"
    ATTR{idVendor}=="9abc"
    ATTR{ltm_capable}=="no"
    ATTR{maxchild}=="0"
    ATTR{product}=="Optical USB Mouse"
    .....

Detailed USB info (environment):

udevadm info /dev/bus/usb/001/007

Example output:

P: /devices/pci0000:00/0000:00:14.0/usb1/1-1
N: bus/usb/001/007
E: BUSNUM=001
E: DEVNAME=/dev/bus/usb/001/007
E: DEVNUM=007
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-1
E: DEVTYPE=usb_device
E: DRIVER=usb
E: ID_BUS=usb
E: ID_MODEL=Optical_USB_Mouse
E: ID_MODEL_ENC=Optical\x20USB\x20Mouse
E: ID_MODEL_ID=def0
E: ID_REVISION=0100
E: ID_SERIAL=9abc_Optical_USB_Mouse
E: ID_USB_INTERFACES=:030102:
E: ID_VENDOR=9abc
E: ID_VENDOR_ENC=9abc
E: ID_VENDOR_FROM_DATABASE=Mouse
E: ID_VENDOR_ID=9abc
E: MAJOR=189
E: MINOR=6
E: PRODUCT=9abc/def0/100
E: SUBSYSTEM=usb
E: TYPE=0/0/0
.....

Step 3: Making the Whitelist

Using the attributes and environment variables from Step 2, we can now create very specific whitelisting rules based on the information we gathered from udevadm.

The example file 99-whitelist.rules (shown below) checks the vendor, product, and USB interface types, but more attributes can be added. The attributes from udevadm info -a should be used with ATTRS{attribute}. The environment variables from udevadm info should be used with ENV{ENV_VAR}.

I use ENV{ID_USB_INTERFACES} instead of the ATTR{bDeviceClass}, ATTR{bDeviceProtocol}, etc. to check USB interface types because the attribute values are sometimes zero (specified by each interface), but the environment variables have always been filled in when testing.

To use this file, place it in /etc/udev/rules.d/ and uncomment the block line, but only after adding all the devices to the whitelist. These rules are checked when any new devices are added, so make sure any needed devices are whitelisted beforehand (*cough* keyboard), and test a few at a time if any changes need to be made. Note these whitelist rules automatically ignore USB hubs, so there's no need to add them explicitly.

I have also written a script, usb-gen-whitelist, that will automatically generate the USB whitelist file and whitelist all USB devices currently plugged in. It can be found below along with the other examples.

To reload udev rules after changes:

udevadm control --reload-rules

To monitor udev rules in action:

udevadm monitor -u

Running a Command on Rejected Devices

We can run a command on each device that was rejected by our whitelist rules by using RUN. To pass attributes to the command, we use $attr{attribute}. To pass environment variables to the command, we use %E{ENV_VAR}.

The example whitelist rules will use RUN to run /usr/local/sbin/udev-blocked (located below) to log any blocked devices to /var/log/udev-whitelist.log. udev-blocked will also use notify-send to popup a notification when a device is blocked.

Download:

file icon 99-whitelist.rules1.0 KB
file icon udev-blocked1.0 KB
file icon usb-gen-whitelist2.7 KB

99-whitelist.rules:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# udev usb whitelist rules

# skip
SUBSYSTEM!="usb", GOTO="wl_end"
ACTION!="add", GOTO="wl_end"

# skip hubs
ATTR{bDeviceClass}=="09", GOTO="wl_end"

#===========
# whitelist
#===========

# keyboard example
#ATTRS{idVendor}=="0123", ATTRS{idProduct}=="4567", ENV{ID_USB_INTERFACES}==":030101:030000:", GOTO="wl_end"
# mouse example
#ATTRS{idVendor}=="89ab", ATTRS{idProduct}=="cdef", ENV{ID_USB_INTERFACES}==":030102:", GOTO="wl_end"

# usb mass storage example
#ATTRS{idVendor}=="0123", ATTRS{idProduct}=="4567", ENV{ID_USB_INTERFACES}==":080650:", GOTO="wl_end"

#===============
# end whitelist
#===============

# block any device not whitelisted
#ENV{DEVTYPE}=="usb_device", ENV{UDISKS_IGNORE}="1", ATTR{authorized}:="0", RUN+="/usr/local/sbin/udev-blocked $attr{idVendor}:$attr{idProduct} ID_MODEL=\"%E{ID_MODEL}\" ID_USB_INTERFACES=\"%E{ID_USB_INTERFACES}\""

LABEL="wl_end"

udev-blocked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

args="$@"

echo $(date +'%b %d %H:%M:%S') udev-whitelist: blocked device: "$args" >> /var/log/udev-whitelist.log

if [ -x "/usr/bin/notify-send" ]; then
	#find the user for the X display
	# https://stackoverflow.com/a/49533938

	display=":$(ls -1 /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"
	user=$(who | grep '('$display')' | awk '{print $1}')

	if [ -n "$user" ]; then
		uid=$(id -u $user)
		sudo -u $user DISPLAY=$display DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus notify-send --expire-time=8000 "udev whitelist" "blocked device: $args"
	fi
fi

exit 0

usb-gen-whitelist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/bin/bash

rulesonly="n"
wlabel="wl_end"
runcmd='/usr/local/sbin/udev-blocked $attr{idVendor} $attr{idProduct} iface:'\''%E{ID_USB_INTERFACES}'\'''

function helpmsg
{
	echo "Usage: usb-whitelist OPTIONS..."
	echo "  -r, --rules-only      only output rules generated, not the whole file"
	echo "  -l, --label [name]    name of jump label (default \"wl_end\")"
	echo "  -u, --run [cmd]       runs this command when a device is blocked"
	echo "    (must escape '\"'!    default runs /usr/local/sbin/udev-blocked)"
}

POSITIONAL=()
while [[ $# -gt 0 ]]; do
	arg="$1"

	case $arg in
		-h|--help)
			helpmsg
			exit
		;;
		-r|--rules-only)
			rulesonly="y"
			shift
		;;
		-l|--label)
			wlabel="$2"
			shift
			shift
		;;
		-u|--run)
			runcmd="$2"
			shift
			shift
		;;
		*)
			POSITIONAL+=("$1")
			shift
		;;
	esac
done
set -- "${POSITIONAL[@]}"

function usb_getattr
{
	local input=$1
	local attr=$2

	#echo "$input" | egrep -m 1 "ATTR{$attr}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
	echo "$input" | sed -E -n 's/^[[:space:]]*(ATTR\{'"$attr"'\}=="[^"]*")/\1/p'
}

function usb_getenv
{
	local input=$1
	local name=$2

	echo "$input" | sed -E -n 's/^E: '"$name"'=(.*)$/ENV{'"$name"'}=="\1"/p'
}

function combinelist
{
	local arr=("$@")

	local i=0
	local len=${#arr[@]}

	for ((i = 0; i < $len; i++)); do
		if [ -z "${arr[$i]}" ]; then
			continue
		fi

		echo -n "${arr[$i]}"
		if ! [ $i -eq $(($len - 1)) ]; then
			echo -n ", "
		fi
	done
}

if [ "$rulesonly" = "n" ]; then
	echo '# udev usb whitelist rules

# skip
SUBSYSTEM!="usb", GOTO="'$wlabel'"
ACTION!="add", GOTO="'$wlabel'"

# skip hubs
ATTR{bDeviceClass}=="09", GOTO="'$wlabel'"

#===========
# whitelist
#===========
'
fi

usbdevices=$(lsusb | sed -E -n 's/^Bus ([0-9]{3}) Device ([0-9]{3}):.*$/\1\/\2/p')

while read -r dev; do
	wlist=()
	devpath="/dev/bus/usb/$dev"

	attributes=$(udevadm info -a $devpath)

	wlist+=( "$(usb_getattr "$attributes" "idVendor")" )
	wlist+=( "$(usb_getattr "$attributes" "idProduct")" )
	wlist+=( "$(usb_getattr "$attributes" "bcdDevice")" )
	wlist+=( "$(usb_getattr "$attributes" "configuration")" )
	wlist+=( "$(usb_getattr "$attributes" "product")" )

	evalues=$(udevadm info $devpath)

	wlist+=( "$(usb_getenv "$evalues" "ID_USB_INTERFACES")" )

	line=$(combinelist "${wlist[@]}")
	line=$line', GOTO="'$wlabel'"'

	line=$(echo "$line" | sed -E 's/ATTR(\{[A-Za-z]+\})/ATTRS\1/g')

	echo $line
done <<< "$usbdevices"

if [ "$rulesonly" = "n" ]; then
	echo '
#===============
# end whitelist
#===============

# block any device not whitelisted
ENV{DEVTYPE}=="usb_device", ENV{UDISKS_IGNORE}="1", ATTR{authorized}:="0", RUN+="'$runcmd'"

LABEL="'$wlabel'"
'
fi