/* * 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_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 <linux/kvm.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 const char vcpu_dev[] = "anon_inode:kvm-vcpu"; 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 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, {cs={base=%#jx, limit=%u, selector=%u" ", type=%u, present=%u, dpl=%u, db=%u, s=%u, l=%u, g=%u, avl=%u}" ", ...}) = 0\n", vcpu_fd, vcpu_dev, (uintmax_t) sregs.cs.base, sregs.cs.limit, sregs.cs.selector, sregs.cs.type, sregs.cs.present, sregs.cs.dpl, sregs.cs.db, sregs.cs.s, sregs.cs.l, sregs.cs.g, sregs.cs.avl); sregs.cs.base = 0; sregs.cs.selector = 0; KVM_IOCTL(vcpu_fd, KVM_SET_SREGS, &sregs); printf("ioctl(%d<%s>, KVM_SET_SREGS, {cs={base=%#jx, limit=%u" ", selector=%u, type=%u, present=%u, dpl=%u, db=%u, s=%u" ", l=%u, g=%u, avl=%u}, ...}) = 0\n", vcpu_fd, vcpu_dev, (uintmax_t) sregs.cs.base, sregs.cs.limit, sregs.cs.selector, sregs.cs.type, sregs.cs.present, sregs.cs.dpl, sregs.cs.db, sregs.cs.s, sregs.cs.l, sregs.cs.g, sregs.cs.avl); /* * 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, {rax=%#jx, ..." ", rsp=%#jx, rbp=%#jx, ..., rip=%#jx, rflags=%#jx}) = 0\n", vcpu_fd, vcpu_dev, (uintmax_t) regs.rax, (uintmax_t) regs.rsp, (uintmax_t) regs.rbp, (uintmax_t) regs.rip, (uintmax_t) regs.rflags); /* 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); printf("ioctl(%d<%s>, KVM_RUN, 0) = 0\n", vcpu_fd, vcpu_dev); 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); } } } 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); 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); 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"); 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_REGS && " "HAVE_STRUCT_KVM_SREGS && " "HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION && " "(__x86_64__ || __i386__)") #endif