Introducción
En versiones del kernel de Linux anteriores a la 4.19.9, se identificó un fallo dentro del subsistema USB. El problema radicaba en la validación incorrecta del tamaño al leer descriptores adicionales asociados a la función __usb_get_extra_descriptor en el archivo drivers/usb/core/usb.c.
Análisis del parche
El commit de corrección se puede encontrar aquí: 704620afc70cf47abb9d6a1a57f3825d2bca49cf.
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)))
Las modificaciones abarcaron cuatro archivos, centrándose todas en la función __usb_get_extra_descriptor. El parche introdujo un parámetro adicional minsize a la firma de la función y añadió una comprobación en su lógica. Ahora, para que la función retorne éxito (0), el campo bLength del descriptor debe ser mayor o igual a este nuevo parámetro minsize.
Análisis del código fuente
Los cinco tipos de descriptores en el estándar USB comparten dos campos comunes en su cabecera, que indican la longitud y el tipo del descriptor. Están representados por la estructura 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));
// Otras definiciones como usb_config_descriptor, usb_interface_descriptor, etc.
En el archivo include/linux/usb/ch9.h se encuentran estas definiciones y los valores posibles para los campos. Para bLength, existen macros como USB_DT_CONFIG_SIZE que definen su tamaño. Para bDescriptorType, los valores son USB_DT_DEVICE, USB_DT_CONFIG, etc., usados para identificar el tipo.
Adicionalmente, existen descriptores definidos por el dispositivo o por el fabricante. En las estructuras internas del kernel para representar endpoints, interfaces, etc. (con prefijo usb_host_), estos descriptores extra se almacenan en campos específicos.
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc;
struct usb_ss_ep_comp_descriptor ss_ep_comp;
struct list_head búfer_list;
void *hcpriv;
struct ep_device *ep_dev; /* Para información sysfs */
unsigned char *extra; /* Descriptores extra */
int extralen;
int enabled;
};
La función __usb_get_extra_descriptor tiene como objetivo buscar un descriptor de un tipo específico dentro de un búfer de datos. Su implementación original iteraba sobre los descriptores almacenados, verificando el tipo y avanzando punteros según la longitud indicada en cada cabecera.
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;
}
La función era invocada, por ejemplo, en usb_enumerate_device_otg para obtener el descriptor OTG, y en hwahc_security_create para obtener el decsriptor de seguridad. Ambas funciones proporcionaban el búfer rawdescriptors y la longitud total de la configuración (wTotalLength).
Aálisis de la vulnerabilidad
El núcleo de la vulnerabilidad reside en la falta de validación del campo bLength. El código original solo verificaba que bLength no fuera menor que 2, pero no comprobaba que no excediera el tamaño restante del búfer (size). Un descriptor malicioso con un bLength excesivamente grande podría causar que la función retornara información fuera de los límites del búfer, llevando a una condición de lectura fuera de rango (out-of-bounds read) y potencialmente a una divulgación de informaicón o a un comportamiento inestable.