diff options
-rw-r--r-- | Cargo.lock | 10 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | crosvm_plugin/Cargo.toml | 2 | ||||
-rw-r--r-- | crosvm_plugin/crosvm.h | 7 | ||||
-rw-r--r-- | crosvm_plugin/src/lib.rs | 26 | ||||
-rw-r--r-- | kvm/src/lib.rs | 40 | ||||
-rw-r--r-- | plugin_proto/Cargo.toml | 2 | ||||
-rw-r--r-- | plugin_proto/protos/plugin.proto | 2 | ||||
-rw-r--r-- | src/plugin/vcpu.rs | 13 | ||||
-rw-r--r-- | tests/mini_plugin_template.c | 159 | ||||
-rw-r--r-- | tests/plugins.rs | 174 |
11 files changed, 421 insertions, 15 deletions
diff --git a/Cargo.lock b/Cargo.lock index 056855e..216fdc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ name = "crosvm" version = "0.1.0" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crosvm_plugin 0.5.0", + "crosvm_plugin 0.6.0", "data_model 0.1.0", "devices 0.1.0", "io_jail 0.1.0", @@ -28,7 +28,7 @@ dependencies = [ "kvm_sys 0.1.0", "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", "net_util 0.1.0", - "plugin_proto 0.5.0", + "plugin_proto 0.6.0", "protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "qcow 0.1.0", "qcow_utils 0.1.0", @@ -41,12 +41,12 @@ dependencies = [ [[package]] name = "crosvm_plugin" -version = "0.5.0" +version = "0.6.0" dependencies = [ "kvm 0.1.0", "kvm_sys 0.1.0", "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "plugin_proto 0.5.0", + "plugin_proto 0.6.0", "protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "sys_util 0.1.0", ] @@ -160,7 +160,7 @@ dependencies = [ [[package]] name = "plugin_proto" -version = "0.5.0" +version = "0.6.0" dependencies = [ "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 3f63e6e..d00c58a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ x86_64 = { path = "x86_64" } [dev-dependencies] rand = "=0.3.20" +sys_util = { path = "sys_util" } diff --git a/crosvm_plugin/Cargo.toml b/crosvm_plugin/Cargo.toml index 7b6491a..6b37961 100644 --- a/crosvm_plugin/Cargo.toml +++ b/crosvm_plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crosvm_plugin" -version = "0.5.0" +version = "0.6.0" authors = ["The Chromium OS Authors"] [lib] diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h index 58f7aff..16dc3a5 100644 --- a/crosvm_plugin/crosvm.h +++ b/crosvm_plugin/crosvm.h @@ -47,7 +47,7 @@ extern "C" { * do not indicate anything about what version of crosvm is running. */ #define CROSVM_API_MAJOR 0 -#define CROSVM_API_MINOR 5 +#define CROSVM_API_MINOR 6 #define CROSVM_API_PATCH 0 enum crosvm_address_space { @@ -414,6 +414,11 @@ int crosvm_vcpu_get_fpu(struct crosvm_vcpu*, struct kvm_fpu*); /* Sets the state of the vcpu's floating point unint. */ int crosvm_vcpu_set_fpu(struct crosvm_vcpu*, const struct kvm_fpu*); +/* Gets the state of the vcpu's debug registers. */ +int crosvm_vcpu_get_debugregs(struct crosvm_vcpu*, struct kvm_debugregs*); +/* Sets the state of the vcpu's debug registers */ +int crosvm_vcpu_set_debugregs(struct crosvm_vcpu*, const struct kvm_debugregs*); + #ifdef __cplusplus } #endif diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index 1fb02a1..1482853 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -43,7 +43,7 @@ use sys_util::Scm; use kvm::dirty_log_bitmap_size; -use kvm_sys::{kvm_regs, kvm_sregs, kvm_fpu}; +use kvm_sys::{kvm_regs, kvm_sregs, kvm_fpu, kvm_debugregs}; use plugin_proto::*; @@ -911,3 +911,27 @@ pub unsafe extern "C" fn crosvm_vcpu_set_fpu(this: *mut crosvm_vcpu, fpu: *const Err(e) => e, } } + +#[no_mangle] +pub unsafe extern "C" fn crosvm_vcpu_get_debugregs(this: *mut crosvm_vcpu, + dregs: *mut kvm_debugregs) + -> c_int { + let this = &mut *this; + let dregs = from_raw_parts_mut(dregs as *mut u8, size_of::<kvm_debugregs>()); + match this.get_state(VcpuRequest_StateSet::DEBUGREGS, dregs) { + Ok(_) => 0, + Err(e) => e, + } +} + +#[no_mangle] +pub unsafe extern "C" fn crosvm_vcpu_set_debugregs(this: *mut crosvm_vcpu, + dregs: *const kvm_debugregs) + -> c_int { + let this = &mut *this; + let dregs = from_raw_parts(dregs as *mut u8, size_of::<kvm_debugregs>()); + match this.set_state(VcpuRequest_StateSet::DEBUGREGS, dregs) { + Ok(_) => 0, + Err(e) => e, + } +} diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index 8f977af..82e8939 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -804,6 +804,32 @@ impl Vcpu { Ok(()) } + /// Gets the VCPU debug registers. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + pub fn get_debugregs(&self) -> Result<kvm_debugregs> { + // Safe because we know that our file is a VCPU fd, we know the kernel will only write the + // correct amount of memory to our pointer, and we verify the return result. + let mut regs = unsafe { std::mem::zeroed() }; + let ret = unsafe { ioctl_with_mut_ref(self, KVM_GET_DEBUGREGS(), &mut regs) }; + if ret != 0 { + return errno_result(); + } + Ok(regs) + } + + /// Sets the VCPU debug registers + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + pub fn set_debugregs(&self, dregs: &kvm_debugregs) -> Result<()> { + let ret = unsafe { + // Here we trust the kernel not to read past the end of the kvm_fpu struct. + ioctl_with_ref(self, KVM_SET_DEBUGREGS(), dregs) + }; + if ret < 0 { + return errno_result(); + } + Ok(()) + } + /// X86 specific call to setup the MSRS /// /// See the documentation for KVM_SET_MSRS. @@ -1144,6 +1170,20 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn debugregs() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap(); + let vm = Vm::new(&kvm, gm).unwrap(); + let vcpu = Vcpu::new(0, &kvm, &vm).unwrap(); + let mut dregs = vcpu.get_debugregs().unwrap(); + dregs.dr7 = 13; + vcpu.set_debugregs(&dregs).unwrap(); + let dregs2 = vcpu.get_debugregs().unwrap(); + assert_eq!(dregs.dr7, dregs2.dr7); + } + + #[test] fn vcpu_mmap_size() { let kvm = Kvm::new().unwrap(); let mmap_size = kvm.get_vcpu_mmap_size().unwrap(); diff --git a/plugin_proto/Cargo.toml b/plugin_proto/Cargo.toml index 67b8441..74082b6 100644 --- a/plugin_proto/Cargo.toml +++ b/plugin_proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plugin_proto" -version = "0.5.0" +version = "0.6.0" authors = ["The Chromium OS Authors"] build = "build.rs" diff --git a/plugin_proto/protos/plugin.proto b/plugin_proto/protos/plugin.proto index aa959fc..e287581 100644 --- a/plugin_proto/protos/plugin.proto +++ b/plugin_proto/protos/plugin.proto @@ -191,6 +191,8 @@ message VcpuRequest { SREGS = 1; // struct kvm_fpu FPU = 2; + // struct kvm_debugregs + DEBUGREGS = 3; } message GetState { diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index 8cfbdca..ce8b67d 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -16,7 +16,7 @@ use protobuf::Message; use data_model::DataInit; use kvm::Vcpu; -use kvm_sys::{kvm_regs, kvm_sregs, kvm_fpu}; +use kvm_sys::{kvm_regs, kvm_sregs, kvm_fpu, kvm_debugregs}; use plugin_proto::*; use super::*; @@ -61,6 +61,9 @@ unsafe impl DataInit for VcpuSregs {} #[derive(Copy, Clone)] struct VcpuFpu(kvm_fpu); unsafe impl DataInit for VcpuFpu {} +#[derive(Copy, Clone)] +struct VcpuDebugregs(kvm_debugregs); +unsafe impl DataInit for VcpuDebugregs {} fn get_vcpu_state(vcpu: &Vcpu, state_set: VcpuRequest_StateSet) -> SysResult<Vec<u8>> { @@ -68,6 +71,9 @@ fn get_vcpu_state(vcpu: &Vcpu, state_set: VcpuRequest_StateSet) -> SysResult<Vec VcpuRequest_StateSet::REGS => VcpuRegs(vcpu.get_regs()?).as_slice().to_vec(), VcpuRequest_StateSet::SREGS => VcpuSregs(vcpu.get_sregs()?).as_slice().to_vec(), VcpuRequest_StateSet::FPU => VcpuFpu(vcpu.get_fpu()?).as_slice().to_vec(), + VcpuRequest_StateSet::DEBUGREGS => { + VcpuDebugregs(vcpu.get_debugregs()?).as_slice().to_vec() + } }) } @@ -88,6 +94,11 @@ fn set_vcpu_state(vcpu: &Vcpu, state_set: VcpuRequest_StateSet, state: &[u8]) -> .ok_or(SysError::new(-EINVAL))? .0) } + VcpuRequest_StateSet::DEBUGREGS => { + vcpu.set_debugregs(&VcpuDebugregs::from_slice(state) + .ok_or(SysError::new(-EINVAL))? + .0) + } } } diff --git a/tests/mini_plugin_template.c b/tests/mini_plugin_template.c new file mode 100644 index 0000000..684b473 --- /dev/null +++ b/tests/mini_plugin_template.c @@ -0,0 +1,159 @@ +/* + * Copyright 2018 The Chromium OS Authors. All rights reserved. + * 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/memfd.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> + +#include "crosvm.h" + +#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 + +#define LOAD_ADDRESS {load_address} + +const uint8_t g_assembly_code[] = {{ + {assembly_code} +}}; + +/* These get defined by the code inserted below. */ +int setup_vm(struct crosvm *, void *mem); +int handle_vpcu_init(struct crosvm_vcpu *, struct kvm_regs *, struct kvm_sregs *); +int handle_vpcu_evt(struct crosvm_vcpu *, struct crosvm_vcpu_event evt); +int check_result(struct crosvm *, void *mem); +{src} + +struct vcpu_context {{ + struct crosvm_vcpu *vcpu; +}}; + +void *vcpu_thread(void *arg) {{ + struct vcpu_context *ctx = arg; + struct crosvm_vcpu *vcpu = ctx->vcpu; + struct crosvm_vcpu_event evt; + int ret; + while (crosvm_vcpu_wait(vcpu, &evt) == 0) {{ + if (evt.kind == CROSVM_VCPU_EVENT_KIND_INIT) {{ + struct kvm_regs regs; + crosvm_vcpu_get_regs(vcpu, ®s); + regs.rflags = 2; + regs.rip = LOAD_ADDRESS; + + struct kvm_sregs sregs; + crosvm_vcpu_get_sregs(vcpu, &sregs); + sregs.cs.base = 0; + sregs.cs.selector = 0; + + handle_vpcu_init(vcpu, ®s, &sregs); + crosvm_vcpu_set_regs(vcpu, ®s); + crosvm_vcpu_set_sregs(vcpu, &sregs); + }} else {{ + ret = handle_vpcu_evt(vcpu, evt); + if (ret) + return NULL; + }} + + crosvm_vcpu_resume(vcpu); + }} + + return NULL; +}} + +int main(int argc, char** argv) {{ + int i; + uint64_t dummy = 1; + struct crosvm *crosvm; + int ret = crosvm_connect(&crosvm); + if (ret) {{ + fprintf(stderr, "failed to connect to crosvm: %d\n", ret); + return 1; + }} + + int kill_evt = crosvm_get_shutdown_eventfd(crosvm); + if (kill_evt < 0) {{ + fprintf(stderr, "failed to get kill eventfd: %d\n", kill_evt); + return 1; + }} + + int mem_size = {mem_size}; + 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, 0); + 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 + LOAD_ADDRESS, g_assembly_code, sizeof(g_assembly_code)); + + struct crosvm_memory *mem_obj; + ret = crosvm_create_memory(crosvm, mem_fd, 0, mem_size, 0, false, false, &mem_obj); + if (ret) {{ + fprintf(stderr, "failed to create memory in crosvm: %d\n", ret); + return 1; + }} + + ret = setup_vm(crosvm, mem); + if (ret) + return ret; + + struct crosvm_vcpu *vcpus[32]; + struct vcpu_context ctxs[32]; + pthread_t vcpu_threads[32]; + uint32_t vcpu_count; + for (vcpu_count = 0; vcpu_count < 32; vcpu_count++) {{ + ret = crosvm_get_vcpu(crosvm, vcpu_count, &vcpus[vcpu_count]); + if (ret == -ENOENT) + break; + + if (ret) {{ + fprintf(stderr, "error while getting all vcpus: %d\n", ret); + return 1; + }} + ctxs[vcpu_count].vcpu = vcpus[vcpu_count]; + pthread_create(&vcpu_threads[vcpu_count], NULL, vcpu_thread, &ctxs[vcpu_count]); + }} + + ret = crosvm_start(crosvm); + if (ret) {{ + fprintf(stderr, "failed to tell crosvm to start: %d\n", ret); + return 1; + }} + + ret = read(kill_evt, &dummy, sizeof(dummy)); + if (ret == -1) {{ + fprintf(stderr, "failed to read kill eventfd: %d\n", errno); + return 1; + }} + + return check_result(crosvm, mem); +}} diff --git a/tests/plugins.rs b/tests/plugins.rs index 94b0767..bdefff9 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -5,18 +5,22 @@ #![cfg(feature = "plugin")] extern crate rand; +extern crate sys_util; use rand::{thread_rng, Rng}; use std::ffi::OsString; -use std::fs::remove_file; -use std::io::Write; +use std::fs::{File, remove_file}; +use std::io::{Write, Read}; use std::env::{current_exe, var_os}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::thread::sleep; +use std::os::unix::io::AsRawFd; use std::time::Duration; +use sys_util::{SharedMemory, ioctl}; + struct RemovePath(PathBuf); impl Drop for RemovePath { fn drop(&mut self) { @@ -89,9 +93,7 @@ fn run_plugin(bin_path: &Path, with_sandbox: bool) { cmd.arg("--disable-sandbox"); } - let mut child = cmd - .spawn() - .expect("failed to spawn crosvm"); + let mut child = cmd.spawn().expect("failed to spawn crosvm"); for _ in 0..12 { match child.try_wait().expect("failed to wait for crosvm") { Some(status) => { @@ -112,6 +114,97 @@ fn test_plugin(src: &str) { run_plugin(&bin_path.0, true); } +fn keep_fd_on_exec<F: AsRawFd>(f: &F) { + unsafe { + ioctl(f, 0x5450 /* FIONCLEX */); + } +} + +/// Takes assembly source code and returns the resulting assembly code. +fn build_assembly(src: &str) -> Vec<u8> { + // Creates a shared memory region with the assembly source code in it. + let in_shm = SharedMemory::new(None).unwrap(); + let mut in_shm_file: File = in_shm.into(); + keep_fd_on_exec(&in_shm_file); + in_shm_file.write_all(src.as_bytes()).unwrap(); + + // Creates a shared memory region that will hold the nasm output. + let mut out_shm_file: File = SharedMemory::new(None).unwrap().into(); + keep_fd_on_exec(&out_shm_file); + + // Runs nasm with the input and output files set to the FDs of the above shared memory regions, + // which we have preserved accross exec. + let status = Command::new("nasm") + .arg(format!("/proc/self/fd/{}", in_shm_file.as_raw_fd())) + .args(&["-f", "bin", "-o"]) + .arg(format!("/proc/self/fd/{}", out_shm_file.as_raw_fd())) + .status() + .expect("failed to spawn assembler"); + assert!(status.success()); + + let mut out_bytes = Vec::new(); + out_shm_file.read_to_end(&mut out_bytes).unwrap(); + out_bytes +} + +// Converts the input bytes to an output string in the format "0x01,0x02,0x03...". +fn format_as_hex(data: &[u8]) -> String { + let mut out = String::new(); + for (i, d) in data.iter().enumerate() { + out.push_str(&format!("0x{:02x}", d)); + if i < data.len() - 1 { + out.push(',') + } + } + out +} + +// A testing framework for creating simple plugins. +struct MiniPlugin { + // The size in bytes of the guest memory based at 0x0000. + mem_size: u64, + // The address in guest memory to load the assembly code. + load_address: u32, + // The nasm syntax 16-bit assembly code that will assembled and loaded into guest memory. + assembly_src: &'static str, + // The C source code that will be included in the mini_plugin_template.c file. This code must + // define the forward declarations above the {src} line so that the completed plugin source will + // compile. + src: &'static str, +} + +impl Default for MiniPlugin { + fn default() -> Self { + MiniPlugin { + mem_size: 0x2000, + load_address: 0x1000, + assembly_src: "hlt", + src: "", + } + } +} + +// Builds and tests the given MiniPlugin definiton. +fn test_mini_plugin(plugin: &MiniPlugin) { + // Adds a preamble to ensure the output opcodes are 16-bit real mode and the lables start at the + // load address. + let assembly_src = format!("org 0x{:x}\nbits 16\n{}", + plugin.load_address, + plugin.assembly_src); + + // Builds the assembly and convert it to a C literal array format. + let assembly = build_assembly(&assembly_src); + let assembly_hex = format_as_hex(&assembly); + + // Glues the pieces of this plugin together and tests the completed plugin. + let generated_src = format!(include_str!("mini_plugin_template.c"), + mem_size = plugin.mem_size, + load_address = plugin.load_address, + assembly_code = assembly_hex, + src = plugin.src); + test_plugin(&generated_src); +} + #[test] fn test_adder() { test_plugin(include_str!("plugin_adder.c")); @@ -131,3 +224,74 @@ fn test_ioevent() { fn test_irqfd() { test_plugin(include_str!("plugin_irqfd.c")); } + +#[test] +fn test_debugregs() { + let mini_plugin = MiniPlugin { + assembly_src: "org 0x1000 + bits 16 + mov dr0, ebx + mov eax, dr1 + mov byte [0x3000], 1", + src: r#" + #define DR1_VALUE 0x12 + #define RBX_VALUE 0xabcdef00 + #define KILL_ADDRESS 0x3000 + + int g_kill_evt; + struct kvm_regs g_regs; + struct kvm_debugregs g_dregs; + + int setup_vm(struct crosvm *crosvm, void *mem) { + g_kill_evt = crosvm_get_shutdown_eventfd(crosvm); + crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_MMIO, KILL_ADDRESS, 1); + return 0; + } + + int handle_vpcu_init(struct crosvm_vcpu *vcpu, struct kvm_regs *regs, + struct kvm_sregs *sregs) + { + regs->rbx = RBX_VALUE; + struct kvm_debugregs dregs; + crosvm_vcpu_get_debugregs(vcpu, &dregs); + dregs.db[1] = DR1_VALUE; + crosvm_vcpu_set_debugregs(vcpu, &dregs); + return 0; + } + + int handle_vpcu_evt(struct crosvm_vcpu *vcpu, struct crosvm_vcpu_event evt) { + if (evt.kind == CROSVM_VCPU_EVENT_KIND_IO_ACCESS && + evt.io_access.address_space == CROSVM_ADDRESS_SPACE_MMIO && + evt.io_access.address == KILL_ADDRESS && + evt.io_access.is_write && + evt.io_access.length == 1 && + evt.io_access.data[0] == 1) + { + uint64_t dummy = 1; + crosvm_vcpu_get_debugregs(vcpu, &g_dregs); + crosvm_vcpu_get_regs(vcpu, &g_regs); + write(g_kill_evt, &dummy, sizeof(dummy)); + return 1; + } + return 0; + } + + int check_result(struct crosvm *vcpu, void *mem) { + if (g_dregs.db[1] != DR1_VALUE) { + fprintf(stderr, "dr1 register has unexpected value: 0x%x\n", g_dregs.db[1]); + return 1; + } + if (g_dregs.db[0] != RBX_VALUE) { + fprintf(stderr, "dr0 register has unexpected value: 0x%x\n", g_dregs.db[0]); + return 1; + } + if (g_regs.rax != DR1_VALUE) { + fprintf(stderr, "eax register has unexpected value: 0x%x\n", g_regs.rax); + return 1; + } + return 0; + }"#, + ..Default::default() + }; + test_mini_plugin(&mini_plugin); +} |