// SPDX-License-Identifier: GPL-2.0-only /* * Reproducer for the KASAN use-after-free in kvm_xen_shared_info_init(). * * https://syzkaller.appspot.com/bug?extid=0948c82180d475ad24e2 * * KVM_XEN_HVM_SET_ATTR(KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA) activates and * refreshes the shinfo gfn_to_pfn_cache and then writes the wallclock * through gpc->khva in kvm_xen_shared_info_init(). Meanwhile another * thread repeatedly maps fresh MAP_SHARED|MAP_ANONYMOUS (shmem) memory * over the same HVA with MAP_FIXED, tearing down the old mapping (mmu * notifier invalidation) and freeing the old shmem page. * * The HVA-based cache is not backed by any memslot, so those * invalidations never bump kvm->mmu_invalidate_seq. An invalidation * which starts and ends entirely within hva_to_pfn_retry()'s window * (gpc->lock dropped for GUP) is therefore invisible to * mmu_notifier_retry_cache(): mn_active_invalidate_count is back to * zero and the seq never moved. The refresh commits a pfn whose page * was freed mid-window, and kvm_xen_shared_info_init() writes the * wallclock through a mapping of a freed page. * * Detection requires KASAN; on a non-KASAN kernel the corruption is * silent. Fires within seconds on an unfixed kernel. */ #include #include #include #include #include #include #include #include #include #include #define NR_PROCS 5 #define MAP_SIZE 0x10000UL #define ITERS_PER_VM 2000 static unsigned long map_addr; static void *remap_thread(void *arg) { for (;;) { /* * Replace the mapping with a fresh shmem object. The old * mapping is torn down (mmu notifier invalidation) and the * old shmem pages are freed unless someone holds a ref. */ void *p = mmap((void *)map_addr, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (p == MAP_FAILED) _exit(2); /* Fault a page in so there is something to free */ *(volatile char *)p = 1; } return NULL; } static void worker(void) { struct kvm_xen_hvm_attr attr; pthread_t tid; int kvm_fd, vm_fd, i; kvm_fd = open("/dev/kvm", O_RDWR); if (kvm_fd < 0) { perror("open /dev/kvm"); _exit(1); } pthread_create(&tid, NULL, remap_thread, NULL); for (;;) { vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0); if (vm_fd < 0) _exit(3); for (i = 0; i < ITERS_PER_VM; i++) { memset(&attr, 0, sizeof(attr)); attr.type = KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA; attr.u.shared_info.hva = map_addr; ioctl(vm_fd, KVM_XEN_HVM_SET_ATTR, &attr); } close(vm_fd); } } int main(void) { int i; /* Pick a fixed address for the contested mapping */ void *p = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) { perror("mmap"); return 1; } map_addr = (unsigned long)p; printf("xen-shinfo-uaf: starting %d workers, hva=0x%lx\n", NR_PROCS, map_addr); for (i = 0; i < NR_PROCS; i++) { if (fork() == 0) { worker(); _exit(0); } } for (;;) pause(); return 0; }