CVE-2018-20169漏洞学习
简介
在4.19.9之前的Linux内核中发现了一个问题。USB子系统在读取与驱动程序/ USB /core/usb.c中的_usb_get_extra_descriptor相关的额外描述符时错误地检查了大小。
补丁分析
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5286640..f76b2e0a 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2251,7 +2251,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev) /* descriptor may appear anywhere in config */ err = __usb_get_extra_descriptor(udev->rawdescriptors[0], le16_to_cpu(udev->config[0].desc.wTotalLength), - USB_DT_OTG, (void **) &desc); + USB_DT_OTG, (void **) &desc, sizeof(*desc)); if (err || !(desc->bmAttributes & USB_OTG_HNP)) return 0; diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 79d8bd7..4ebfbd7 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -832,14 +832,14 @@ EXPORT_SYMBOL_GPL(usb_get_current_frame_number); */ int __usb_get_extra_descriptor(char *buffer, unsigned size, - unsigned char type, void **ptr) + unsigned char type, void **ptr, size_t minsize) { struct usb_descriptor_header *header; while (size >= sizeof(struct usb_descriptor_header)) { header = (struct usb_descriptor_header *)buffer; - if (header->bLength < 2) { + if (header->bLength < 2 || header->bLength > size) { printk(KERN_ERR "%s: bogus descriptor, type %d length %d\n", usbcore_name, @@ -848,7 +848,7 @@ int __usb_get_extra_descriptor(char *buffer, unsigned size, return -1; } - if (header->bDescriptorType == type) { + if (header->bDescriptorType == type && header->bLength >= minsize) { *ptr = header; return 0; } diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 684d6f0..09a8ebd 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -640,7 +640,7 @@ static int hwahc_security_create(struct hwahc *hwahc) top = itr + itr_size; result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), - USB_DT_SECURITY, (void **) &secd); + USB_DT_SECURITY, (void **) &secd, sizeof(*secd)); if (result == -1) { dev_warn(dev, "BUG? WUSB host has no security descriptors\n"); return 0; diff --git a/include/linux/usb.h b/include/linux/usb.h index 4cdd515..5e49e82 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -407,11 +407,11 @@ struct usb_host_bos { }; int __usb_get_extra_descriptor(char *buffer, unsigned size, - unsigned char type, void **ptr); + unsigned char type, void **ptr, size_t min); #define usb_get_extra_descriptor(ifpoint, type, ptr) \ __usb_get_extra_descriptor((ifpoint)->extra, \ (ifpoint)->extralen, \ - type, (void **)ptr) + type, (void **)ptr, sizeof(**(ptr)))
总共修改了四个文件,但是修改都围绕着__usb_get_extra_descriptor这个函数,包括这个函数的定义以及引用这个函数的位置。补丁中位这个函数增加了一个参数minsize,然后在__usb_get_extra_descriptor的逻辑中增加了判断,在__usb_get_extra_descriptor正常退出也就是返回0的逻辑之中,让bLength必须大于minsize
源码分析
USB中的5种描述符中,都有共同的两个字段,这个两个字段放在描述符的头部,表示描述符长度,描述符类型编号,用usb_descriptor_header来表示
struct usb_descriptor_header { __u8 bLength; __u8 bDescriptorType; } __attribute__ ((packed)); struct usb_device_descriptor { __u8 bLength; __u8 bDescriptorType; __le16 bcdUSB; __u8 bDeviceClass; __u8 bDeviceSubClass; __u8 bDeviceProtocol; __u8 bMaxPacketSize0; __le16 idVendor; __le16 idProduct; __le16 bcdDevice; __u8 iManufacturer; __u8 iProduct; __u8 iSerialNumber; __u8 bNumConfigurations; } __attribute__ ((packed)); struct usb_config_descriptor { __u8 bLength; __u8 bDescriptorType; __le16 wTotalLength; __u8 bNumInterfaces; __u8 bConfigurationValue; __u8 iConfiguration; __u8 bmAttributes; __u8 bMaxPower; } __attribute__ ((packed)); struct usb_string_descriptor { __u8 bLength; __u8 bDescriptorType; __le16 wData[1]; /* UTF-16LE encoded */ } __attribute__ ((packed)); struct usb_interface_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bInterfaceNumber; __u8 bAlternateSetting; __u8 bNumEndpoints; __u8 bInterfaceClass; __u8 bInterfaceSubClass; __u8 bInterfaceProtocol; __u8 iInterface; } __attribute__ ((packed)); struct usb_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; __u8 bmAttributes; __le16 wMaxPacketSize; __u8 bInterval; /* NOTE: these two are _only_ in audio endpoints. */ /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */ __u8 bRefresh; __u8 bSynchAddress; } __attribute__ ((packed));
在include\linux\usb\ch9.h中,可以找到上述这些描述符的定义,然后在该文件中,同样可以找到这些描述符中各个字段的取值,我们看前两个字段bLength和bDescriptorType。
有USB_DT_CONFIG_SIZE和USB_DT_DEVICE_SIZE等这些宏,这就是bLength的取值,表示这个描述符中占多少个字节,__u8就表示一个字节,__lel16就表示两个字节。
bDescriptorType同样,取值是USB_DT_DEVICE,USB_DT_CONFIG等,就是单纯用来区分这些描述符的类型
但是除了上述几种描述符之外,还有一类设备定义的描述符和厂商为设备特别定义的描述符。在内核中描述设备、接口、配置、端口等信息的时候,使用的是另外的结构,他们以usb_host_开头,看include\linux\usb.h中关于端口的描述,描述符被存放在第一个字段
struct usb_host_endpoint { struct usb_endpoint_descriptor desc; struct usb_ss_ep_comp_descriptor ss_ep_comp; struct list_head urb_list; void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ unsigned char *extra; /* Extra descriptors */ int extralen; int enabled; };
结构体中,还会有extra字段,这就是上面所说的设备定义的描述符和厂商为设备特别定义的描述符。
好,接下来看__usb_get_extra_descriptor的实现,这个函数用来在buffer中取出一个特定类型的描述符,地址写在ptr中。在修改之前,这个函数有4个参数,第一个参数表示描述符数组,第二个参数表示这个buffer中描述符项数,第三个参数为需要寻找的描述符类型,第四个参数表示最终结果,描述符位置
int __usb_get_extra_descriptor(char *buffer, unsigned size, unsigned char type, void **ptr) { struct usb_descriptor_header *header; while (size >= sizeof(struct usb_descriptor_header)) { header = (struct usb_descriptor_header *)buffer; if (header->bLength < 2) { printk(KERN_ERR "%s: bogus descriptor, type %d length %d\n", usbcore_name, header->bDescriptorType, header->bLength); return -1; } if (header->bDescriptorType == type) { *ptr = header; return 0; } buffer += header->bLength; size -= header->bLength; } return -1; } EXPORT_SYMBOL_GPL(__usb_get_extra_descriptor);
所以上面这个函数的逻辑也比较清楚了,从buffer中不断遍历,直到找到需要类型的描述符为止。
接下来看调用这个函数的位置,也是上述补丁中调用__usb_get_extra_descriptor的两个函数:
usb_enumerate_device_otg中,需要从rawdescriptors中取出usb_otg_descriptor。rawdescriptors是字符指针数组,在USB枚举阶段,主机使用GET_DESCRIPTOR请求去获得配置描述符所得到的结果。所有的配置描述符都放着这里,这个函数中需要取出USB_DT_OTG,类型的描述符,OTG是电源管理相关的配置。从这个函数也可以看得出来。config表示所有的配置描述符,wTotalLength表示USB枚举阶段从设备默认端口获得的配置描述信息的长度
__usb_get_extra_descriptor (udev->rawdescriptors[0], le16_to_cpu(udev->config[0].desc.wTotalLength), USB_DT_OTG, (void **) &desc) == 0)
hwahc_security_create中同样的用法,actconfig表示的是当前正在使用的配置描述符,其他同上。
result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), USB_DT_SECURITY, (void **) &secd);
漏洞分析
所以该漏洞的核心点在于bLength的值,补丁在bLength异常时会返回-1,有判断bLength小于2的情况,但是没有判断bLength大于size的情况,所以最终会造成不正确的数据向上传递。