• CVE-2017-17558漏洞学习


    简介

    这是USB core中的一个拒绝服务漏洞。带有精心设计的描述符的恶意USB设备可以通过在配置描述符中设置过高的bNumInterfaces值来导致内核访问未分配的内存。虽然在解析期间调整了该值,但是在其中一个错误返回路径中跳过了该调整。该漏洞出现在4.15之前

    补丁分析

    看一下补丁怎么写的:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=48a4ff1c7bb5a32d2e396b03132d20d552c0eca7

    diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
    index 55b198b..78e92d2 100644
    --- a/drivers/usb/core/config.c
    +++ b/drivers/usb/core/config.c
    @@ -555,6 +555,9 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
         unsigned iad_num = 0;
     
         memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
    +    nintf = nintf_orig = config->desc.bNumInterfaces;
    +    config->desc.bNumInterfaces = 0;    // Adjusted later
    +
         if (config->desc.bDescriptorType != USB_DT_CONFIG ||
             config->desc.bLength < USB_DT_CONFIG_SIZE ||
             config->desc.bLength > size) {
    @@ -568,7 +571,6 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
         buffer += config->desc.bLength;
         size -= config->desc.bLength;
     
    -    nintf = nintf_orig = config->desc.bNumInterfaces;
         if (nintf > USB_MAXINTERFACES) {
             dev_warn(ddev, "config %d has too many interfaces: %d, "
                 "using maximum allowed: %d
    ",

    漏洞出在usb_parse_configuration函数中,而usb_parse_configuration是驱动中解析设备配置的函数。补丁将下面这一行提前了几行

    nintf = nintf_orig = config->desc.bNumInterfaces;

    然后再讲config->desc.bNumInterfaces置为0了

    下面,先介绍USB的驱动的整体架构,然后给出一个调usb_parse_configuration的例子,解析usb_parse_configuration函数

    USB驱动架构

    四个重要的描述符

    为了方便USB设备驱动的编写,USB核心将USB设备抽象成4个层次,自顶向下依次是

    1、设备:代表整个设备,一个设备会有相对应一个文件等

    2、配置:一个设备有一个或是多个配置,不同的配置使设备表现出不同的功能组合,在连接期间必须从中选择一个。比如说带话筒的耳机可以可以有3个配置,让硬件有3中工作模式:耳机工作、话筒工作、耳机和话筒同时工作。

    3、接口:一个配置包含多个接口。比如一个USB音响可以有音频接口和开关按钮的接口,一个配置可以使所有的接口同时有效,并且被不同的驱动同时连接

    4、端点:一个接口可以有多个端点。端点是主机与硬件设备通信的最基本形式,每个端点有属于自己的地址,并且数据的传输是单向的,每一个USB设备在主机看来就是多个端点的集合

    在include/linux/usb/ch9.h中,定义了几个结构体:usb_device_descriptor、usb_config_descriptor 、usb_interface_descriptor、usb_endpoint_descriptor。分别表示上面这些含义的描述符。每个设备均会分配一个或是多个。

    看一个usb_config_descriptor函数的内容,总而言之就是和配置相关的信息:

    struct usb_config_descriptor {
        __u8  bLength;//描述符的长度,值为USB_DT_CONFIG_SIZE=9
        __u8  bDescriptorType;//描述符类型
    
        __le16 wTotalLength;//向设备请求配置描述时获得数据包的长度
        __u8  bNumInterfaces;//接口数
        __u8  bConfigurationValue;//用这个值表示将要激活的配置
        __u8  iConfiguration;//配置描述信息的字符串描述符的索引值
        __u8  bmAttributes;//一些属性
        __u8  bMaxPower;//最大的电流
    } __attribute__ ((packed));

    而usb_parse_configuration函数中参数之一是usb_host_config结构,定义如下,可以知道主要的结构就是上面的usb_config_descriptor内容

    struct usb_host_config {
        struct usb_config_descriptor    desc;
    
        char *string;        /* iConfiguration string, if present */
    
        /* List of any Interface Association Descriptors in this
         * configuration. */
        struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];
    
        /* the interfaces associated with this configuration,
         * stored in no particular order */
        struct usb_interface *interface[USB_MAXINTERFACES];
    
        /* Interface information available even when this is not the
         * active configuration */
        struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
    
        unsigned char *extra;   /* Extra descriptors */
        int extralen;
    };

    USB设备中的几个重要概念

    USB驱动整体的架构如下

    在硬件层面,USB设备以主机控制器为根形成一颗树,主机控制器连接在PCI总线上作为一个PCI设备,而连接实际物理设备的节点又叫Hub

    USB设备状态

    当一个USB设备插入接口,USB设备需要经过几个步骤才能正常开始运作,比如说要让主机和设备相互认识一下,主机给设备分配个地址什么的。在USB协议中,定义了几个状态来描述主机和设备的交互的状态:

    • Attached:设备连接上Hub,表示初始状态
    • Powered:,连接上设备之后,加电
    • Default:加电后,设备收到复位信号,才能使用默认的地址回应主机发送过来的设备和配置描述符的请求
    • Address:主机分配了一个唯一地址给设备
    • Configured:表示设备以及被主机给配置过了
    • Suspended:为了省电,设备可以被挂起

    usb_parse_configuration函数解读

    usb_parse_configuration完全由usb_get_configuration调用,从名字就可以看,usb_get_configuration就是获取USB设备的配置信息(参考前面USB设备的状态)。当一个设备被插入Hub的时候,内核最终就会调用这个函数,为了保持逻辑完整性,从usb_get_configuration函数开始看起,这个函数的参数只有一个,就是struct usb_device *dev,usb_device结构就是内核中对于一个usb设备的抽象,这个函数就是获取这个设备的配置

    int usb_get_configuration(struct usb_device *dev)
    {
        struct device *ddev = &dev->dev;
        int ncfg = dev->descriptor.bNumConfigurations;
        int result = 0;
        unsigned int cfgno, length;
        unsigned char *bigbuffer;
        struct usb_config_descriptor *desc;
    
        cfgno = 0;
        if (dev->authorized == 0)    /* Not really an error */
            goto out_not_authorized;
        result = -ENOMEM;
        if (ncfg > USB_MAXCONFIG) {
            dev_warn(ddev, "too many configurations: %d, "
                "using maximum allowed: %d
    ", ncfg, USB_MAXCONFIG);
            dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
        }
    
        if (ncfg < 1) {
            dev_err(ddev, "no configurations
    ");
            return -EINVAL;
        }
    
        length = ncfg * sizeof(struct usb_host_config);
        dev->config = kzalloc(length, GFP_KERNEL);
        if (!dev->config)
            goto err2;
    
        length = ncfg * sizeof(char *);
        dev->rawdescriptors = kzalloc(length, GFP_KERNEL);
        if (!dev->rawdescriptors)
            goto err2;
    
        desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
        if (!desc)
            goto err2;
    
        result = 0;
        for (; cfgno < ncfg; cfgno++) {
            /* We grab just the first descriptor so we know how long
             * the whole configuration is */
            result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
                desc, USB_DT_CONFIG_SIZE);
            if (result < 0) {
                dev_err(ddev, "unable to read config index %d "
                    "descriptor/%s: %d
    ", cfgno, "start", result);
                dev_err(ddev, "chopping to %d config(s)
    ", cfgno);
                dev->descriptor.bNumConfigurations = cfgno;
                break;
            } else if (result < 4) {
                dev_err(ddev, "config index %d descriptor too short "
                    "(expected %i, got %i)
    ", cfgno,
                    USB_DT_CONFIG_SIZE, result);
                result = -EINVAL;
                goto err;
            }
            length = max((int) le16_to_cpu(desc->wTotalLength),
                USB_DT_CONFIG_SIZE);
    
            /* Now that we know the length, get the whole thing */
            bigbuffer = kmalloc(length, GFP_KERNEL);
            if (!bigbuffer) {
                result = -ENOMEM;
                goto err;
            }
            result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
                bigbuffer, length);
            if (result < 0) {
                dev_err(ddev, "unable to read config index %d "
                    "descriptor/%s
    ", cfgno, "all");
                kfree(bigbuffer);
                goto err;
            }
            if (result < length) {
                dev_warn(ddev, "config index %d descriptor too short "
                    "(expected %i, got %i)
    ", cfgno, length, result);
                length = result;
            }
    
            dev->rawdescriptors[cfgno] = bigbuffer;
    
            result = usb_parse_configuration(dev, cfgno,
                &dev->config[cfgno], bigbuffer, length);
            if (result < 0) {
                ++cfgno;
                goto err;
            }
        }
        result = 0;
    
    err:
        kfree(desc);
    out_not_authorized:
        dev->descriptor.bNumConfigurations = cfgno;
    err2:
        if (result == -ENOMEM)
            dev_err(ddev, "out of memory
    ");
        return result;
    }

    上面的函数逻辑也比较的简单,不断的获取配置,然后调用usb_parse_configuration去解析,其他部分的实现就不具体看了,这里主要是弄清楚,传递给usb_parse_configuration函数的几个参数的含义,dev指usb设备,cfgno指当前第几个配置,&dev->config[cfgno]指需要解析的配置,后面是buffer和长度

    在usb_parse_configuration就是解析从USB设备读取过来的配置信息,判断数据正确性。

    先看for循环前面的一部分,memcpy函数将buffer里面的值放入描述符内,紧接着的一个if条件判断如果失败,则会造成这个函数的结束。然而这个config->desc里面的值却没有被更改,所以可以利用恶意设备来操纵,造成前面所说的访问无效内存

        memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
        if (config->desc.bDescriptorType != USB_DT_CONFIG ||
            config->desc.bLength < USB_DT_CONFIG_SIZE) {
            dev_err(ddev, "invalid descriptor for config index %d: "
                "type = 0x%X, length = %d
    ", cfgidx,
                config->desc.bDescriptorType, config->desc.bLength);
            return -EINVAL;
        }
        cfgno = config->desc.bConfigurationValue;
    
        buffer += config->desc.bLength;
        size -= config->desc.bLength;
    
        nintf = nintf_orig = config->desc.bNumInterfaces;
        if (nintf > USB_MAXINTERFACES) {
            dev_warn(ddev, "config %d has too many interfaces: %d, "
                "using maximum allowed: %d
    ",
                cfgno, nintf, USB_MAXINTERFACES);
            nintf = USB_MAXINTERFACES;
        }

    所以最后的补丁就是将config->desc.bNumInterfaces先赋值成0,这样在错误退出的路径之上就不会有无效内存的访问出现,并且在最后的大for循环退出之后,会有如下语句将这个值恢复,所以正常运行退出的时候也不会有什么错误:

        config->desc.bNumInterfaces = nintf = n;
  • 相关阅读:
    如何解决移动端滚动穿透问题
    如何在mac中通过命令行使用sublime
    正向代理和反向代理
    UTF8、UTF16、UTF16-LE、UTF16-BE、UTF32都是些什么?
    依赖的版本
    如何移除inline-block元素之间的空白
    如何用JavaScript重定向到另一个网页?
    [读书笔记] 高性能网站建设指南
    java使用jconsole查看java程序运行(jmx原理)
    oracle相关知识点
  • 原文地址:https://www.cnblogs.com/likaiming/p/10863079.html
Copyright © 2020-2023  润新知