A Quick Note on CVE-2024-53104
CVE-2024-53104 是一個 Linux kernel UVC (USB Video Class) driver 上的 Out-Of-Bound Write 漏洞,並且在該漏洞被揭露以前,似乎已經在 Android 上使用一陣子了。
我在做 Linux kernel 研究時,很少會注意這種類型的攻擊面,主要是這種類型的 driver 並不常見,就算出現漏洞,影響性也不高。然而,在 Android 手機上接上未知的 USB 裝置做攻擊,似乎是一個很常發生的情境,如果漏洞能在偵測裝置時就觸發,甚至不需要使用者互動就能 exploit,確實是個蠻嚴重的問題。
這篇文章會對該漏洞做一些簡單的分析,並且對 USB 裝置與 driver 的互動機制有基本的了解。
1. Introduction
從 USB 裝置(硬體)到 USB Driver(軟體)可以分成四個部分:
+---------------------------------------------+
| USB Driver (USB Core) |
| - USB Host | [4]
| - USB Device Driver (e.g. uvcvideo) |
+---------------------------------------------+
| USB HCD (Host Controller Driver) |
| - EHCI(USB 2.0) |
| - OHCI(USB 1.1) | [3]
| - UHCI(USB 1.1) |
| - XHCI(USB 3.0/3.1/3.2) |
+---------------------------------------------+
| Host Controller (Hardware) |
| - Intel USB Controller | [2]
| - AMD USB Controller |
| - Other USB Host hardware |
+---------------------------------------------+
|
|
|
USB device [1]
插入 USB [1] 後會先由電腦上的 USB Controller 硬體裝置 [2] 負責處理,之後發送 hardirq 給對應的 HCD (Host Controller Driver) [3],並將 probe 請求傳給 USB device / interface 的 driver,最後觸發 interface 的 probe handler [4]。
2. Patch
Patch 位於 UVC driver probing 過程中呼叫的 function uvc_parse_format()
,讓 ftype
為 0 的請求不會執行後續的解析。
@@ -371,7 +371,7 @@ static int uvc_parse_format(struct uvc_device *dev,
* Parse the frame descriptors. Only uncompressed, MJPEG and frame
* based formats have frame descriptors.
*/
- while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+ while (ftype && buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
buffer[2] == ftype) {
unsigned int maxIntervalIndex;
也就是說,在 uvc_parse_format()
解析 USB 裝置傳送的資料時,會根據不同的類型決定處理資料的方式,但是因為 if condition 沒寫好,導致 type 與 type handler 不相符,最終導致越界寫入。
3. Analyze
3.1. Registration
UVC driver 會在初始化時呼叫 usb_register()
註冊一個 USB device driver,而 UVC 本身的 probe handler 則是 uvc_probe()
[2]。
struct uvc_driver uvc_driver = {
.driver = { // struct usb_driver
.name = "uvcvideo",
.probe = uvc_probe, // [2]
// [...]
},
}
struct usb_driver {
// [...]
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
// [...]
struct device_driver driver;
// [...]
};
static int __init uvc_init(void)
{
int ret;
ret = usb_register(&uvc_driver.driver); // [1]
// [...]
}
不同種類的 USB driver 都可以呼叫 usb_register()
來註冊 handler 到 USB bus 上 [2]。
#define usb_register(driver) \
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
// [...]
new_driver->driver.bus = &usb_bus_type; // [2]
// [...]
new_driver->driver.probe = usb_probe_interface;
// [...]
retval = driver_register(&new_driver->driver);
// [...]
}
int driver_register(struct device_driver *drv)
{
// [...]
ret = bus_add_driver(drv);
// [...]
}
3.2. USB Device Probing
插入裝置時會產生 interrupt,間接觸發 driver probing 的機制。過程中會呼叫 USB device driver 的 general probe handler usb_probe_interface()
找對應的 interface [1],接著由 USB device deriver 自己的 probe handler 來處理 [2]。
static int driver_probe_device(struct device_driver *drv, struct device *dev)
{
// [...]
ret = __driver_probe_device(drv, dev); // <-----------------
// [...]
}
static int __driver_probe_device(struct device_driver *drv, struct device *dev)
{
// [...]
ret = really_probe(dev, drv); // <-----------------
// [...]
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
// [...]
ret = call_driver_probe(dev, drv); // <-----------------
// [...]
}
static int call_driver_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
// [...]
else if (drv->probe)
ret = drv->probe(dev); // [1] `usb_probe_interface()`
// [...]
}
static int usb_probe_interface(struct device *dev)
{
struct usb_driver *driver = to_usb_driver(dev->driver);
struct usb_interface *intf = to_usb_interface(dev);
// [...]
id = usb_match_dynamic_id(intf, driver);
// [...]
error = driver->probe(intf, id); // [2] `uvc_probe()`
// [...]
}
UVC 的 probe handler 是 uvc_probe()
,會解析 extra data [3] 來初始化 UVC device。Extra data 會是由多組 descriptor 所組成,會由 function uvc_parse_standard_control()
[4] 來解析。
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct uvc_device *dev;
// [...]
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
// [...]
dev->intf = usb_get_intf(intf);
// [...]
uvc_parse_control(dev); // <-----------------
// [...]
}
static int uvc_parse_control(struct uvc_device *dev)
{
struct usb_host_interface *alts = dev->intf->cur_altsetting;
const unsigned char *buffer = alts->extra; // [3]
int buflen = alts->extralen;
int ret;
while (buflen > 2) {
// [...]
// 1. Check the vendor information of the interface
if (uvc_parse_vendor_control(dev, buffer, buflen) ||
buffer[1] != USB_DT_CS_INTERFACE)
goto next_descriptor;
// [...]
// 2. Parse the data
ret = uvc_parse_standard_control(dev, buffer, buflen); // [4]
// 3. Take next descriptor
next_descriptor:
buflen -= buffer[0];
buffer += buffer[0];
}
}
Descriptor 有許多種類型,如果是 UVC_VC_HEADER
[5] 的話,會呼叫 function uvc_parse_streaming()
來處理 [6]。
/* A.5. Video Class-Specific VC Interface Descriptor Subtypes */
#define UVC_VC_DESCRIPTOR_UNDEFINED 0x00
#define UVC_VC_HEADER 0x01
// [...]
#define UVC_VC_EXTENSION_UNIT 0x06
static int uvc_parse_standard_control(struct uvc_device *dev,
const unsigned char *buffer, int buflen)
{
// [...]
switch (buffer[2]) { // [5]
// [...]
case UVC_VC_HEADER:
n = buflen >= 12 ? buffer[11] : 0;
dev->uvc_version = get_unaligned_le16(&buffer[3]);
dev->clock_frequency = get_unaligned_le32(&buffer[7]);
for (i = 0; i < n; ++i) {
intf = usb_ifnum_to_if(udev, buffer[12+i]);
uvc_parse_streaming(dev, intf); // [6]
}
break;
}
// [...]
}
Function uvc_parse_streaming()
會初始化 interface (USB_DT_CS_INTERFACE
) [7],並且 interface 也可以分成不同種。假設 interface 為 UNCOMPRESSED
、MJPEG
、DV
或 FRAME_BASED
這四種其中一種,就會呼叫 uvc_parse_format()
[8] 來做 interface 的初始化。
/* A.6. Video Class-Specific VS Interface Descriptor Subtypes */
// [...]
#define UVC_VS_FORMAT_UNCOMPRESSED 0x04
#define UVC_VS_FORMAT_MJPEG 0x06
#define UVC_VS_FORMAT_DV 0x0c
#define UVC_VS_FORMAT_FRAME_BASED 0x10
static int uvc_parse_streaming(struct uvc_device *dev,
struct usb_interface *intf)
{
// [...]
while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE) { // [7]
switch (buffer[2]) {
case UVC_VS_FORMAT_UNCOMPRESSED:
case UVC_VS_FORMAT_MJPEG:
case UVC_VS_FORMAT_DV:
case UVC_VS_FORMAT_FRAME_BASED:
ret = uvc_parse_format(dev, streaming, format, frame, // [8]
&interval, buffer, buflen);
// [...]
continue;
default:
break;
}
buflen -= buffer[0];
buffer += buffer[0];
}
// [...]
}
不同的 interface type 有不同的解析方式,有些 interface 還會有更下層的 frame descriptor 需要解析。然而,其中一種 interface type UVC_VS_FORMAT_DV
雖然沒有 frame descriptor,但因為 if condition 沒有寫好 [9],導致其資料會以有 frame descriptor 的方式解析,間接造成 OOB Write。
static int uvc_parse_format(struct uvc_device *dev,
struct uvc_streaming *streaming, struct uvc_format *format,
struct uvc_frame *frames, u32 **intervals, const unsigned char *buffer,
int buflen)
{
u8 ftype;
switch (buffer[2]) {
case UVC_VS_FORMAT_UNCOMPRESSED:
case UVC_VS_FORMAT_FRAME_BASED:
// [...]
if (buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED) {
ftype = UVC_VS_FRAME_UNCOMPRESSED;
} else {
ftype = UVC_VS_FRAME_FRAME_BASED;
}
break;
case UVC_VS_FORMAT_MJPEG:
// [...]
ftype = UVC_VS_FRAME_MJPEG;
break;
case UVC_VS_FORMAT_DV:
// [...]
ftype = 0; // UVC_VS_UNDEFINED
break;
}
// [...]
/*
* Parse the frame descriptors. Only uncompressed, MJPEG and frame
* based formats have frame descriptors.
*/
while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE && // [9]
buffer[2] == ftype) {
// ... parsing frame descriptors
}
}
3.3. Configuration (Extra) Data
不過 extra data 是怎麼來的?
Host Controller Driver 在發現有新的 USB Controller 時,會嘗試遍歷底下的 USB 裝置,並把從 USB 讀取的設定資料 (descriptor) 儲存起來 [1]。
int usb_add_hcd(struct usb_hcd *hcd,
unsigned int irqnum, unsigned long irqflags)
{
// [...]
retval = register_root_hub(hcd); // <-----------------
// [...]
}
static int register_root_hub(struct usb_hcd *hcd)
{
struct usb_device *usb_dev = hcd->self.root_hub;
// [...]
retval = usb_new_device (usb_dev); // <-----------------
}
int usb_new_device(struct usb_device *udev)
{
// [...]
err = usb_enumerate_device(udev); // <-----------------
// [...]
}
static int usb_enumerate_device(struct usb_device *udev)
{
int err;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
if (udev->config == NULL) {
err = usb_get_configuration(udev); // <-----------------
}
// [...]
}
int usb_get_configuration(struct usb_device *dev)
{
desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
for (cfgno = 0; cfgno < ncfg; cfgno++) {
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, // [2] <----------------- read data from USB device
desc, USB_DT_CONFIG_SIZE);
// [...]
length = max((int) le16_to_cpu(desc->wTotalLength),
USB_DT_CONFIG_SIZE);
bigbuffer = kmalloc(length, GFP_KERNEL);
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, // [3] <----------------- read data from USB device
bigbuffer, length);
// [...]
result = usb_parse_configuration(dev, cfgno, // [1]
&dev->config[cfgno], bigbuffer, length);
}
}
Function usb_get_descriptor()
會透過跟 USB device 拿 descriptor。第一次 [2] 只會拿 CONFIG_SIZE
的資料,內容為 configuration 的 metadata,像是所有 configuration 的資料量 (desc->wTotalLength
)。而第二次讀取 [3],就會把所有 configuration data 讀出來。
該 function 的實作方式,就是在 software layer (kernel) 建立一個 URB (USB Request Block) [4],之後再透過 host controller driver 發送出去 [5]。
int usb_get_descriptor(struct usb_device *dev, unsigned char type,
unsigned char index, void *buf, int size)
{
int i;
int result;
// [...]
result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), // <-----------------
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
(type << 8) + index, 0, buf, size,
USB_CTRL_GET_TIMEOUT);
// [...]
}
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)
{
struct usb_ctrlrequest *dr;
int ret;
dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO);
dr->bRequestType = requesttype;
dr->bRequest = request;
dr->wValue = cpu_to_le16(value);
dr->wIndex = cpu_to_le16(index);
dr->wLength = cpu_to_le16(size);
ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); // <-----------------
kfree(dr);
return ret;
}
static int usb_internal_control_msg(struct usb_device *usb_dev,
unsigned int pipe,
struct usb_ctrlrequest *cmd,
void *data, int len, int timeout)
{
struct urb *urb;
int retv;
int length;
// [...]
urb = usb_alloc_urb(0, GFP_NOIO); // [4]
usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
len, usb_api_blocking_completion, NULL);
retv = usb_start_wait_urb(urb, timeout, &length); // [5]
// [...]
}
Function usb_parse_configuration()
會根據 USB 傳的 descriptor,呼叫 usb_parse_interface()
來分配好每個 USB interface 的資料。
static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
struct usb_host_config *config, unsigned char *buffer, int size)
{
config->desc.bNumInterfaces = nintf = n;
// [...]
while (size > 0) {
retval = usb_parse_interface(ddev, cfgno, config, // [6]
buffer, size, inums, nalts);
buffer += retval;
size -= retval;
}
// [...]
}
USB interface 的 configuration data 只會先存到 extra buffer 內 [7],後續再由 USB device driver 自己的 probe handler 來解析。
static int usb_parse_interface(struct device *ddev, int cfgno,
struct usb_host_config *config, unsigned char *buffer, int size,
u8 inums[], u8 nalts[])
{
struct usb_host_interface *alt;
// 挑 interface
for (i = 0; i < config->desc.bNumInterfaces /* usb interface */; ++i) {
if (inums[i] == inum) {
intfc = config->intf_cache[i];
break;
}
}
for ((i = 0, alt = &intfc->altsetting[0]); i < intfc->num_altsetting; (++i, ++alt)) {
if (alt->desc.bAlternateSetting == asnum) {
// check duplicate
goto skip_to_next_interface_descriptor;
}
}
++intfc->num_altsetting;
// [...]
alt->extra = buffer; // [7]
// [...]
alt->extralen = i;
// [...]
}