| /* |
| * XXX - Credit, licence, copyright |
| */ |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/kvm.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "helpers.h" |
| |
| #define GUEST_PHYS_ADDR (1UL << 30) |
| #define GUEST_MMIO_ADDR (1UL << 38) |
| #define GUEST_MEM_SIZE (PMD_SIZE*4) |
| |
| /* |
| * x0[0] = x1; |
| * x1 = x0[0]; |
| */ |
| void guest_code() |
| { |
| asm( |
| " str x1, [x0] \n" |
| " brk #0 \n" |
| " ldr x1, [x0] \n" |
| " brk #0 \n" |
| ::); |
| |
| } |
| #define GUEST_NR_INST 4 |
| #define GUEST_CODE_SIZE() (GUEST_NR_INST * ARM64_INST_SIZE) |
| |
| void guest_brk() |
| { |
| asm("brk #0 \n" ::); |
| } |
| |
| static void guest_write_memory(int vcpufd, struct kvm_run *run, uint64_t addr) |
| { |
| set_one_reg(vcpufd, REG_X(0), addr); |
| set_one_reg(vcpufd, REG_X(1), 1); |
| set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR); |
| assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG); |
| } |
| |
| static void guest_read_memory(int vcpufd, struct kvm_run *run, uint64_t addr) |
| { |
| set_one_reg(vcpufd, REG_X(0), addr); |
| set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR + 2 * ARM64_INST_SIZE); |
| assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG); |
| } |
| |
| struct kvm_userspace_memory_region guest_prepare_memory(int vmfd, int vcpufd) |
| { |
| uint8_t *mem_code; |
| |
| /* Note: this uses MAP_HUGETLB */ |
| mem_code = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); |
| if (mem_code == MAP_FAILED) |
| err(-ENOMEM, "Are there PMD hugetlb pages available on the system?"); |
| memcpy(mem_code, guest_code, GUEST_CODE_SIZE()); |
| |
| /* Map code memory to the second page frame. */ |
| struct kvm_userspace_memory_region region = { |
| .slot = 0, |
| .guest_phys_addr = GUEST_PHYS_ADDR, |
| .memory_size = GUEST_MEM_SIZE, |
| .userspace_addr = (uint64_t)mem_code, |
| }; |
| KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| |
| vm_add_mmio_page(vmfd, 1, GUEST_MMIO_ADDR); |
| set_one_reg(vcpufd, REG_X(3), GUEST_MMIO_ADDR); |
| |
| return region; |
| } |
| |
| int main(void) |
| { |
| struct kvm_userspace_memory_region region; |
| uint64_t addr, offset, user_addr; |
| struct kvm_run *run = NULL; |
| struct kvm_dirty_log dlog; |
| int kvm, vmfd, vcpufd; |
| uint8_t *dirty_bm; |
| int i; |
| |
| /* Prepare the guest */ |
| kvm = get_kvm(); |
| vmfd = create_vm(kvm); |
| vcpufd = create_vcpu(kvm, vmfd, &run, true); |
| |
| /* Create a memslot backed by hugetlb */ |
| region = guest_prepare_memory(vmfd, vcpufd); |
| |
| /* Enable dirty logging, to force PTEs despite having huge pages */ |
| region.flags = KVM_MEM_LOG_DIRTY_PAGES; |
| KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| |
| /* |
| * The address from which we read and write is in the same page as the |
| * code, so the first read / exec faults should get us the RX mappings |
| * we want, and then the write should trigger the bug Will found out |
| */ |
| offset = GUEST_CODE_SIZE(); |
| addr = GUEST_PHYS_ADDR + offset; |
| |
| /* Force user_mem_abort() to create the RX mapping, with PTEs*/ |
| guest_read_memory(vcpufd, run, addr); |
| |
| /* Disable dirty logging, to now get block mappings */ |
| region.flags = 0; |
| KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| |
| /* Write to the same page now, to trigger the write fault */ |
| guest_write_memory(vcpufd, run, addr); |
| |
| printf("Passing.\n"); |
| } |