check of PMR capability is missing for PMRCTL register write
Brief introduction
An access on an unknown address is triggered in memory_region_set_enabled
/root/qemu/build-oss-fuzz/../softmmu/memory.c:2482:24
because the check of PRM capability is missing for PMRCTL register write when PMR is not set in the launch command line.
static void nvme_write_bar(NvmeCtrl *n, hwaddr offset, uint64_t dat[nvme-00.tar.gz](/uploads/016fc0c62d5fa887be1b02180f6f055a/nvme-00.tar.gz)a, unsigned size) {
switch (offset) {
case 0xE04: /* PMRCTL */
n->bar.pmrctl = data;
if (NVME_PMRCTL_EN(data)) {
memory_region_set_enabled(&n->pmr.dev->mr, true); // n->pmr.dev = NULL
n->bar.pmrsts = 0; // ---------------------
} else { // invalid pointer
...
}
return;
void memory_region_set_enabled(MemoryRegion *mr, bool enabled) {
if (enabled == mr->enabled) {
return; // -------------------
} // crash at the deref of mr->enabled
...
}
More technique details
- QEMU version, upstream commit/tag
6.0.50, 3e13d8e3
- Host and Guest
Ubuntu 18.04 docker QTest Fuzzer
- Stack traces, crash details
AddressSanitizer:DEADLYSIGNAL
=================================================================
==144==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000000ea (pc 0x55ac399a2783 bp 0x7ffe09be78f0 sp 0x7ffe09be78d0 T0)
==144==The signal is caused by a READ memory access.
==144==Hint: address points to the zero page.
#0 0x55ac399a2783 in memory_region_set_enabled /root/qemu/build-oss-fuzz/../softmmu/memory.c:2482:24
#1 0x55ac3942691b in nvme_write_bar /root/qemu/build-oss-fuzz/../hw/block/nvme.c:5588:13
#2 0x55ac3942691b in nvme_mmio_write /root/qemu/build-oss-fuzz/../hw/block/nvme.c:5814:9
#3 0x55ac39999757 in memory_region_write_accessor /root/qemu/build-oss-fuzz/../softmmu/memory.c:491:5
#4 0x55ac3999911d in access_with_adjusted_size /root/qemu/build-oss-fuzz/../softmmu/memory.c:552:18
#5 0x55ac3999911d in memory_region_dispatch_write /root/qemu/build-oss-fuzz/../softmmu/memory.c:1502:16
#6 0x55ac398191d0 in flatview_write_continue /root/qemu/build-oss-fuzz/../softmmu/physmem.c:2746:23
#7 0x55ac3980b7e7 in flatview_write /root/qemu/build-oss-fuzz/../softmmu/physmem.c:2786:14
#8 0x55ac3980af8c in address_space_write /root/qemu/build-oss-fuzz/../softmmu/physmem.c:2878:18
#9 0x55ac38d12f6c in __wrap_qtest_writel /root/qemu/build-oss-fuzz/../tests/qtest/fuzz/qtest_wrappers.c:177:9
#10 0x55ac38d0de6a in stateful_fuzz /root/qemu/build-oss-fuzz/../tests/qtest/fuzz/stateful_fuzz.c:402:13
#11 0x55ac38d0f5e0 in LLVMFuzzerTestOneInput /root/qemu/build-oss-fuzz/../tests/qtest/fuzz/fuzz.c:151:5
#12 0x55ac38bfe383 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:603
#13 0x55ac38c01d48 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool, bool*) /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:509
#14 0x55ac38c03afe in fuzzer::Fuzzer::MutateAndTestOne() /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:750
#15 0x55ac38c06f87 in fuzzer::Fuzzer::Loop(std::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:887
#16 0x55ac38bec439 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:908
#17 0x55ac38bd8112 in main /root/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20
#18 0x7f5225198bf6 in __libc_start_main (/root/qemu/build-oss-fuzz/DEST_DIR/lib/libc.so.6+0x21bf6)
#19 0x55ac38bd8169 in _start (/root/qemu/build-oss-fuzz/DEST_DIR/qemu-fuzz-i386-target-stateful-fuzz-nvme+0xed2169)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /root/qemu/build-oss-fuzz/../softmmu/memory.c:2482:24 in memory_region_set_enabled
4 Reproducer steps
I wrote a kernel module to reproduce this crash.
uint32_t address = (uint32_t)ioremap(0xfebd0000, 16 * 1024);
// trigger
writel(0xffffffff, (void *)(address + 0xe04));
Execute
#!/bin/bash
export QEMU=/root/qemu/build-coverage/qemu-system-i386
export BUILDROOT=/root/images
$QEMU \
-M q35 \
-kernel $BUILDROOT/bzImage \
-drive file=$BUILDROOT/rootfs.ext2,if=virtio,format=raw \
-append "root=/dev/vda console=ttyS0" \
-nic user,model=virtio-net-pci \
-drive id=nvm,file=null-co://,file.read-zeroes=on,if=none,format=raw \
-device nvme,serial=deadbeef,drive=nvm \
-nographic \
-m 64
The username is root and the password is empty. Then,
modprobe nvme-00
You will see the crash.
# modprobe nvme-00
nvme_00: loading out-of-tree module taints kernel.
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==34==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x0000000000ea (pc 0x56486060a980 bp 0x7f413b2fbfb0 sp 0x7f413b2fbf90 T36)
==34==The signal is caused by a READ memory access.
==34==Hint: address points to the zero page.
The bzImage and rootfs.ext2 are attached.
Suggested fix
diff --git a/hw/block/nvme.c b/hw/block/nvme.c
index 5fe082e..da95fa5 100644
--- a/hw/block/nvme.c
+++ b/hw/block/nvme.c
@@ -5580,6 +5580,10 @@ static void nvme_write_bar(NvmeCtrl *n, hwaddr offset, uint64_t data,
"invalid write to PMRCAP register, ignored");
return;
case 0xE04: /* PMRCTL */
+ if (!NVME_CAP_PMRS(n->bar.cap)) {
+ return;
+ }
+
n->bar.pmrctl = data;
if (NVME_PMRCTL_EN(data)) {
memory_region_set_enabled(&n->pmr.dev->mr, true);
Edited by Qiang Liu