/* * Support for decoding of KVM_* ioctl commands. * * Copyright (c) 2017 Masatake YAMATO <yamato@redhat.com> * Copyright (c) 2017 Red Hat, Inc. * Copyright (c) 2017-2018 The strace developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "defs.h" #ifdef HAVE_LINUX_KVM_H # include <linux/kvm.h> # include "print_fields.h" # include "arch_kvm.c" # include "xmalloc.h" # include "mmap_cache.h" struct vcpu_info { struct vcpu_info *next; int fd; int cpuid; long mmap_addr; unsigned long mmap_len; bool resolved; }; static bool dump_kvm_run_structure; static struct vcpu_info * vcpu_find(struct tcb *const tcp, int fd) { for (struct vcpu_info *vcpu_info = tcp->vcpu_info_list; vcpu_info; vcpu_info = vcpu_info->next) if (vcpu_info->fd == fd) return vcpu_info; return NULL; } static struct vcpu_info * vcpu_alloc(struct tcb *const tcp, int fd, int cpuid) { struct vcpu_info *vcpu_info = xcalloc(1, sizeof(*vcpu_info)); vcpu_info->fd = fd; vcpu_info->cpuid = cpuid; vcpu_info->next = tcp->vcpu_info_list; tcp->vcpu_info_list = vcpu_info; return vcpu_info; } void kvm_vcpu_info_free(struct tcb *tcp) { struct vcpu_info *head, *next; for (head = tcp->vcpu_info_list; head; head = next) { next = head->next; free(head); } tcp->vcpu_info_list = NULL; } static void vcpu_register(struct tcb *const tcp, int fd, int cpuid) { if (fd < 0) return; struct vcpu_info *vcpu_info = vcpu_find(tcp, fd); if (!vcpu_info) vcpu_info = vcpu_alloc(tcp, fd, cpuid); else if (vcpu_info->cpuid != cpuid) { vcpu_info->cpuid = cpuid; vcpu_info->resolved = false; } } static bool is_map_for_file(struct mmap_cache_entry_t *map_info, void *data) { /* major version for anon inode may be given in get_anon_bdev() * in linux kernel. * * *p = MKDEV(0, dev & MINORMASK); *-----------------^ */ return map_info->binary_filename && map_info->major == 0 && strcmp(map_info->binary_filename, data) == 0; } static unsigned long map_len(struct mmap_cache_entry_t *map_info) { return map_info->start_addr < map_info->end_addr ? map_info->end_addr - map_info->start_addr : 0; } #define VCPU_DENTRY_PREFIX "anon_inode:kvm-vcpu:" static struct vcpu_info* vcpu_get_info(struct tcb *const tcp, int fd) { struct vcpu_info *vcpu_info = vcpu_find(tcp, fd); struct mmap_cache_entry_t *map_info; const char *cpuid_str; enum mmap_cache_rebuild_result mc_stat = mmap_cache_rebuild_if_invalid(tcp, __func__); if (mc_stat == MMAP_CACHE_REBUILD_NOCACHE) return NULL; if (vcpu_info && vcpu_info->resolved) { if (mc_stat == MMAP_CACHE_REBUILD_READY) return vcpu_info; else { map_info = mmap_cache_search(tcp, vcpu_info->mmap_addr); if (map_info) { cpuid_str = STR_STRIP_PREFIX(map_info->binary_filename, VCPU_DENTRY_PREFIX); if (cpuid_str != map_info->binary_filename) { int cpuid = string_to_uint(cpuid_str); if (cpuid < 0) return NULL; if (vcpu_info->cpuid == cpuid) return vcpu_info; } } /* The vcpu vma may be mremap'ed. */ vcpu_info->resolved = false; } } /* Slow path: !vcpu_info || !vcpu_info->resolved */ char path[PATH_MAX + 1]; cpuid_str = path; if (getfdpath(tcp, fd, path, sizeof(path)) >= 0) cpuid_str = STR_STRIP_PREFIX(path, VCPU_DENTRY_PREFIX); if (cpuid_str == path) map_info = NULL; else map_info = mmap_cache_search_custom(tcp, is_map_for_file, path); if (map_info) { int cpuid = string_to_uint(cpuid_str); if (cpuid < 0) return NULL; if (!vcpu_info) vcpu_info = vcpu_alloc(tcp, fd, cpuid); else if (vcpu_info->cpuid != cpuid) vcpu_info->cpuid = cpuid; vcpu_info->mmap_addr = map_info->start_addr; vcpu_info->mmap_len = map_len(map_info); vcpu_info->resolved = true; return vcpu_info; } return NULL; } static int kvm_ioctl_create_vcpu(struct tcb *const tcp, const kernel_ulong_t arg) { uint32_t cpuid = arg; if (entering(tcp)) { tprintf(", %u", cpuid); if (dump_kvm_run_structure) return 0; } else if (!syserror(tcp)) { vcpu_register(tcp, tcp->u_rval, cpuid); } return RVAL_IOCTL_DECODED | RVAL_FD; } # ifdef HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION # include "xlat/kvm_mem_flags.h" static int kvm_ioctl_set_user_memory_region(struct tcb *const tcp, const kernel_ulong_t arg) { struct kvm_userspace_memory_region u_memory_region; tprints(", "); if (umove_or_printaddr(tcp, arg, &u_memory_region)) return RVAL_IOCTL_DECODED; PRINT_FIELD_U("{", u_memory_region, slot); PRINT_FIELD_FLAGS(", ", u_memory_region, flags, kvm_mem_flags, "KVM_MEM_???"); PRINT_FIELD_X(", ", u_memory_region, guest_phys_addr); PRINT_FIELD_U(", ", u_memory_region, memory_size); PRINT_FIELD_X(", ", u_memory_region, userspace_addr); tprints("}"); return RVAL_IOCTL_DECODED; } # endif /* HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION */ # ifdef HAVE_STRUCT_KVM_REGS static int kvm_ioctl_decode_regs(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { struct kvm_regs regs; if (code == KVM_GET_REGS && entering(tcp)) return 0; tprints(", "); if (!umove_or_printaddr(tcp, arg, ®s)) arch_print_kvm_regs(tcp, arg, ®s); return RVAL_IOCTL_DECODED; } # endif /* HAVE_STRUCT_KVM_REGS */ # ifdef HAVE_STRUCT_KVM_CPUID2 # include "xlat/kvm_cpuid_flags.h" static bool print_kvm_cpuid_entry(struct tcb *const tcp, void* elem_buf, size_t elem_size, void* data) { const struct kvm_cpuid_entry2 *entry = elem_buf; PRINT_FIELD_X("{", *entry, function); PRINT_FIELD_X(", ", *entry, index); PRINT_FIELD_FLAGS(", ", *entry, flags, kvm_cpuid_flags, "KVM_CPUID_FLAG_???"); PRINT_FIELD_X(", ", *entry, eax); PRINT_FIELD_X(", ", *entry, ebx); PRINT_FIELD_X(", ", *entry, ecx); PRINT_FIELD_X(", ", *entry, edx); tprints("}"); return true; } static int kvm_ioctl_decode_cpuid2(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { struct kvm_cpuid2 cpuid; if (entering(tcp) && (code == KVM_GET_SUPPORTED_CPUID # ifdef KVM_GET_EMULATED_CPUID || code == KVM_GET_EMULATED_CPUID # endif )) return 0; tprints(", "); if (!umove_or_printaddr(tcp, arg, &cpuid)) { PRINT_FIELD_U("{", cpuid, nent); tprints(", entries="); if (abbrev(tcp)) { tprints("["); if (cpuid.nent) tprints("..."); tprints("]"); } else { struct kvm_cpuid_entry2 entry; print_array(tcp, arg + sizeof(cpuid), cpuid.nent, &entry, sizeof(entry), tfetch_mem, print_kvm_cpuid_entry, NULL); } tprints("}"); } return RVAL_IOCTL_DECODED; } # endif /* HAVE_STRUCT_KVM_CPUID2 */ # ifdef HAVE_STRUCT_KVM_SREGS static int kvm_ioctl_decode_sregs(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { struct kvm_sregs sregs; if (code == KVM_GET_SREGS && entering(tcp)) return 0; tprints(", "); if (!umove_or_printaddr(tcp, arg, &sregs)) arch_print_kvm_sregs(tcp, arg, &sregs); return RVAL_IOCTL_DECODED; } # endif /* HAVE_STRUCT_KVM_SREGS */ # include "xlat/kvm_cap.h" static int kvm_ioctl_decode_check_extension(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { tprints(", "); printxval_index(kvm_cap, arg, "KVM_CAP_???"); return RVAL_IOCTL_DECODED; } # include "xlat/kvm_exit_reason.h" static void kvm_ioctl_run_attach_auxstr(struct tcb *const tcp, struct vcpu_info *info) { static struct kvm_run vcpu_run_struct; if (info->mmap_len < sizeof(vcpu_run_struct)) return; if (umove(tcp, info->mmap_addr, &vcpu_run_struct) < 0) return; tcp->auxstr = xlat_idx(kvm_exit_reason, ARRAY_SIZE(kvm_exit_reason) - 1, vcpu_run_struct.exit_reason); if (!tcp->auxstr) tcp->auxstr = "KVM_EXIT_???"; } static int kvm_ioctl_decode_run(struct tcb *const tcp) { if (entering(tcp)) return 0; int r = RVAL_DECODED; if (syserror(tcp)) return r; if (dump_kvm_run_structure) { tcp->auxstr = NULL; int fd = tcp->u_arg[0]; struct vcpu_info *info = vcpu_get_info(tcp, fd); if (info) { kvm_ioctl_run_attach_auxstr(tcp, info); if (tcp->auxstr) r |= RVAL_STR; } } return r; } int kvm_ioctl(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { switch (code) { case KVM_CREATE_VCPU: return kvm_ioctl_create_vcpu(tcp, arg); # ifdef HAVE_STRUCT_KVM_USERSPACE_MEMORY_REGION case KVM_SET_USER_MEMORY_REGION: return kvm_ioctl_set_user_memory_region(tcp, arg); # endif # ifdef HAVE_STRUCT_KVM_REGS case KVM_SET_REGS: case KVM_GET_REGS: return kvm_ioctl_decode_regs(tcp, code, arg); # endif # ifdef HAVE_STRUCT_KVM_SREGS case KVM_SET_SREGS: case KVM_GET_SREGS: return kvm_ioctl_decode_sregs(tcp, code, arg); # endif # ifdef HAVE_STRUCT_KVM_CPUID2 case KVM_SET_CPUID2: case KVM_GET_SUPPORTED_CPUID: # ifdef KVM_GET_EMULATED_CPUID case KVM_GET_EMULATED_CPUID: # endif return kvm_ioctl_decode_cpuid2(tcp, code, arg); # endif case KVM_CHECK_EXTENSION: return kvm_ioctl_decode_check_extension(tcp, code, arg); case KVM_CREATE_VM: return RVAL_DECODED | RVAL_FD; case KVM_RUN: return kvm_ioctl_decode_run(tcp); case KVM_GET_VCPU_MMAP_SIZE: case KVM_GET_API_VERSION: default: return RVAL_DECODED; } } void kvm_run_structure_decoder_init(void) { dump_kvm_run_structure = true; mmap_cache_enable(); } #endif /* HAVE_LINUX_KVM_H */