CVE-2018-19985漏洞学习

简介

4.19.8之前,在Linux内核中,hso_probe()函数中发现了一个缺陷,该函数从USB设备(作为u8)读取if_num值,并且不需要对数组进行长度检查就使用它来索引数组,从而导致在hso_probe()或hso_get_config_data()中读取OOB内存。攻击者使用伪造的USB设备和对系统的物理访问(需要连接这样的设备)可以导致系统崩溃和拒绝服务。

补丁分析

补丁在这里:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5146f95df782b0ac61abde36567e718692725c89

diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index 184c24b..d6916f7 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -2807,6 +2807,12 @@ static int hso_get_config_data(struct usb_interface *interface)
         return -EIO;
     }
 
+    /* check if we have a valid interface */
+    if (if_num > 16) {
+        kfree(config_data);
+        return -EINVAL;
+    }
+
     switch (config_data[if_num]) {
     case 0x0:
         result = 0;
@@ -2877,10 +2883,18 @@ static int hso_probe(struct usb_interface *interface,
 
     /* Get the interface/port specification from either driver_info or from
      * the device itself */
-    if (id->driver_info)
+    if (id->driver_info) {
+        /* if_num is controlled by the device, driver_info is a 0 terminated
+         * array. Make sure, the access is in bounds! */
+        for (i = 0; i <= if_num; ++i)
+            if (((u32 *)(id->driver_info))[i] == 0)
+                goto exit;
         port_spec = ((u32 *)(id->driver_info))[if_num];
-    else
+    } else {
         port_spec = hso_get_config_data(interface);
+        if (port_spec < 0)
+            goto exit;
+    }
 
     /* Check if we need to switch to alt interfaces prior to port
      * configuration */

确定问题出在drivers/net/usb/hso.c,分析一下这个文件

源码分析

文件最后,有模块的一些操作,文件开头有一些注释,得知这是文件是一个驱动的实现,Option High Speed Mobile Devices,还有一个专门的编译选项CONFIG_USB_HSO

module_init(hso_init);
module_exit(hso_exit);

MODULE_AUTHOR(MOD_AUTHOR);
MODULE_DESCRIPTION(MOD_DESCRIPTION);
MODULE_LICENSE(MOD_LICENSE);

初始化函数是hso_init,卸载函数是hso_exit,卸载函数比较简单,只是注销了tty和usb的驱动,同样在hso_init函数中,也注册了这两个驱动。

static void __exit hso_exit(void)
{
    printk(KERN_INFO "hso: unloaded\n");

    tty_unregister_driver(tty_drv);
    /* deregister the usb driver */
    usb_deregister(&hso_driver);
}

这应该是一个连在USB总线上的串口,下层使用USB来和硬件交流,然后将USB获得的信息传递给tty层。从文件中定义的tty_operations这些个函数看下去,最终实现都是USB这边的实现,usb_submit_urb、usb_control_msg函数

static const struct tty_operations hso_serial_ops = {
    .open = hso_serial_open,
    .close = hso_serial_close,
    .write = hso_serial_write,
    .write_room = hso_serial_write_room,
    .ioctl = hso_serial_ioctl,
    .set_termios = hso_serial_set_termios,
    .chars_in_buffer = hso_serial_chars_in_buffer,
    .tiocmget = hso_serial_tiocmget,
    .tiocmset = hso_serial_tiocmset,
    .get_icount = hso_get_count,
    .unthrottle = hso_unthrottle
};

hso_probe函数是定义的usb驱动的probe函数,就是USB设备插上之后,USB驱动第一个被调用的函数,在这个函数中应该要做一些设备的初始化功能

static struct usb_driver hso_driver = {
    .name = driver_name,
    .probe = hso_probe,
    .disconnect = hso_disconnect,
    .id_table = hso_ids,
    .suspend = hso_suspend,
    .resume = hso_resume,
    .reset_resume = hso_resume,
    .supports_autosuspend = 1,
};

补丁的修改位置就在hso_probe的开头,问题出在if_num处

    if_num = interface->altsetting->desc.bInterfaceNumber;

    /* Get the interface/port specification from either driver_info or from
     * the device itself */
    if (id->driver_info)
        port_spec = ((u32 *)(id->driver_info))[if_num];
    else
        port_spec = hso_get_config_data(interface);

    if (interface->cur_altsetting->desc.bInterfaceClass != 0xFF) {
        dev_err(&interface->dev, "Not our interface\n");
        return -ENODEV;
    }

altsetting,这个值表示可选的设置,bInterfaceNumber表示该配置的接口号。这些值都是USB枚举阶段从外部设备读入的值,也就是if_num这个值是可以被恶意的外部设备所控制

在port_spec = ((u32 *)(id->driver_info))[if_num];这句话,当id->driver_info==0的时候if_num的超过会造成越界访问,不过好像看了下hso_ids中的值,会有等于0的情况吗?

然后是hso_get_config_data函数,开头同样有这样的一段和上述代码类似

    struct usb_device *usbdev = interface_to_usbdev(interface);
    u8 config_data[17];
    u32 if_num = interface->altsetting->desc.bInterfaceNumber;
    s32 result;

    if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
                0x86, 0xC0, 0, 0, config_data, 17,
                USB_CTRL_SET_TIMEOUT) != 0x11) {
        return -EIO;
    }

    switch (config_data[if_num]) {

同样是对于interface->altsetting->desc.bInterfaceNumber这个值的数据访问。所以加上一个越界判断

 

posted @ 2019-05-19 14:52  番茄汁汁  阅读(381)  评论(0编辑  收藏  举报