// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #![cfg(feature = "plugin")] use std::env::current_exe; use std::env::var_os; use std::ffi::OsString; use std::fs::remove_file; use std::io::Read; use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; use std::thread::sleep; use std::time::Duration; use base::ioctl; use base::AsRawDescriptor; use net_util::TapTCommon; use once_cell::sync::Lazy; use rand::random; use tempfile::tempfile; static TAP_AVAILABLE: Lazy = Lazy::new(|| net_util::sys::unix::Tap::new(true, false).is_ok()); struct RemovePath(PathBuf); impl Drop for RemovePath { fn drop(&mut self) { if let Err(e) = remove_file(&self.0) { eprintln!("failed to remove path: {}", e); } } } fn get_target_path() -> PathBuf { current_exe() .ok() .map(|mut path| { path.pop(); path }) .expect("failed to get crosvm binary directory") } fn get_crosvm_path() -> PathBuf { current_exe() .ok() .map(|mut path| { path.pop(); if path.ends_with("deps") { path.pop(); } path }) .expect("failed to get crosvm binary directory") } fn build_plugin(src: &str) -> RemovePath { let libcrosvm_plugin_dir = get_target_path(); let mut out_bin = libcrosvm_plugin_dir.clone(); out_bin.push(format!("plugin-test-{:08X}", random::())); let mut child = Command::new(var_os("CC").unwrap_or(OsString::from("cc"))) .args(["-Icrosvm_plugin", "-pthread", "-o"]) // crosvm.h location and set output path. .arg(&out_bin) .arg("-L") // Path of shared object to link to. .arg(&libcrosvm_plugin_dir) .arg("-Wl,-rpath") // Search for shared object in the same path when exec'd. .arg(&libcrosvm_plugin_dir) .args(["-Wl,-rpath", "."]) // Also check current directory in case of sandboxing. .args(["-xc", "-"]) // Read source code from piped stdin. .arg("-lcrosvm_plugin") .stdin(Stdio::piped()) .spawn() .expect("failed to spawn compiler"); let stdin = child.stdin.as_mut().expect("failed to open stdin"); stdin .write_all(src.as_bytes()) .expect("failed to write source to stdin"); let status = child.wait().expect("failed to wait for compiler"); assert!(status.success(), "failed to build plugin"); RemovePath(out_bin) } fn run_plugin(bin_path: &Path, with_sandbox: bool) { let mut crosvm_path = get_crosvm_path(); crosvm_path.push("crosvm"); let mut cmd = Command::new(crosvm_path); cmd.args([ "run", "-c", "1", "--seccomp-policy-dir", "tests", "--plugin", ]) .arg( bin_path .canonicalize() .expect("failed to canonicalize plugin path"), ); if *TAP_AVAILABLE { cmd.args([ "--host-ip", "100.115.92.5", "--netmask", "255.255.255.252", "--mac", "de:21:e8:47:6b:6a", ]); } if !with_sandbox { cmd.arg("--disable-sandbox"); } 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) => { assert!(status.success()); return; } None => sleep(Duration::from_millis(100)), } } child.kill().expect("failed to kill crosvm"); panic!("crosvm process has timed out"); } fn test_plugin(src: &str) { let bin_path = build_plugin(src); // Run with and without the sandbox enabled. run_plugin(&bin_path.0, false); run_plugin(&bin_path.0, true); } fn keep_fd_on_exec(f: &F) { unsafe { ioctl(f, 0x5450 /* FIONCLEX */); } } /// Takes assembly source code and returns the resulting assembly code. fn build_assembly(src: &str) -> Vec { // Creates a file with the assembly source code in it. let mut in_file = tempfile().expect("failed to create tempfile"); keep_fd_on_exec(&in_file); in_file.write_all(src.as_bytes()).unwrap(); // Creates a file that will hold the nasm output. let mut out_file = tempfile().expect("failed to create tempfile"); keep_fd_on_exec(&out_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_file.as_raw_descriptor())) .args(["-f", "bin", "-o"]) .arg(format!("/proc/self/fd/{}", out_file.as_raw_descriptor())) .status() .expect("failed to spawn assembler"); assert!(status.success()); let mut out_bytes = Vec::new(); out_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() { #[allow(clippy::format_push_string)] 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")); } #[ignore] // TODO(b/239094055): fix the SIGSTOP usage that stops the cargo test runner #[test] fn test_hint() { test_plugin(include_str!("plugin_hint.c")); } #[test] fn test_async_write() { test_plugin(include_str!("plugin_async_write.c")); } #[test] fn test_dirty_log() { test_plugin(include_str!("plugin_dirty_log.c")); } #[test] fn test_ioevent() { test_plugin(include_str!("plugin_ioevent.c")); } #[test] fn test_irqfd() { test_plugin(include_str!("plugin_irqfd.c")); } #[test] fn test_extensions() { test_plugin(include_str!("plugin_extensions.c")); } #[test] fn test_supported_cpuid() { test_plugin(include_str!("plugin_supported_cpuid.c")); } // b:223675792 #[ignore] #[test] fn test_enable_cap() { test_plugin(include_str!("plugin_enable_cap.c")); } #[test] fn test_msr_index_list() { test_plugin(include_str!("plugin_msr_index_list.c")); } #[test] fn test_vm_state_manipulation() { test_plugin(include_str!("plugin_vm_state.c")); } #[test] fn test_vcpu_pause() { test_plugin(include_str!("plugin_vcpu_pause.c")); } #[test] fn test_net_config() { if *TAP_AVAILABLE { test_plugin(include_str!("plugin_net_config.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); } #[test] fn test_xcrs() { let mini_plugin = MiniPlugin { assembly_src: "org 0x1000 bits 16 mov byte [0x3000], 1", src: r#" #define XCR0_VALUE 0x1 #define KILL_ADDRESS 0x3000 int g_kill_evt; struct kvm_xcrs g_xcrs; 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) { struct kvm_xcrs xcrs = {}; xcrs.nr_xcrs = 1; xcrs.xcrs[0].value = XCR0_VALUE; crosvm_vcpu_set_xcrs(vcpu, &xcrs); 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_xcrs(vcpu, &g_xcrs); write(g_kill_evt, &dummy, sizeof(dummy)); return 1; } return 0; } int check_result(struct crosvm *vcpu, void *mem) { if (g_xcrs.xcrs[0].value != XCR0_VALUE) { fprintf(stderr, "xcr0 register has unexpected value: 0x%x\n", g_xcrs.xcrs[0].value); return 1; } return 0; }"#, ..Default::default() }; test_mini_plugin(&mini_plugin); } #[test] fn test_msrs() { let mini_plugin = MiniPlugin { assembly_src: "org 0x1000 bits 16 rdmsr mov [0x0], eax mov [0x4], edx mov ecx, ebx mov eax, [0x8] mov edx, [0xc] wrmsr mov byte [es:0], 1", src: r#" #define MSR1_INDEX 0x00000174 #define MSR1_DATA 1 #define MSR2_INDEX 0x00000175 #define MSR2_DATA 2 #define KILL_ADDRESS 0x3000 int g_kill_evt; uint32_t g_msr2_count; struct kvm_msr_entry g_msr2; 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); ((uint64_t*)mem)[1] = MSR2_DATA; return 0; } int handle_vpcu_init(struct crosvm_vcpu *vcpu, struct kvm_regs *regs, struct kvm_sregs *sregs) { regs->rcx = MSR1_INDEX; regs->rbx = MSR2_INDEX; sregs->es.base = KILL_ADDRESS; struct kvm_msr_entry msr1 = {0}; msr1.index = MSR1_INDEX; msr1.data = MSR1_DATA; crosvm_vcpu_set_msrs(vcpu, 1, &msr1); 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; g_msr2.index = MSR2_INDEX; crosvm_vcpu_get_msrs(vcpu, 1, &g_msr2, &g_msr2_count); write(g_kill_evt, &dummy, sizeof(dummy)); return 1; } return 0; } int check_result(struct crosvm *vcpu, void *mem) { uint64_t msr1_data = ((uint64_t*)mem)[0]; if (msr1_data != MSR1_DATA) { fprintf(stderr, "msr1 has unexpected value: 0x%x\n", msr1_data); return 1; } if (g_msr2_count != 1) { fprintf(stderr, "incorrect number of returned MSRSs: %d\n", g_msr2_count); return 1; } if (g_msr2.data != MSR2_DATA) { fprintf(stderr, "msr2 has unexpected value: 0x%x\n", g_msr2.data); return 1; } return 0; }"#, ..Default::default() }; test_mini_plugin(&mini_plugin); } #[test] fn test_cpuid() { let mini_plugin = MiniPlugin { assembly_src: "org 0x1000 bits 16 push eax push ecx cpuid mov [0x0], eax mov [0x4], ebx mov [0x8], ecx mov [0xc], edx pop ecx pop eax add ecx, 1 cpuid mov [0x10], eax mov [0x14], ebx mov [0x18], ecx mov [0x1c], edx mov byte [es:0], 1", src: r#" #define ENTRY1_INDEX 0 #define ENTRY1_EAX 0x40414243 #define ENTRY1_EBX 0x50515253 #define ENTRY1_ECX 0x60616263 #define ENTRY1_EDX 0x71727374 #define ENTRY2_INDEX 1 #define ENTRY2_EAX 0xAABBCCDD #define ENTRY2_EBX 0xEEFF0011 #define ENTRY2_ECX 0x22334455 #define ENTRY2_EDX 0x66778899 #define KILL_ADDRESS 0x3000 int g_kill_evt; struct kvm_msr_entry g_msr2; 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->rax = ENTRY1_INDEX; regs->rcx = 0; regs->rsp = 0x1000; sregs->es.base = KILL_ADDRESS; struct kvm_cpuid_entry2 entries[2]; entries[0].function = 0; entries[0].index = ENTRY1_INDEX; entries[0].flags = KVM_CPUID_FLAG_SIGNIFCANT_INDEX; entries[0].eax = ENTRY1_EAX; entries[0].ebx = ENTRY1_EBX; entries[0].ecx = ENTRY1_ECX; entries[0].edx = ENTRY1_EDX; entries[1].function = 0; entries[1].index = ENTRY2_INDEX; entries[1].flags = KVM_CPUID_FLAG_SIGNIFCANT_INDEX; entries[1].eax = ENTRY2_EAX; entries[1].ebx = ENTRY2_EBX; entries[1].ecx = ENTRY2_ECX; entries[1].edx = ENTRY2_EDX; return crosvm_vcpu_set_cpuid(vcpu, 2, entries); } 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; write(g_kill_evt, &dummy, sizeof(dummy)); return 1; } return 0; } int check_result(struct crosvm *vcpu, void *memory) { uint32_t *mem = (uint32_t*)memory; if (mem[0] != ENTRY1_EAX) { fprintf(stderr, "entry 1 eax has unexpected value: 0x%x\n", mem[0]); return 1; } if (mem[1] != ENTRY1_EBX) { fprintf(stderr, "entry 1 ebx has unexpected value: 0x%x\n", mem[1]); return 1; } if (mem[2] != ENTRY1_ECX) { fprintf(stderr, "entry 1 ecx has unexpected value: 0x%x\n", mem[2]); return 1; } if (mem[3] != ENTRY1_EDX) { fprintf(stderr, "entry 1 edx has unexpected value: 0x%x\n", mem[3]); return 1; } if (mem[4] != ENTRY2_EAX) { fprintf(stderr, "entry 2 eax has unexpected value: 0x%x\n", mem[4]); return 1; } if (mem[5] != ENTRY2_EBX) { fprintf(stderr, "entry 2 ebx has unexpected value: 0x%x\n", mem[5]); return 1; } if (mem[6] != ENTRY2_ECX) { fprintf(stderr, "entry 2 ecx has unexpected value: 0x%x\n", mem[6]); return 1; } if (mem[7] != ENTRY2_EDX) { fprintf(stderr, "entry 2 edx has unexpected value: 0x%x\n", mem[7]); return 1; } return 0; }"#, ..Default::default() }; test_mini_plugin(&mini_plugin); } #[test] fn test_vcpu_state_manipulation() { let mini_plugin = MiniPlugin { assembly_src: "org 0x1000 bits 16 mov byte [0x3000], 1", src: r#" #define KILL_ADDRESS 0x3000 int g_kill_evt; bool success = false; 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) { int ret; struct kvm_lapic_state lapic; ret = crosvm_vcpu_get_lapic_state(vcpu, &lapic); if (ret < 0) { fprintf(stderr, "failed to get initial LAPIC state: %d\n", ret); return 1; } ret = crosvm_vcpu_set_lapic_state(vcpu, &lapic); if (ret < 0) { fprintf(stderr, "failed to update LAPIC state: %d\n", ret); return 1; } ret = crosvm_vcpu_get_lapic_state(vcpu, &lapic); if (ret < 0) { fprintf(stderr, "failed to get updated LAPIC state: %d\n", ret); return 1; } struct kvm_mp_state mp_state; ret = crosvm_vcpu_get_mp_state(vcpu, &mp_state); if (ret < 0) { fprintf(stderr, "failed to get initial MP state: %d\n", ret); return 1; } ret = crosvm_vcpu_set_mp_state(vcpu, &mp_state); if (ret < 0) { fprintf(stderr, "failed to update MP state: %d\n", ret); return 1; } struct kvm_vcpu_events events; ret = crosvm_vcpu_get_vcpu_events(vcpu, &events); if (ret < 0) { fprintf(stderr, "failed to get VCPU events: %d\n", ret); return 1; } ret = crosvm_vcpu_set_vcpu_events(vcpu, &events); if (ret < 0) { fprintf(stderr, "failed to set VCPU events: %d\n", ret); return 1; } success = true; 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; write(g_kill_evt, &dummy, sizeof(dummy)); return 1; } return 0; } int check_result(struct crosvm *vcpu, void *mem) { if (!success) { fprintf(stderr, "test failed\n"); return 1; } return 0; }"#, ..Default::default() }; test_mini_plugin(&mini_plugin); }