在4.19.9以前的Linux內核中發現了一個問題。USB子系統在讀取與驅動程序/ USB /core/usb.c中的_usb_get_extra_descriptor相關的額外描述符時錯誤地檢查了大小。linux
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來表示app
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等,就是單純用來區分這些描述符的類型spa
可是除了上述幾種描述符以外,還有一類設備定義的描述符和廠商爲設備特別定義的描述符。在內核中描述設備、接口、配置、端口等信息的時候,使用的是另外的結構,他們以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字段,這就是上面所說的設備定義的描述符和廠商爲設備特別定義的描述符。code
好,接下來看__usb_get_extra_descriptor的實現,這個函數用來在buffer中取出一個特定類型的描述符,地址寫在ptr中。在修改以前,這個函數有4個參數,第一個參數表示描述符數組,第二個參數表示這個buffer中描述符項數,第三個參數爲須要尋找的描述符類型,第四個參數表示最終結果,描述符位置blog
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的狀況,因此最終會形成不正確的數據向上傳遞。