mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-25 05:03:05 +00:00
1dab58a2cf
This search/replace updates all copyright notices to drop the "All rights reserved", Use "ChromiumOS" instead of "Chromium OS" and drops the trailing dots. This fulfills the request from legal and unifies our notices. ./tools/health-check has been updated to only accept this style. BUG=b:246579983 TEST=./tools/health-check Change-Id: I87a80701dc651f1baf4820e5cc42469d7c5f5bf7 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3894243 Reviewed-by: Daniel Verkamp <dverkamp@chromium.org> Commit-Queue: Dennis Kempin <denniskempin@google.com>
323 lines
8.5 KiB
C
323 lines
8.5 KiB
C
/*
|
|
* Copyright 2020 The ChromiumOS Authors
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/kvm.h>
|
|
#include <linux/memfd.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
|
|
#include "crosvm.h"
|
|
|
|
#define KILL_ADDRESS 0x3f9
|
|
|
|
#ifndef F_LINUX_SPECIFIC_BASE
|
|
#define F_LINUX_SPECIFIC_BASE 1024
|
|
#endif
|
|
|
|
#ifndef F_ADD_SEALS
|
|
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
|
|
#endif
|
|
|
|
#ifndef F_SEAL_SHRINK
|
|
#define F_SEAL_SHRINK 0x0002
|
|
#endif
|
|
|
|
const uint8_t code[] = {
|
|
// Set a non-zero value for HV_X64_MSR_GUEST_OS_ID
|
|
// to enable hypercalls.
|
|
|
|
// mov edx, 0xffffffff
|
|
0x66, 0xba, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
// mov eax, 0xffffffff
|
|
0x66, 0xb8, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
// mov ecx, 0x40000000 # HV_X64_MSR_GUEST_OS_ID
|
|
0x66, 0xb9, 0x00, 0x00, 0x00, 0x40,
|
|
|
|
// wrmsr
|
|
0x0f, 0x30,
|
|
|
|
// Establish page at 0x2000 as the hypercall page.
|
|
|
|
// mov edx, 0x00000000
|
|
0x66, 0xba, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
// mov eax, 0x00002001 # lowest bit is enable bit
|
|
0x66, 0xb8, 0x01, 0x20, 0x00, 0x00,
|
|
|
|
// mov ecx, 0x40000001 # HV_X64_MSR_HYPERCALL
|
|
0x66, 0xb9, 0x01, 0x00, 0x00, 0x40,
|
|
|
|
// wrmsr
|
|
0x0f, 0x30,
|
|
|
|
// We can't test generic hypercalls since they're
|
|
// defined to UD for processors running in real mode.
|
|
|
|
// for HV_X64_MSR_CONTROL:
|
|
// edx:eax gets transferred as 'control'
|
|
|
|
// mov edx, 0x05060708
|
|
0x66, 0xba, 0x08, 0x07, 0x06, 0x05,
|
|
|
|
// mov eax, 0x01020304
|
|
0x66, 0xb8, 0x04, 0x03, 0x02, 0x01,
|
|
|
|
// mov ecx, 0x40000080 # HV_X64_MSR_SCONTROL
|
|
0x66, 0xb9, 0x80, 0x00, 0x00, 0x40,
|
|
|
|
// wrmsr
|
|
0x0f, 0x30,
|
|
|
|
// Establish page at 0x3000 as the evt_page.
|
|
|
|
// mov edx, 0x00000000
|
|
0x66, 0xba, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
// mov eax, 0x00003000
|
|
0x66, 0xb8, 0x00, 0x30, 0x00, 0x00,
|
|
|
|
// mov ecx, 0x40000082 # HV_X64_MSR_SIEFP
|
|
0x66, 0xb9, 0x82, 0x00, 0x00, 0x40,
|
|
|
|
// wrmsr
|
|
0x0f, 0x30,
|
|
|
|
// Establish page at 0x4000 as the 'msg_page'.
|
|
|
|
// mov edx, 0x00000000
|
|
0x66, 0xba, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
// mov eax, 0x00004000
|
|
0x66, 0xb8, 0x00, 0x40, 0x00, 0x00,
|
|
|
|
// mov ecx, 0x40000083 # HV_X64_MSR_SIMP
|
|
0x66, 0xb9, 0x83, 0x00, 0x00, 0x40,
|
|
|
|
// wrmsr
|
|
0x0f, 0x30,
|
|
|
|
// Request a kill.
|
|
|
|
// mov dx, 0x3f9
|
|
0xba, 0xf9, 0x03,
|
|
|
|
// mov al, 0x1
|
|
0xb0, 0x01,
|
|
|
|
// out dx, al
|
|
0xee,
|
|
|
|
// hlt
|
|
0xf4
|
|
};
|
|
|
|
int check_synic_access(struct crosvm_vcpu* vcpu, struct crosvm_vcpu_event *evt,
|
|
uint32_t msr, uint64_t control, uint64_t evt_page,
|
|
uint64_t msg_page, const char *phase) {
|
|
if (evt->kind != CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC) {
|
|
fprintf(stderr, "Got incorrect exit type before %s: %d\n", phase,
|
|
evt->kind);
|
|
return 1;
|
|
}
|
|
if (evt->hyperv_synic.msr != msr ||
|
|
evt->hyperv_synic._reserved != 0 ||
|
|
evt->hyperv_synic.control != control ||
|
|
evt->hyperv_synic.evt_page != evt_page ||
|
|
evt->hyperv_synic.msg_page != msg_page) {
|
|
fprintf(stderr, "Got unexpected synic message after %s: "
|
|
"0x%x vs 0x%x, 0x%lx vs 0x%lx, 0x%lx vs 0x%lx, "
|
|
"0x%lx vs 0x%lx\n",
|
|
phase, msr, evt->hyperv_synic.msr,
|
|
control, evt->hyperv_synic.control,
|
|
evt_page, evt->hyperv_synic.evt_page,
|
|
msg_page, evt->hyperv_synic.msg_page);
|
|
return 1;
|
|
}
|
|
|
|
if (crosvm_vcpu_resume(vcpu) != 0) {
|
|
fprintf(stderr, "Failed to resume after %s\n", phase);
|
|
return 1;
|
|
}
|
|
|
|
if (crosvm_vcpu_wait(vcpu, evt) != 0) {
|
|
fprintf(stderr, "Failed to wait after %s\n", phase);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
struct crosvm* crosvm = NULL;
|
|
uint64_t cap_args[4] = {0};
|
|
|
|
int ret = crosvm_connect(&crosvm);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to connect to crosvm: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT,
|
|
KILL_ADDRESS, 1);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to reserve kill port: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
// VM mem layout:
|
|
// null page, code page, hypercall page, synic evt_page, synic msg_page
|
|
int mem_size = 0x4000;
|
|
int mem_fd = syscall(SYS_memfd_create, "guest_mem",
|
|
MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
|
if (mem_fd < 0) {
|
|
fprintf(stderr, "failed to create guest memfd: %d\n", errno);
|
|
return 1;
|
|
}
|
|
ret = ftruncate(mem_fd, mem_size);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to set size of guest memory: %d\n", errno);
|
|
return 1;
|
|
}
|
|
uint8_t *mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
mem_fd, 0x0);
|
|
if (mem == MAP_FAILED) {
|
|
fprintf(stderr, "failed to mmap guest memory: %d\n", errno);
|
|
return 1;
|
|
}
|
|
fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK);
|
|
memcpy(mem, code, sizeof(code));
|
|
|
|
// Before MSR verify hypercall page is zero
|
|
int i;
|
|
for (i = 0; i < 5; ++i) {
|
|
if (mem[0x1000 + i]) {
|
|
fprintf(stderr, "Hypercall page isn't zero\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
struct crosvm_memory *mem_obj;
|
|
ret = crosvm_create_memory(crosvm, mem_fd, 0x0, mem_size, 0x1000,
|
|
false, false, &mem_obj);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to create memory in crosvm: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
struct crosvm_vcpu* vcpu = NULL;
|
|
ret = crosvm_get_vcpu(crosvm, 0, &vcpu);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to get vcpu #0: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
ret = crosvm_start(crosvm);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to start vm: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
struct crosvm_vcpu_event evt = {0};
|
|
ret = crosvm_vcpu_wait(vcpu, &evt);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to wait for vm start: %d\n", ret);
|
|
return 1;
|
|
}
|
|
if (evt.kind != CROSVM_VCPU_EVENT_KIND_INIT) {
|
|
fprintf(stderr, "Got unexpected exit type: %d\n", evt.kind);
|
|
return 1;
|
|
}
|
|
|
|
ret = crosvm_enable_capability(crosvm, 0, 0, cap_args);
|
|
if (ret != -EINVAL) {
|
|
fprintf(stderr, "Unexpected crosvm_enable_capability result: %d\n",
|
|
ret);
|
|
return 1;
|
|
}
|
|
|
|
ret = crosvm_vcpu_enable_capability(vcpu, KVM_CAP_HYPERV_SYNIC, 0,
|
|
cap_args);
|
|
if (ret) {
|
|
fprintf(stderr, "crosvm_vcpu_enable_capability() failed: %d\n", ret);
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
struct kvm_sregs sregs = {0};
|
|
crosvm_vcpu_get_sregs(vcpu, &sregs);
|
|
sregs.cs.base = 0;
|
|
sregs.cs.selector = 0;
|
|
sregs.es.base = 0;
|
|
sregs.es.selector = 0;
|
|
crosvm_vcpu_set_sregs(vcpu, &sregs);
|
|
|
|
struct kvm_regs regs = {0};
|
|
crosvm_vcpu_get_regs(vcpu, ®s);
|
|
regs.rip = 0x1000;
|
|
regs.rflags = 2;
|
|
crosvm_vcpu_set_regs(vcpu, ®s);
|
|
}
|
|
|
|
if (crosvm_vcpu_resume(vcpu) != 0) {
|
|
fprintf(stderr, "Failed to resume after init\n");
|
|
return 1;
|
|
}
|
|
|
|
if (crosvm_vcpu_wait(vcpu, &evt) != 0) {
|
|
fprintf(stderr, "Failed to wait after init\n");
|
|
return 1;
|
|
}
|
|
if (check_synic_access(vcpu, &evt, 0x40000080, 0x506070801020304, 0, 0,
|
|
"synic msg #1")) {
|
|
return 1;
|
|
}
|
|
|
|
// After first MSR verify hypercall page is non-zero
|
|
uint8_t value = 0;
|
|
for (i = 0; i < 5; ++i) {
|
|
value |= mem[0x1000+i];
|
|
}
|
|
if (value == 0) {
|
|
fprintf(stderr, "Hypercall page is still zero\n");
|
|
return 1;
|
|
}
|
|
|
|
if (check_synic_access(vcpu, &evt, 0x40000082, 0x506070801020304, 0x3000,
|
|
0, "synic msg #2")) {
|
|
return 1;
|
|
}
|
|
|
|
if (check_synic_access(vcpu, &evt, 0x40000083, 0x506070801020304, 0x3000,
|
|
0x4000, "synic msg #3")) {
|
|
return 1;
|
|
}
|
|
|
|
if (evt.kind != CROSVM_VCPU_EVENT_KIND_IO_ACCESS) {
|
|
fprintf(stderr, "Got incorrect exit type after synic #3: %d\n",
|
|
evt.kind);
|
|
return 1;
|
|
}
|
|
if (evt.io_access.address_space != CROSVM_ADDRESS_SPACE_IOPORT ||
|
|
evt.io_access.address != KILL_ADDRESS ||
|
|
!evt.io_access.is_write ||
|
|
evt.io_access.length != 1 ||
|
|
evt.io_access.data[0] != 1) {
|
|
fprintf(stderr, "Didn't see kill request from VM\n");
|
|
return 1;
|
|
}
|
|
|
|
fprintf(stderr, "Saw kill request from VM, exiting\n");
|
|
|
|
return 0;
|
|
}
|