CVE-2020-14364 QEMU USB Out-of-Bounds R/W Access Issue

Jiajie Li2020-09-01-QEMU -CVE -USB -buffer overflow -patch

CVE-2020-14364 QEMU USB Out-of-bounds R/W Access Issue

Vulnerability Description

QEMU (short for Quick Emulator) is a free, open source hosted virtual machine written by Fabrice Bellard et al. for hardware virtualization. It is similar to Bochs and PearPC, but it has some features that the latter two do not have, such as high speed (working with KVM) and cross-platform features. QEMU emulates CPUs through dynamic binary translation and provides a set of device models, enabling it to run a variety of unmodified guest operating systems. In virtualization and cloud computing scenarios, it is used together with KVM to run virtual machines at near-native speed.

Recently, Qihoo 360 Inc. found an out-of-bounds read/write access issue in the QEMU USB controller emulation source code hw/usb/core.c. Attackers may exploit this vulnerability to obtain the execution permission of QEMU users and perform a full QEMU VM escape. (CVE-2020-14364)

Vulnerability Analysis

  • This vulnerability exists in the USB controller emulator code usb_process_one. Assigning a value to s->setup_len without verification may introduce out-of-bounds read/write risks. By default, the VM started by libvirt is configured with a USB device. The interaction between any USB controller (such as UHCI, EHCI, and XHCI) and USB device (such as USB tablets and USB mouse devices) requires the usb_process_one function in the core.c file. Therefore, theoretically, as long as VMs use USB devices, vulnerability attacks may occur. The usb_process_one function may also be introduced to the do_parameter and do_token_setup functions. That is, the array length (s->setup_len) has been set before it is checked.
  • This vulnerability has a serious impact because attackers can exploit this vulnerability to read and write any address and obtain all permissions of the QEMU process to cause a VM escape for malicious attacks. What's worse, if the QEMU process is run by the root user group, attackers can obtain the full control permissions of the operating system to run any Linux system commands.

Code Analysis

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]; // Assigns a value to s->setup_len.    
    if (s->setup_len > sizeof(s->data_buf)) { // Verifies the validity of 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]; // Assigns a value to 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)) { // Verifies the validity of 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;                                                                 
    }    

According to the code, in the do_token_setup and do_parameter functions, s->setup_len is specified before it is checked. Even the value of s->setup_len exceeds the preset buffer size, the value of the former is not changed. As a result, the contaminated data can still be read and written out of bounds.

Impact Analysis

  1. Impact Scope
    This vulnerability exists in the source code of QEMU 1.x and later versions, including the QEMU code used by the openEuler community.
  2. Trigger Condition
    The VM must have been connected to at least one USB device. In most cases, libvirt configures a USB device for the VM by default.

Vulnerability Fixing Method

According to the repair solution provided by Qihoo 360 Inc., when setup_len is invalid, s->setup_len is cleared to discard the USB request in the buffer, and the USB status is set to SETUP_STATE_ACK to accept other requests. The patch is as follows:

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;
     }
 
-- 

Solution

  • Download the latest QEMU software package released by openEuler.

  • Update the QEMU software package.

    • rpm -Uvh qemu-*.rpm
  • After the update is complete, check the release number of the QEMU software package. If the release number is greater than 17, the vulnerability is successfully fixed.

    • 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
      

FAQs

  1. What measures did the openEuler community take to resolve this vulnerability?
    After learning the vulnerability, related personnel in the community immediately developed a patch and integrate the patch into the related branches of the community.
  2. How do I obtain the specific reproduction scenario of the vulnerability?
    For details, see Qihoo 360 Inc.'s sharing at the 8th Internet Security Conference (“ISC 2020”).
    • https://isc.360.com/2020/detail.html?vid=108
    • https://www.anquanke.com/post/id/215405

[Disclaimer] This article only represents the author's opinions, and is irrelevant to this website. This website is neutral in terms of the statements and opinions in this article, and does not provide any express or implied warranty of accuracy, reliability, or completeness of the contents contained therein. This article is for readers' reference only, and all legal responsibilities arising therefrom are borne by the reader himself.