| /* |
| * Check decoding of KVM_* commands of ioctl syscall using /dev/kvm API. |
| * Based on kvmtest.c from https://lwn.net/Articles/658512/ |
| * |
| * kvmtest.c author: Josh Triplett <josh@joshtriplett.org> |
| * Copyright (c) 2015 Intel Corporation |
| * Copyright (c) 2017-2018 The strace developers. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to |
| * deal in the Software without restriction, including without limitation the |
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| * sell copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| */ |
| |
| #include "tests.h" |
| |
| #if defined HAVE_LINUX_KVM_H \ |
| && defined HAVE_STRUCT_KVM_CPUID2 \ |
| && defined HAVE_STRUCT_KVM_REGS \ |
| && defined HAVE_STRUCT_KVM_SREGS \ |
| && defined HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION \ |
| &&(defined __x86_64__ || defined __i386__) |
| |
| # include <fcntl.h> |
| # include <stdint.h> |
| # include <stdio.h> |
| # include <stdlib.h> |
| # include <string.h> |
| # include <sys/ioctl.h> |
| # include <sys/mman.h> |
| # include <unistd.h> |
| # include <linux/kvm.h> |
| |
| # ifndef KVM_MAX_CPUID_ENTRIES |
| # define KVM_MAX_CPUID_ENTRIES 80 |
| # endif |
| |
| #include "xlat.h" |
| #include "xlat/kvm_cpuid_flags.h" |
| |
| static int |
| kvm_ioctl(int fd, unsigned long cmd, const char *cmd_str, void *arg) |
| { |
| int rc = ioctl(fd, cmd, arg); |
| if (rc < 0) |
| perror_msg_and_skip("%s", cmd_str); |
| return rc; |
| } |
| |
| #define KVM_IOCTL(fd_, cmd_, arg_) \ |
| kvm_ioctl((fd_), (cmd_), #cmd_, (arg_)) |
| |
| static const char dev[] = "/dev/kvm"; |
| static const char vm_dev[] = "anon_inode:kvm-vm"; |
| static char vcpu_dev[] = "anon_inode:kvm-vcpu:0"; |
| static size_t page_size; |
| |
| extern const char code[]; |
| extern const unsigned short code_size; |
| |
| __asm__( |
| ".type code, @object \n" |
| "code: \n" |
| " mov $0xd80003f8, %edx \n" |
| " mov $'\n', %al \n" |
| " out %al, (%dx) \n" |
| " hlt \n" |
| ".size code, . - code \n" |
| ".type code_size, @object \n" |
| "code_size: \n" |
| " .short . - code \n" |
| ".size code_size, . - code_size \n" |
| ); |
| |
| static void |
| print_kvm_segment(const struct kvm_segment *seg) |
| { |
| printf("{base=%#jx, limit=%u, selector=%u, type=%u, present=%u, " |
| "dpl=%u, db=%u, s=%u, l=%u, g=%u, avl=%u}", |
| (uintmax_t) seg->base, seg->limit, seg->selector, seg->type, |
| seg->present, seg->dpl, seg->db, seg->s, seg->l, seg->g, |
| seg->avl); |
| } |
| |
| static void |
| print_kvm_sregs(const struct kvm_sregs *sregs) |
| { |
| printf("{cs="); |
| print_kvm_segment(&sregs->cs); |
| #if VERBOSE |
| printf(", ds="); |
| print_kvm_segment(&sregs->ds); |
| printf(", es="); |
| print_kvm_segment(&sregs->es); |
| printf(", fs="); |
| print_kvm_segment(&sregs->fs); |
| printf(", gs="); |
| print_kvm_segment(&sregs->gs); |
| printf(", ss="); |
| print_kvm_segment(&sregs->ss); |
| printf(", tr="); |
| print_kvm_segment(&sregs->tr); |
| printf(", ldt="); |
| print_kvm_segment(&sregs->ldt); |
| printf(", gdt={base=%#jx, limit=%u}, idt={base=%#jx, limit=%u}, " |
| "cr0=%llu, cr2=%llu, cr3=%llu, cr4=%llu, cr8=%llu, efer=%llu, " |
| "apic_base=%#jx", (uintmax_t) sregs->gdt.base, sregs->gdt.limit, |
| (uintmax_t) sregs->idt.base, sregs->idt.limit, sregs->cr0, |
| sregs->cr2, sregs->cr3, sregs->cr4, sregs->cr8, sregs->efer, |
| (uintmax_t)sregs->apic_base); |
| printf(", interrupt_bitmap=["); |
| for (size_t i = 0; i < ARRAY_SIZE(sregs->interrupt_bitmap); i++) { |
| if (i) |
| printf(", "); |
| printf("%#jx", (uintmax_t) sregs->interrupt_bitmap[i]); |
| } |
| printf("]"); |
| #else |
| printf(", ..."); |
| #endif |
| printf("}"); |
| } |
| |
| static void |
| print_kvm_regs(const struct kvm_regs *regs) |
| { |
| printf("{rax=%#jx", (uintmax_t) regs->rax); |
| #if VERBOSE |
| printf(", rbx=%#jx, rcx=%#jx, rdx=%#jx, rsi=%#jx, rdi=%#jx", |
| (uintmax_t) regs->rbx, (uintmax_t) regs->rcx, |
| (uintmax_t) regs->rdx, (uintmax_t) regs->rsi, |
| (uintmax_t) regs->rdi); |
| #else |
| printf(", ..."); |
| #endif |
| printf(", rsp=%#jx, rbp=%#jx", (uintmax_t) regs->rsp, |
| (uintmax_t) regs->rbp); |
| #if VERBOSE |
| printf(", r8=%#jx, r9=%#jx, r10=%#jx, r11=%#jx, r12=%#jx, r13=%#jx" |
| ", r14=%#jx, r15=%#jx", |
| (uintmax_t) regs->r8, (uintmax_t) regs->r9, |
| (uintmax_t) regs->r10, (uintmax_t) regs->r11, |
| (uintmax_t) regs->r12, (uintmax_t) regs->r13, |
| (uintmax_t) regs->r14, (uintmax_t) regs->r15); |
| #else |
| printf(", ..."); |
| #endif |
| printf(", rip=%#jx, rflags=%#jx}", (uintmax_t) regs->rip, |
| (uintmax_t) regs->rflags); |
| } |
| |
| # define need_print_KVM_RUN 1 |
| |
| static void |
| print_KVM_RUN(const int fd, const char *const dev, const unsigned int reason); |
| |
| static void |
| run_kvm(const int vcpu_fd, struct kvm_run *const run, const size_t mmap_size, |
| void *const mem) |
| { |
| /* Initialize CS to point at 0, via a read-modify-write of sregs. */ |
| struct kvm_sregs sregs; |
| KVM_IOCTL(vcpu_fd, KVM_GET_SREGS, &sregs); |
| printf("ioctl(%d<%s>, KVM_GET_SREGS, ", vcpu_fd, vcpu_dev); |
| print_kvm_sregs(&sregs); |
| printf(") = 0\n"); |
| |
| sregs.cs.base = 0; |
| sregs.cs.selector = 0; |
| KVM_IOCTL(vcpu_fd, KVM_SET_SREGS, &sregs); |
| printf("ioctl(%d<%s>, KVM_SET_SREGS, ", vcpu_fd, vcpu_dev); |
| print_kvm_sregs(&sregs); |
| printf(") = 0\n"); |
| |
| /* |
| * Initialize registers: instruction pointer for our code, addends, |
| * and initial flags required by x86 architecture. |
| */ |
| struct kvm_regs regs = { |
| .rip = page_size, |
| .rax = 2, |
| .rbx = 2, |
| .rflags = 0x2, |
| }; |
| KVM_IOCTL(vcpu_fd, KVM_SET_REGS, ®s); |
| printf("ioctl(%d<%s>, KVM_SET_REGS, ", vcpu_fd, vcpu_dev); |
| print_kvm_regs(®s); |
| printf(") = 0\n"); |
| |
| /* Copy the code */ |
| memcpy(mem, code, code_size); |
| |
| const char *p = "\n"; |
| |
| /* Repeatedly run code and handle VM exits. */ |
| for (;;) { |
| KVM_IOCTL(vcpu_fd, KVM_RUN, NULL); |
| print_KVM_RUN(vcpu_fd, vcpu_dev, run->exit_reason); |
| |
| switch (run->exit_reason) { |
| case KVM_EXIT_HLT: |
| if (p) |
| error_msg_and_fail("premature KVM_EXIT_HLT"); |
| return; |
| case KVM_EXIT_IO: |
| if (run->io.direction == KVM_EXIT_IO_OUT |
| && run->io.size == 1 |
| && run->io.port == 0x03f8 |
| && run->io.count == 1 |
| && run->io.data_offset < mmap_size |
| && p && *p == ((char *) run)[run->io.data_offset]) |
| p = NULL; |
| else |
| error_msg_and_fail("unhandled KVM_EXIT_IO"); |
| break; |
| case KVM_EXIT_MMIO: |
| error_msg_and_fail("Got an unexpected MMIO exit:" |
| " phys_addr %#llx," |
| " data %02x %02x %02x %02x" |
| " %02x %02x %02x %02x," |
| " len %u, is_write %hhu", |
| (unsigned long long) run->mmio.phys_addr, |
| run->mmio.data[0], run->mmio.data[1], |
| run->mmio.data[2], run->mmio.data[3], |
| run->mmio.data[4], run->mmio.data[5], |
| run->mmio.data[6], run->mmio.data[7], |
| run->mmio.len, run->mmio.is_write); |
| |
| default: |
| error_msg_and_fail("exit_reason = %#x", |
| run->exit_reason); |
| } |
| } |
| } |
| |
| static int |
| vcpu_dev_should_have_cpuid(int fd) |
| { |
| int r = 0; |
| char *filename = NULL; |
| char buf[sizeof(vcpu_dev)]; |
| |
| if (asprintf(&filename, "/proc/%d/fd/%d", getpid(), fd) < 0) |
| error_msg_and_fail("asprintf"); |
| |
| if (readlink(filename, buf, sizeof(buf)) == sizeof(buf) - 1 |
| && (memcmp(buf, vcpu_dev, sizeof(buf) - 1) == 0)) |
| r = 1; |
| free(filename); |
| return r; |
| } |
| |
| static void |
| print_cpuid_ioctl(int fd, const char *fd_dev, |
| const char *ioctl_name, const struct kvm_cpuid2 *cpuid) |
| { |
| printf("ioctl(%d<%s>, %s, {nent=%u, entries=[", |
| fd, fd_dev, ioctl_name, cpuid->nent); |
| #if VERBOSE |
| for (size_t i = 0; i < cpuid->nent; i++) { |
| if (i) |
| printf(", "); |
| printf("{function=%#x, index=%#x, flags=", |
| cpuid->entries[i].function, cpuid->entries[i].index); |
| printflags(kvm_cpuid_flags, cpuid->entries[i].flags, |
| "KVM_CPUID_FLAG_???"); |
| printf(", eax=%#x, ebx=%#x, ecx=%#x, edx=%#x}", |
| cpuid->entries[i].eax, cpuid->entries[i].ebx, |
| cpuid->entries[i].ecx, cpuid->entries[i].edx); |
| } |
| #else |
| if (cpuid->nent) |
| printf("..."); |
| #endif |
| printf("]}) = 0\n"); |
| } |
| |
| int |
| main(void) |
| { |
| skip_if_unavailable("/proc/self/fd/"); |
| |
| int kvm = open(dev, O_RDWR); |
| if (kvm < 0) |
| perror_msg_and_skip("open: %s", dev); |
| |
| /* Make sure we have the stable version of the API */ |
| int ret = KVM_IOCTL(kvm, KVM_GET_API_VERSION, 0); |
| if (ret != KVM_API_VERSION) |
| error_msg_and_skip("KVM_GET_API_VERSION returned %d" |
| ", KVM_API_VERSION is %d", |
| kvm, KVM_API_VERSION); |
| printf("ioctl(%d<%s>, KVM_GET_API_VERSION, 0) = %d\n", |
| kvm, dev, ret); |
| |
| ret = KVM_IOCTL(kvm, KVM_CHECK_EXTENSION, |
| (void *) (uintptr_t) KVM_CAP_USER_MEMORY); |
| printf("ioctl(%d<%s>, KVM_CHECK_EXTENSION, KVM_CAP_USER_MEMORY) = %d\n", |
| kvm, dev, ret); |
| |
| int vm_fd = KVM_IOCTL(kvm, KVM_CREATE_VM, 0); |
| printf("ioctl(%d<%s>, KVM_CREATE_VM, 0) = %d<%s>\n", |
| kvm, dev, vm_fd, vm_dev); |
| |
| /* Allocate one aligned page of guest memory to hold the code. */ |
| page_size = get_page_size(); |
| void *const mem = mmap(NULL, page_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, -1, 0); |
| if (mem == MAP_FAILED) |
| perror_msg_and_fail("mmap page"); |
| |
| /* Map it to the second page frame (to avoid the real-mode IDT at 0). */ |
| struct kvm_userspace_memory_region region = { |
| .slot = 0, |
| .guest_phys_addr = page_size, |
| .memory_size = page_size, |
| .userspace_addr = (uintptr_t) mem, |
| }; |
| KVM_IOCTL(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| printf("ioctl(%d<%s>, KVM_SET_USER_MEMORY_REGION" |
| ", {slot=0, flags=0, guest_phys_addr=%#lx, memory_size=%lu" |
| ", userspace_addr=%p}) = 0\n", vm_fd, vm_dev, |
| (unsigned long) page_size, (unsigned long) page_size, mem); |
| |
| int vcpu_fd = KVM_IOCTL(vm_fd, KVM_CREATE_VCPU, NULL); |
| if (!vcpu_dev_should_have_cpuid(vcpu_fd)) { |
| /* |
| * This is an older kernel that doesn't place a cpuid |
| * at the end of the dentry associated with vcpu_fd. |
| * Trim the cpuid part of vcpu_dev like: |
| * "anon_inode:kvm-vcpu:0" -> "anon_inode:kvm-vcpu" |
| */ |
| vcpu_dev[strlen (vcpu_dev) - 2] = '\0'; |
| #ifdef KVM_NO_CPUID_CALLBACK |
| KVM_NO_CPUID_CALLBACK; |
| #endif |
| } |
| |
| printf("ioctl(%d<%s>, KVM_CREATE_VCPU, 0) = %d<%s>\n", |
| vm_fd, vm_dev, vcpu_fd, vcpu_dev); |
| |
| /* Map the shared kvm_run structure and following data. */ |
| ret = KVM_IOCTL(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL); |
| struct kvm_run *run; |
| if (ret < (int) sizeof(*run)) |
| error_msg_and_fail("KVM_GET_VCPU_MMAP_SIZE returned %d < %d", |
| ret, (int) sizeof(*run)); |
| printf("ioctl(%d<%s>, KVM_GET_VCPU_MMAP_SIZE, 0) = %d\n", |
| kvm, dev, ret); |
| |
| const size_t mmap_size = (ret + page_size - 1) & -page_size; |
| run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, vcpu_fd, 0); |
| if (run == MAP_FAILED) |
| perror_msg_and_fail("mmap vcpu"); |
| |
| size_t cpuid_nent = KVM_MAX_CPUID_ENTRIES; |
| struct kvm_cpuid2 *cpuid = tail_alloc(sizeof(*cpuid) + |
| cpuid_nent * |
| sizeof(*cpuid->entries)); |
| |
| cpuid->nent = 0; |
| ioctl(kvm, KVM_GET_SUPPORTED_CPUID, cpuid); |
| printf("ioctl(%d<%s>, KVM_GET_SUPPORTED_CPUID, %p) = -1 E2BIG (%m)\n", |
| kvm, dev, cpuid); |
| |
| cpuid->nent = cpuid_nent; |
| |
| KVM_IOCTL(kvm, KVM_GET_SUPPORTED_CPUID, cpuid); |
| print_cpuid_ioctl(kvm, dev, "KVM_GET_SUPPORTED_CPUID", cpuid); |
| |
| struct kvm_cpuid2 cpuid_tmp = { .nent = 0 }; |
| KVM_IOCTL(vcpu_fd, KVM_SET_CPUID2, &cpuid_tmp); |
| printf("ioctl(%d<%s>, KVM_SET_CPUID2, {nent=%u, entries=[]}) = 0\n", |
| vcpu_fd, vcpu_dev, cpuid_tmp.nent); |
| |
| KVM_IOCTL(vcpu_fd, KVM_SET_CPUID2, cpuid); |
| print_cpuid_ioctl(vcpu_fd, vcpu_dev, "KVM_SET_CPUID2", cpuid); |
| |
| ioctl(vcpu_fd, KVM_SET_CPUID2, NULL); |
| printf("ioctl(%d<%s>, KVM_SET_CPUID2, NULL) = -1 EFAULT (%m)\n", |
| vcpu_fd, vcpu_dev); |
| |
| run_kvm(vcpu_fd, run, mmap_size, mem); |
| |
| puts("+++ exited with 0 +++"); |
| return 0; |
| } |
| |
| #else /* !HAVE_LINUX_KVM_H */ |
| |
| SKIP_MAIN_UNDEFINED("HAVE_LINUX_KVM_H && HAVE_STRUCT_KVM_CPUID2 && " |
| "HAVE_STRUCT_KVM_REGS && HAVE_STRUCT_KVM_SREGS && " |
| "HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION && " |
| "(__x86_64__ || __i386__)") |
| |
| # define need_print_KVM_RUN 0 |
| |
| #endif |