xlat/kvm_cap.in has been generated using the following command line:
grep '#define\s\+KVM_CAP' $linux/include/uapi/linux/kvm.h |
sed -E -e 's/^#define\s+([^ \t]+)\s*([0-9]+).*$/printf "%-40s%s\n" \1 \2/e' \
       -e 's/ {8}/\t/g; s/ +/\t/g'
* xlat/kvm_cap.in: New file.
* kvm.c: Include "xlat/kvm_cap.h".
(kvm_ioctl_decode_check_extension): New function.
(kvm_ioctl): Use it.
* tests/ioctl_kvm_run_common.c (main): Check decoding
of KVM_CHECK_EXTENSION command.
Signed-off-by: Pierre Marsais <pierre.marsais@lse.epita.fr>
		
	
		
			
				
	
	
		
			440 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			440 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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 */
 |