博客\

Jiajie Li

2020-09-01

浏览

CVE-2020-14364 QEMU USB数组越界读写问题

标签: Qemu , CVE , USB , Buffer overflow , Patch

# CVE-2020-14364 QEMU USB数组越界读写问题

# 漏洞描述

QEMU(quick emulator)是一款由Fabrice Bellard等人编写的免费的可执行硬件虚拟化的开源托管虚拟机。其与Bochs,PearPC类似,但拥有高速(配合KVM),跨平台的特性。QEMU通过动态的二进制转换,模拟CPU,并且提供一组设备模型,使其能够运行多种未修改的客户机OS。通过QEMU与KVM一起使用,可以实现以接近本地速度来运行虚拟机,被广泛应用到虚拟化和云计算场景中。

近日360公司发现QEMU USB控制器模拟源代码hw/usb/core.c之中存在数组越界读写的问题,攻击者可以利用该漏洞获得qemu用户的执行权限进而实现完整的QEMU虚拟机逃逸。(CVE-2020-14364)

# 漏洞分析

  • 经过分析,该漏洞存在于USB控制器模拟代码usb_process_one中,s->setup_len未经验证就赋值可能会引入的数组越界读写的风险 。由于libvirt启动的虚拟机默认会有配置有usb设备,而任何usb控制器(如uhci,ehci,xhci)与usb设备(如usb-tablet,usb-mouse等)之间交互都会经过core.c文件中的usb_process_one函数,因此理论上只要虚拟机有使用usb设备都存在漏洞攻击的风险。 此外,usb_process_one函数中可能进入的两个分支do_parameter和do_token_setup均存在该问题,即:在检查最终需要使用的数组长度前已经提前设置了该数组长度(s->setup_len)
  • 该漏洞的影响后果非常严重,因为攻击者可以利用该漏洞实现任意地址读取和写入,从而获得qemu进程的所有权限从而实现虚拟机逃逸,实现恶意代码执行攻击。此外,如果qemu进程处于root用户组那么攻击者就可以完整获得操作系统的控制权进而执行任意linux系统命令!

# 代码分析

static void do_token_setup(USBDevice *s, USBPacket *p)                                     
{                                                                               
    int request, value, index;                                                  
                                                                                
    if (p->iov.size != 8) {                                                     
        p->status = USB_RET_STALL;                                              
        return;                                                                 
    }                                                                           
                                                                                
    usb_packet_copy(p, s->setup_buf, p->iov.size);                              
    s->setup_index = 0;                                                         
    p->actual_length = 0;                                                       
    s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6]; //先对s->setup_len赋值     
    if (s->setup_len > sizeof(s->data_buf)) { // 后对setup_len进行合法性校验                 
        fprintf(stderr,                                                         
                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
                s->setup_len, sizeof(s->data_buf));                             
        p->status = USB_RET_STALL;                                              
        return;                                                                 
    }                                                                           
static void do_parameter(USBDevice *s, USBPacket *p)                            
{                                                                               
    int i, request, value, index;                                               
                                                                                
    for (i = 0; i < 8; i++) {                                                   
        s->setup_buf[i] = p->parameter >> (i*8);                                
    }                                                                           
                                                                                
    s->setup_state = SETUP_STATE_PARAM;                                         
    s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6]; //先对s->setup_len赋值       
    s->setup_index = 0;                                                         
                                                                                
    request = (s->setup_buf[0] << 8) | s->setup_buf[1];                         
    value   = (s->setup_buf[3] << 8) | s->setup_buf[2];                         
    index   = (s->setup_buf[5] << 8) | s->setup_buf[4];                         
                                                                                
    if (s->setup_len > sizeof(s->data_buf)) { // 后对setup_len进行合法性校验                 
        fprintf(stderr,                                                         
                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
                s->setup_len, sizeof(s->data_buf));                             
        p->status = USB_RET_STALL;                                              
        return;                                                                 
    }    

通过代码可以看到:do_token_setup以及do_parameter两个函数中,都在对s->setup_len做检查之前就设置了具体内容,并且当s->setup_len超出预定buffer大小之后,也没有对其进行更改,这使得其中被污染的数据依旧可以完成之后的越界读写功能。

# 影响性分析

  1. 影响范围 QEMU 1.x 至今的QEMU的版本源代码之中均存在该漏洞,其中也包括了openEuler社区之前所使用的QEMU的代码。
  2. 触发条件 触发该漏洞需要虚拟机至少连接有一个usb设备,而多数情况下libvirt会默认为虚拟机配置USB设备。

# 漏洞修复方法

根据360给出的修复方案,当检测到setup_len非法之后,将s->setup_len清零表示丢弃buffer中的USB请求,同时将USB的状态设置为SETUP_STATE_ACK,重新开始接受其他请求。补丁内容为:

Subject: [PATCH] hw/usb/core.c fix buffer overflow

Store calculated setup_len in a local variable, verify it,
 and only write it to the struct (USBDevice->setup_len) in case it passed the
 sanity checks.

This prevents other code (do_token_{in,out} function specifically)
from working with invalid USBDevice->setup_len values and overruning
the USBDevice->setup_buf[] buffer.
Store
Fixes: CVE-2020-14364
Signed-off-by: Gred Hoffman <kraxel@redhat.com>
---
 hw/usb/core.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/hw/usb/core.c b/hw/usb/core.c
index 5abd128b..12342f13 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -144,6 +144,8 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
                 "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
                 s->setup_len, sizeof(s->data_buf));
         p->status = USB_RET_STALL;
+		s->setup_len = 0;
+		s->setup_state = SETUP_STATE_ACK;
         return;
     }
 
@@ -277,6 +279,8 @@ static void do_parameter(USBDevice *s, USBPacket *p)
                 "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
                 s->setup_len, sizeof(s->data_buf));
         p->status = USB_RET_STALL;
+		s->setup_len = 0;
+		s->setup_state = SETUP_STATE_ACK;
         return;
     }
 
-- 

# 解决方案

  • 下载openEuler发布最新的qemu软件包:

  • 升级qemu软件包

    • rpm -Uvh qemu-*.rpm
  • 升级完成之后查看qemu软件包的release号码,当release号大于17表示漏洞修复成功。

    • rpm -qi qemu-4.1.0

      Name        : qemu
      Epoch       : 2
      Version     : 4.1.0
      Release     : 18.oe1
      Architecture: aarch64
      Install Date: Mon 10 Aug 2020 04:53:20 PM CST
      Group       : Unspecified
      Size        : 19468602
      License     : GPLv2 and BSD and MIT and CC-BY
      Signature   : RSA/SHA1, Thu 09 Jul 2020 11:52:58 AM CST, Key ID d557065eb25e7f66
      Source RPM  : qemu-4.1.0-14.oe1.src.rpm
      Build Date  : Thu 09 Jul 2020 11:44:23 AM CST
      Build Host  : obs-worker-004
      Packager    : http://openeuler.org
      Vendor      : http://openeuler.org
      URL         : http://www.qemu.org
      

# FAQ

  1. OpenEuler社区对此漏洞采取了什么措施? 社区相关人员得知漏洞之后,立刻制作出了相对应的补丁,并且第一时间将补丁合入社区相关分支之中。
  2. 如何获取该漏洞的具体复现场景? 可以参考360在ISC2020第八届互联网安全大会上的分享:
    • https://isc.360.com/2020/detail.html?vid=108
    • https://www.anquanke.com/post/id/215405
【版权声明】Copyright © 2021 openEuler Community。本文由openEuler社区首发,欢迎遵照 CC-BY-SA 4.0 协议规定转载。转载时敬请在正文注明并保留原文链接和作者信息。
【免责声明】本文仅代表作者本人观点,与本网站无关。本网站对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文仅供读者参考,由此产生的所有法律责任均由读者本人承担。