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 為 UNCOMPRESSEDMJPEGDVFRAME_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;
    // [...]
}