
CVE-2022-20009 - Descripción y exploit de muestra para Android USB Gadgets
Ciertas revisiones del kernel de Linux utilizadas en Android se ven afectadas por el problema CVE-2021-39685. Se puede encontrar información detallada y un PoC para CVE-2021-39685 en el repositorio inspector-gadget.
Este repositorio incluye un PoC dirigido a varios gadgets USB específicos de Android, incluyendo:
a) f_accessory
int acc_ctrlrequest(struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
struct acc_dev *dev = get_acc_dev();
int value = -EOPNOTSUPP;
struct acc_hid_dev *hid;
int offset;
u8 b_requestType = ctrl->bRequestType;
u8 b_request = ctrl->bRequest;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
unsigned long flags;
...
if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) {
...
} else if (b_request == ACCESSORY_SEND_STRING) {
dev_info(&cdev->gadget->dev, "%s: got ACCESSORY_SEND_STRING(52) request\n",
__func__);
schedule_work(&dev->sendstring_work);
dev->string_index = w_index;
cdev->gadget->ep0->driver_data = dev;
cdev->req->complete = acc_complete_set_string;
value = w_length;
...
} else if (b_request == ACCESSORY_SEND_HID_EVENT) {
spin_lock_irqsave(&dev->lock, flags);
hid = acc_hid_get(&dev->hid_list, w_value);
spin_unlock_irqrestore(&dev->lock, flags);
if (!hid) {
value = -EINVAL;
goto err;
}
cdev->req->context = hid;
cdev->req->complete = acc_complete_send_hid_event;
value = w_length;
}
...
if (value >= 0) {
cdev->req->zero = 0;
cdev->req->length = value;
value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
if (value < 0)
ERROR(cdev, "%s setup response queue error\n",
__func__);
}
b) f_audio_source
static int
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;
}
/* respond with data transfer or status phase? */
if (value >= 0) {
pr_debug("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;
req->complete = audio_control_complete;
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (value < 0)
pr_err("audio response on err %d\n", value);
}
static int audio_set_endpoint_req(struct usb_function *f,
const struct usb_ctrlrequest *ctrl)
{
int value = -EOPNOTSUPP;
u16 ep = le16_to_cpu(ctrl->wIndex);
u16 len = le16_to_cpu(ctrl->wLength);
u16 w_value = le16_to_cpu(ctrl->wValue);
pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
ctrl->bRequest, w_value, len, ep);
switch (ctrl->bRequest) {
case UAC_SET_CUR:
case UAC_SET_MIN:
case UAC_SET_MAX:
case UAC_SET_RES:
value = len;
break;
default:
break;
}
return value;
}
c) f_gsi
static int
gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct f_gsi *gsi = func_to_gsi(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
int id, 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);
struct gsi_ctrl_pkt *cpkt;
u8 *buf;
u32 n;
bool line_state;
...
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
...
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_SEND_ENCAPSULATED_COMMAND:
log_event_dbg("USB_CDC_SEND_ENCAPSULATED_COMMAND");
if (w_value || w_index != id)
goto invalid;
/* read the request; process it later */
value = w_length;
req->context = gsi;
if (gsi->prot_id == IPA_USB_RNDIS)
req->complete = gsi_rndis_command_complete;
else
req->complete = gsi_ctrl_cmd_complete;
/* later, rndis_response_available() sends a notification */
break;
...
...
/* respond with data transfer or status phase? */
if (value >= 0) {
log_event_dbg("req%02x.%02x v%04x i%04x l%d",
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)
log_event_err("response on err %d", value);
}
d) f_mtp
static int mtp_ctrlrequest(struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
struct mtp_dev *dev = _mtp_dev;
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);
unsigned long flags;
...
} else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
mtp_log("class request: %d index: %d value: %d length: %d\n",
ctrl->bRequest, w_index, w_value, w_length);
if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0
&& w_value == 0) {
mtp_log("MTP_REQ_CANCEL\n");
spin_lock_irqsave(&dev->lock, flags);
if (dev->state == STATE_BUSY) {
dev->state = STATE_CANCELED;
wake_up(&dev->read_wq);
wake_up(&dev->write_wq);
}
spin_unlock_irqrestore(&dev->lock, flags);
/* We need to queue a request to read the remaining
* bytes, but we don't actually need to look at
* the contents.
*/
value = w_length;
...
/* respond with data transfer or status phase? */
if (value >= 0) {
int rc;
cdev->req->zero = value < w_length;
cdev->req->length = value;
rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
if (rc < 0)
pr_err("%s: response queue error\n", __func__);
}
e) f_qc_rndis
static int
rndis_qc_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct f_rndis_qc *rndis = func_to_rndis_qc(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);
...
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_qc_command_complete;
/* 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->context = rndis;
req->zero = (value < w_length);
req->length = value;
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (value < 0)
pr_err("rndis response on err %d\n", value);
}
f) f_rmnet
static int
frmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct f_rmnet *dev = func_to_rmnet(f);
struct usb_composite_dev *cdev = dev->cdev;
struct usb_request *req = cdev->req;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
int ret = -EOPNOTSUPP;
...
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_SEND_ENCAPSULATED_COMMAND:
pr_debug("%s: USB_CDC_SEND_ENCAPSULATED_COMMAND\n"
, __func__);
ret = w_length;
req->complete = frmnet_cmd_complete;
req->context = dev;
break;
...
/* respond with data transfer or status phase? */
if (ret >= 0) {
VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
req->zero = (ret < w_length);
req->length = ret;
ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (ret < 0)
ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret);
}
Los dispositivos que implementan clases de gadget USB afectadas (f_accessory, f_audio_source, f_gsi, f_mtp, f_qc_rndis, f_rmnet) 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.
Emitir solicitudes de transferencia de control con wLength mayor que los 4096 bytes estándar requiere que el host utilice una compilación personalizada de libusb con MAX_CTRL_BUFFER_LENGTH incrementado a 0xffff. Este valor se puede modificar en libusb/os/linux_usbfs.h antes de la compilación.
El script gadget.py requiere pyusb. Puede instalar este paquete mediante pip como se muestra a continuación.
python3 -m pip install pyusb
La ayuda se puede consultar con los parámetros -h o --help.
usage: gadget.py [-h] -v VID -p PID [-l LENGTH] [-d {read,write}] [-f {accessory,audio_source,gsi,qc_rndis,rmnet,mtp}]
Sample exploit for CVE-2022-20009
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 {accessory,audio_source,gsi,qc_rndis,rmnet,mtp}, --function {accessory,audio_source,gsi,qc_rndis,rmnet,mtp}
Ejemplos de invocación:
./gadget.py -v 0x1b67 -p 0x400c -f audio_source
./gadget.py -v 0x18d1 -p 0x4e23 -f mtp
Actualice su Android a la última versión estable.