diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 76cbf0b..73aab43 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,7 +6,7 @@ jobs: fmt: runs-on: ubuntu-18.04 container: - image: adamschwalm/hypervisor-build:14 + image: adamschwalm/hypervisor-build:16 options: "-u 0:0" steps: - uses: actions/checkout@v1 @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-18.04 needs: fmt container: - image: adamschwalm/hypervisor-build:14 + image: adamschwalm/hypervisor-build:16 options: "-u 0:0" steps: - uses: actions/checkout@v1 @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-18.04 needs: fmt container: - image: adamschwalm/hypervisor-build:14 + image: adamschwalm/hypervisor-build:16 options: "-u 0:0" steps: - uses: actions/checkout@v1 diff --git a/Makefile b/Makefile index fa93c50..21be92a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CARGO?=cargo BUILD_TYPE?=release -BUILD_REPO_TAG=14 +BUILD_REPO_TAG=16 DOCKER_IMAGE=adamschwalm/hypervisor-build:$(BUILD_REPO_TAG) TEST_IMAGE_REPO=https://github.com/mythril-hypervisor/build diff --git a/mythril/Cargo.lock b/mythril/Cargo.lock index 2740a16..63ee189 100644 --- a/mythril/Cargo.lock +++ b/mythril/Cargo.lock @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "linked_list_allocator" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "660b26e6156a7d00eefb19052fe1943cf5ab2f353a723a577fad6ba2f99d1f90" +checksum = "84565678e403453d1a27a0886882b3b271701e65146d972d9d7d9a4c4a0ff498" dependencies = [ "spinning_top", ] diff --git a/mythril/Cargo.toml b/mythril/Cargo.toml index e781997..da3d135 100644 --- a/mythril/Cargo.toml +++ b/mythril/Cargo.toml @@ -17,7 +17,7 @@ bitflags = "1.2.0" byteorder = { version = "1", default-features = false } num_enum = { version = "0.5.0", default-features = false } x86 = "0.34.0" -linked_list_allocator = "0.8.1" +linked_list_allocator = "0.8.6" log = { version = "0.4.8", default-features = false } multiboot = "0.3.0" multiboot2 = "0.9.0" diff --git a/mythril/src/ap_startup.S b/mythril/src/ap_startup.S index bd42735..dae967d 100644 --- a/mythril/src/ap_startup.S +++ b/mythril/src/ap_startup.S @@ -56,6 +56,9 @@ ap_startup_32: mov ds, ax mov ss, ax mov es, ax + + ; Ensure FS/GS are zeroed + xor ax, ax mov fs, ax mov gs, ax @@ -88,12 +91,6 @@ ap_startup_64: ; Load the stack provided by the bsp mov rsp, [AP_STACK_ADDR] - ; Initialize FS with our per-core index, this will be used for the - ; fast per-core access later - mov rdx, [AP_IDX] - shl rdx, 3 ; Shift the AP_IDX to allow the RPL and TI bits to be 0 - mov fs, rdx - ; See ap::ApData push qword [AP_IDX] diff --git a/mythril/src/apic.rs b/mythril/src/apic.rs index 62018d0..5eb1013 100644 --- a/mythril/src/apic.rs +++ b/mythril/src/apic.rs @@ -3,6 +3,7 @@ use crate::error::{Error, Result}; use crate::time; use crate::{declare_per_core, get_per_core, get_per_core_mut}; +use num_enum::TryFromPrimitive; use raw_cpuid::CpuId; use x86::msr; @@ -17,21 +18,21 @@ const IA32_APIC_BASE_EXD: u64 = 1 << 10; /// BSP mask const IA32_APIC_BASE_BSP: u64 = 1 << 8; -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive)] #[repr(u8)] /// ICR destination shorthand values pub enum DstShorthand { /// No shorthand used NoShorthand = 0x00, - // TODO(dlrobertson): Is there any reason to include self? AFAIK - // SELF_IPI negates the need for it. + /// Send only to myself + MySelf = 0x01, /// Broadcast including myself AllIncludingSelf = 0x02, /// Broadcast excluding myself AllExcludingSelf = 0x03, } -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive)] #[repr(u8)] /// INIT IPI Level pub enum Level { @@ -41,7 +42,7 @@ pub enum Level { Assert = 0x01, } -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive)] #[repr(u8)] /// ICR trigger modes pub enum TriggerMode { @@ -51,7 +52,7 @@ pub enum TriggerMode { Level = 0x01, } -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive)] #[repr(u8)] /// ICR mode of the Destination field pub enum DstMode { @@ -61,7 +62,7 @@ pub enum DstMode { Logical = 0x01, } -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive)] #[repr(u8)] /// ICR delivery mode pub enum DeliveryMode { diff --git a/mythril/src/boot.S b/mythril/src/boot.S index 86ff8a9..6477d2c 100644 --- a/mythril/src/boot.S +++ b/mythril/src/boot.S @@ -227,11 +227,11 @@ trampoline: mov eax, GDT64.data mov ds, eax mov es, eax - mov gs, eax mov ss, eax - ; FS stores the per-core index, which is always 0 for the BSP - mov edx, 0 + ; Ensure GS/FS are zeroed + xor edx, edx + mov gs, edx mov fs, edx jmp kmain_early diff --git a/mythril/src/emulate/memio.rs b/mythril/src/emulate/memio.rs index 365bf3e..1543511 100644 --- a/mythril/src/emulate/memio.rs +++ b/mythril/src/emulate/memio.rs @@ -3,9 +3,32 @@ use crate::memory; use crate::virtdev::{ DeviceEvent, MemReadRequest, MemWriteRequest, ResponseEventArray, }; -use crate::{vcpu, vmcs, vmexit}; +use crate::{vcpu, vm, vmcs, vmexit}; use arrayvec::ArrayVec; +use byteorder::ByteOrder; +use core::mem::size_of; use iced_x86; +use x86::bits64::paging::BASE_PAGE_SIZE; + +trait MemIoCallback: + Fn( + &mut vcpu::VCpu, + memory::GuestPhysAddr, + DeviceEvent, + &mut ResponseEventArray, +) -> Result<()> +{ +} + +impl MemIoCallback for T where + T: Fn( + &mut vcpu::VCpu, + memory::GuestPhysAddr, + DeviceEvent, + &mut ResponseEventArray, + ) -> Result<()> +{ +} macro_rules! read_register { ($out:ident, $value:expr, $type:ty) => {{ @@ -16,20 +39,6 @@ macro_rules! read_register { }}; } -macro_rules! write_register { - ($vm:ident, $vcpu:ident, $addr:expr, $responses:ident, $value:expr, $type:ty, $mask:expr) => {{ - let mut buff = <$type>::default().to_be_bytes(); - let request = MemReadRequest::new(&mut buff[..]); - $vm.dispatch_event( - $addr, - DeviceEvent::MemRead($addr, request), - $vcpu, - $responses, - )?; - $value = ($value & $mask) | <$type>::from_be_bytes(buff) as u64; - }}; -} - fn read_register_value( register: iced_x86::Register, vmcs: &vmcs::ActiveVmcs, @@ -150,6 +159,7 @@ fn do_mmio_write( guest_cpu: &mut vmexit::GuestCpuState, responses: &mut ResponseEventArray, instr: iced_x86::Instruction, + on_write: impl MemIoCallback, ) -> Result<()> { let mut res = ArrayVec::<[u8; 8]>::new(); let data = match instr.op1_kind() { @@ -181,11 +191,10 @@ fn do_mmio_write( }; let request = MemWriteRequest::new(&data[..]); - let mut vm = vcpu.vm.write(); - vm.dispatch_event( + (on_write)( + vcpu, addr, crate::virtdev::DeviceEvent::MemWrite(addr, request), - vcpu, responses, ) } @@ -196,527 +205,151 @@ fn do_mmio_read( guest_cpu: &mut vmexit::GuestCpuState, responses: &mut ResponseEventArray, instr: iced_x86::Instruction, + on_read: impl MemIoCallback, ) -> Result<()> { - let mut vm = vcpu.vm.write(); - match instr.op0_kind() { + let (reg, size, offset) = match instr.op0_kind() { iced_x86::OpKind::Register => match instr.op_register(0) { - iced_x86::Register::AL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rax, - u8, - !0xff - ), - iced_x86::Register::AX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rax, - u16, - !0xffff - ), - iced_x86::Register::EAX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rax, - u32, - !0xffffffff - ), - iced_x86::Register::RAX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rax, - u64, - 0x00 - ), - - iced_x86::Register::BL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rbx, - u8, - !0xff - ), - iced_x86::Register::BX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rbx, - u16, - !0xffff - ), - iced_x86::Register::EBX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rbx, - u32, - !0xffffffff - ), - iced_x86::Register::RBX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rbx, - u64, - 0x00 - ), - - iced_x86::Register::CL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rcx, - u8, - !0xff - ), - iced_x86::Register::CX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rcx, - u16, - !0xffff - ), - iced_x86::Register::ECX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rcx, - u32, - !0xffffffff - ), - iced_x86::Register::RCX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdx, - u64, - 0x00 - ), - - iced_x86::Register::DL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdx, - u8, - !0xff - ), - iced_x86::Register::DX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdx, - u16, - !0xffff - ), - iced_x86::Register::EDX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdx, - u32, - !0xffffffff - ), - iced_x86::Register::RDX => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdx, - u64, - 0x00 - ), - - iced_x86::Register::R8L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r8, - u8, - !0xff - ), - iced_x86::Register::R8W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r8, - u16, - !0xffff - ), - iced_x86::Register::R8D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r8, - u32, - !0xffffffff - ), - iced_x86::Register::R8 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r8, - u64, - 0x00 - ), - - iced_x86::Register::R9L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r9, - u8, - !0xff - ), - iced_x86::Register::R9W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r9, - u16, - !0xffff - ), - iced_x86::Register::R9D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r9, - u32, - !0xffffffff - ), - iced_x86::Register::R9 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r9, - u64, - 0x00 - ), - - iced_x86::Register::R10L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r10, - u8, - !0xff - ), - iced_x86::Register::R10W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r10, - u16, - !0xffff - ), - iced_x86::Register::R10D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r10, - u32, - !0xffffffff - ), - iced_x86::Register::R10 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r10, - u64, - 0x00 - ), - - iced_x86::Register::R11L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r11, - u8, - !0xff - ), - iced_x86::Register::R11W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r11, - u16, - !0xffff - ), - iced_x86::Register::R11D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r11, - u32, - !0xffffffff - ), - iced_x86::Register::R11 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r11, - u64, - 0x00 - ), - - iced_x86::Register::R12L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r12, - u8, - !0xff - ), - iced_x86::Register::R12W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r12, - u16, - !0xffff - ), - iced_x86::Register::R12D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r12, - u32, - !0xffffffff - ), - iced_x86::Register::R12 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r12, - u64, - 0x00 - ), - - iced_x86::Register::R13L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r13, - u8, - !0xff - ), - iced_x86::Register::R13W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r13, - u16, - !0xffff - ), - iced_x86::Register::R13D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r13, - u32, - !0xffffffff - ), - iced_x86::Register::R13 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r13, - u64, - 0x00 - ), - - iced_x86::Register::R14L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r14, - u8, - !0xff - ), - iced_x86::Register::R14W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r14, - u16, - !0xffff - ), - iced_x86::Register::R14D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r14, - u32, - !0xffffffff - ), - iced_x86::Register::R14 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r14, - u64, - 0x00 - ), - - iced_x86::Register::R15L => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r15, - u8, - !0xff - ), - iced_x86::Register::R15W => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r15, - u16, - !0xffff - ), - iced_x86::Register::R15D => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r15, - u32, - !0xffffffff - ), - iced_x86::Register::R15 => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.r15, - u64, - 0x00 - ), - - iced_x86::Register::DIL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdi, - u8, - !0xff - ), - iced_x86::Register::DI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdi, - u16, - !0xffff - ), - iced_x86::Register::EDI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdi, - u32, - !0xffffffff - ), - iced_x86::Register::RDI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rdi, - u64, - 0x00 - ), - - iced_x86::Register::SIL => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rsi, - u8, - !0xff - ), - iced_x86::Register::SI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rsi, - u16, - !0xffff - ), - iced_x86::Register::ESI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rsi, - u32, - !0xffffffff - ), - iced_x86::Register::RSI => write_register!( - vm, - vcpu, - addr, - responses, - guest_cpu.rsi, - u64, - 0x00 - ), + iced_x86::Register::AL => (&mut guest_cpu.rax, size_of::(), 0), + iced_x86::Register::AX => (&mut guest_cpu.rax, size_of::(), 0), + iced_x86::Register::EAX => { + (&mut guest_cpu.rax, size_of::(), 0) + } + iced_x86::Register::RAX => { + (&mut guest_cpu.rax, size_of::(), 0) + } + + iced_x86::Register::BL => (&mut guest_cpu.rbx, size_of::(), 0), + iced_x86::Register::BX => (&mut guest_cpu.rbx, size_of::(), 0), + iced_x86::Register::EBX => { + (&mut guest_cpu.rbx, size_of::(), 0) + } + iced_x86::Register::RBX => { + (&mut guest_cpu.rbx, size_of::(), 0) + } + + iced_x86::Register::CL => (&mut guest_cpu.rcx, size_of::(), 0), + iced_x86::Register::CX => (&mut guest_cpu.rcx, size_of::(), 0), + iced_x86::Register::ECX => { + (&mut guest_cpu.rcx, size_of::(), 0) + } + iced_x86::Register::RCX => { + (&mut guest_cpu.rcx, size_of::(), 0) + } + + iced_x86::Register::DL => (&mut guest_cpu.rdx, size_of::(), 0), + iced_x86::Register::DX => (&mut guest_cpu.rdx, size_of::(), 0), + iced_x86::Register::EDX => { + (&mut guest_cpu.rdx, size_of::(), 0) + } + iced_x86::Register::RDX => { + (&mut guest_cpu.rdx, size_of::(), 0) + } + + iced_x86::Register::R8L => (&mut guest_cpu.r8, size_of::(), 0), + iced_x86::Register::R8W => (&mut guest_cpu.r8, size_of::(), 0), + iced_x86::Register::R8D => (&mut guest_cpu.r8, size_of::(), 0), + iced_x86::Register::R8 => (&mut guest_cpu.r8, size_of::(), 0), + + iced_x86::Register::R9L => (&mut guest_cpu.r9, size_of::(), 0), + iced_x86::Register::R9W => (&mut guest_cpu.r9, size_of::(), 0), + iced_x86::Register::R9D => (&mut guest_cpu.r9, size_of::(), 0), + iced_x86::Register::R9 => (&mut guest_cpu.r9, size_of::(), 0), + + iced_x86::Register::R10L => { + (&mut guest_cpu.r10, size_of::(), 0) + } + iced_x86::Register::R10W => { + (&mut guest_cpu.r10, size_of::(), 0) + } + iced_x86::Register::R10D => { + (&mut guest_cpu.r10, size_of::(), 0) + } + iced_x86::Register::R10 => { + (&mut guest_cpu.r10, size_of::(), 0) + } + + iced_x86::Register::R11L => { + (&mut guest_cpu.r11, size_of::(), 0) + } + iced_x86::Register::R11W => { + (&mut guest_cpu.r11, size_of::(), 0) + } + iced_x86::Register::R11D => { + (&mut guest_cpu.r11, size_of::(), 0) + } + iced_x86::Register::R11 => { + (&mut guest_cpu.r11, size_of::(), 0) + } + + iced_x86::Register::R12L => { + (&mut guest_cpu.r12, size_of::(), 0) + } + iced_x86::Register::R12W => { + (&mut guest_cpu.r12, size_of::(), 0) + } + iced_x86::Register::R12D => { + (&mut guest_cpu.r12, size_of::(), 0) + } + iced_x86::Register::R12 => { + (&mut guest_cpu.r12, size_of::(), 0) + } + + iced_x86::Register::R13L => { + (&mut guest_cpu.r13, size_of::(), 0) + } + iced_x86::Register::R13W => { + (&mut guest_cpu.r13, size_of::(), 0) + } + iced_x86::Register::R13D => { + (&mut guest_cpu.r13, size_of::(), 0) + } + iced_x86::Register::R13 => { + (&mut guest_cpu.r13, size_of::(), 0) + } + + iced_x86::Register::R14L => { + (&mut guest_cpu.r14, size_of::(), 0) + } + iced_x86::Register::R14W => { + (&mut guest_cpu.r14, size_of::(), 0) + } + iced_x86::Register::R14D => { + (&mut guest_cpu.r14, size_of::(), 0) + } + iced_x86::Register::R14 => { + (&mut guest_cpu.r14, size_of::(), 0) + } + + iced_x86::Register::R15L => { + (&mut guest_cpu.r15, size_of::(), 0) + } + iced_x86::Register::R15W => { + (&mut guest_cpu.r15, size_of::(), 0) + } + iced_x86::Register::R15D => { + (&mut guest_cpu.r15, size_of::(), 0) + } + iced_x86::Register::R15 => { + (&mut guest_cpu.r15, size_of::(), 0) + } + + iced_x86::Register::DIL => (&mut guest_cpu.rdi, size_of::(), 0), + iced_x86::Register::DI => (&mut guest_cpu.rdi, size_of::(), 0), + iced_x86::Register::EDI => { + (&mut guest_cpu.rdi, size_of::(), 0) + } + iced_x86::Register::RDI => { + (&mut guest_cpu.rdi, size_of::(), 0) + } + + iced_x86::Register::SIL => (&mut guest_cpu.rsi, size_of::(), 0), + iced_x86::Register::SI => (&mut guest_cpu.rsi, size_of::(), 0), + iced_x86::Register::ESI => { + (&mut guest_cpu.rsi, size_of::(), 0) + } + iced_x86::Register::RSI => { + (&mut guest_cpu.rsi, size_of::(), 0) + } register => { return Err(Error::InvalidValue(format!( @@ -728,25 +361,50 @@ fn do_mmio_read( _ => return Err(Error::NotSupported), }; + let mut arr = [0u8; size_of::()]; + let request = MemReadRequest::new(&mut arr[..size]); + (on_read)(vcpu, addr, DeviceEvent::MemRead(addr, request), responses)?; + + let (value, mask) = match size { + 1 => (arr[0] as u64, ((u8::MAX as u64) << (offset * 8))), + 2 => ( + byteorder::LittleEndian::read_u16(&arr[..size]) as u64, + (u16::MAX as u64) << (offset * 8), + ), + 4 => ( + byteorder::LittleEndian::read_u32(&arr[..size]) as u64, + (u32::MAX as u64) << (offset * 8), + ), + 8 => ( + byteorder::LittleEndian::read_u64(&arr[..size]) as u64, + (u64::MAX as u64) << (offset * 8), + ), + _ => unreachable!(), + }; + + *reg &= !mask; + *reg |= value << (offset * 8); + Ok(()) } -pub fn handle_ept_violation( +fn process_memio_op( + addr: memory::GuestPhysAddr, vcpu: &mut vcpu::VCpu, guest_cpu: &mut vmexit::GuestCpuState, - _exit: vmexit::EptInformation, responses: &mut ResponseEventArray, + on_read: impl MemIoCallback, + on_write: impl MemIoCallback, ) -> Result<()> { let instruction_len = vcpu .vmcs .read_field(vmcs::VmcsField::VmExitInstructionLen)?; let ip = vcpu.vmcs.read_field(vmcs::VmcsField::GuestRip)?; - let mut vm = vcpu.vm.write(); let ip_addr = memory::GuestVirtAddr::new(ip, &vcpu.vmcs)?; - let view = memory::GuestAddressSpaceViewMut::from_vmcs( + let view = memory::GuestAddressSpaceView::from_vmcs( &vcpu.vmcs, - &mut vm.guest_space, + &vcpu.vm.guest_space, )?; let bytes = view.read_bytes( @@ -754,7 +412,6 @@ pub fn handle_ept_violation( instruction_len as usize, memory::GuestAccess::Read(memory::PrivilegeLevel(0)), )?; - drop(vm); let efer = vcpu.vmcs.read_field(vmcs::VmcsField::GuestIa32Efer)?; // TODO: 16bit support @@ -765,21 +422,16 @@ pub fn handle_ept_violation( decoder.set_ip(ip); let instr = decoder.decode(); - let addr = memory::GuestPhysAddr::new( - vcpu.vmcs - .read_field(vmcs::VmcsField::GuestPhysicalAddress)?, - ); - // For now, just assume everything is like MOV. This is obviously very // incomplete. if instr.op0_kind() == iced_x86::OpKind::Memory || instr.op0_kind() == iced_x86::OpKind::Memory64 { - do_mmio_write(addr, vcpu, guest_cpu, responses, instr)?; + do_mmio_write(addr, vcpu, guest_cpu, responses, instr, on_write)?; } else if instr.op1_kind() == iced_x86::OpKind::Memory || instr.op1_kind() == iced_x86::OpKind::Memory64 { - do_mmio_read(addr, vcpu, guest_cpu, responses, instr)?; + do_mmio_read(addr, vcpu, guest_cpu, responses, instr, on_read)?; } else { return Err(Error::InvalidValue(format!( "Unsupported mmio instruction: {:?} (rip=0x{:x}, bytes={:?})", @@ -788,6 +440,103 @@ pub fn handle_ept_violation( bytes, ))); } - Ok(()) } + +pub fn handle_ept_violation( + vcpu: &mut vcpu::VCpu, + guest_cpu: &mut vmexit::GuestCpuState, + _exit: vmexit::EptInformation, + responses: &mut ResponseEventArray, +) -> Result<()> { + fn on_ept_violation( + vcpu: &mut vcpu::VCpu, + addr: memory::GuestPhysAddr, + event: DeviceEvent, + responses: &mut ResponseEventArray, + ) -> Result<()> { + vcpu.vm.dispatch_event(addr, event, vcpu, responses) + } + + let addr = memory::GuestPhysAddr::new( + vcpu.vmcs + .read_field(vmcs::VmcsField::GuestPhysicalAddress)?, + ); + + process_memio_op( + addr, + vcpu, + guest_cpu, + responses, + on_ept_violation, + on_ept_violation, + ) +} + +pub fn handle_apic_access( + vcpu: &mut vcpu::VCpu, + guest_cpu: &mut vmexit::GuestCpuState, + exit: vmexit::ApicAccessInformation, + responses: &mut ResponseEventArray, +) -> Result<()> { + fn address_to_apic_offset(addr: memory::GuestPhysAddr) -> u16 { + let addr = addr.as_u64(); + let apic_base = vm::GUEST_LOCAL_APIC_ADDR.as_u64(); + assert!( + addr >= apic_base && addr < (apic_base + BASE_PAGE_SIZE as u64) + ); + ((addr - apic_base) / size_of::() as u64) as u16 + } + + fn on_apic_read( + vcpu: &mut vcpu::VCpu, + addr: memory::GuestPhysAddr, + event: DeviceEvent, + _responses: &mut ResponseEventArray, + ) -> Result<()> { + let offset = address_to_apic_offset(addr); + let res = vcpu.local_apic.register_read(offset)?; + let mut bytes = res.to_be_bytes(); + + match event { + DeviceEvent::MemRead(_, mut req) => { + req.as_mut_slice().copy_from_slice(&mut bytes[..]); + Ok(()) + } + _ => return Err(Error::NotSupported), + } + } + + fn on_apic_write( + vcpu: &mut vcpu::VCpu, + addr: memory::GuestPhysAddr, + event: DeviceEvent, + _responses: &mut ResponseEventArray, + ) -> Result<()> { + let offset = address_to_apic_offset(addr); + let mut bytes = [0u8; 4]; + let value = match event { + DeviceEvent::MemWrite(_, req) => { + bytes[..].copy_from_slice(req.as_slice()); + u32::from_be_bytes(bytes) + } + _ => return Err(Error::NotSupported), + }; + + vcpu.local_apic + .register_write(vcpu.vm.clone(), offset, value) + } + + let addr = vm::GUEST_LOCAL_APIC_ADDR + + (exit.offset.expect("Apic access with no offset") as usize + * size_of::()); + + process_memio_op( + addr, + vcpu, + guest_cpu, + responses, + on_apic_read, + on_apic_write, + ) +} diff --git a/mythril/src/emulate/portio.rs b/mythril/src/emulate/portio.rs index bc76637..16b2cdd 100644 --- a/mythril/src/emulate/portio.rs +++ b/mythril/src/emulate/portio.rs @@ -13,8 +13,6 @@ fn emulate_outs( exit: vmexit::IoInstructionInformation, responses: &mut ResponseEventArray, ) -> Result<()> { - let mut vm = vcpu.vm.write(); - let linear_addr = vcpu.vmcs.read_field(vmcs::VmcsField::GuestLinearAddress)?; let guest_addr = memory::GuestVirtAddr::new(linear_addr, &vcpu.vmcs)?; @@ -23,9 +21,9 @@ fn emulate_outs( // assume that is requires supervisor let access = memory::GuestAccess::Read(memory::PrivilegeLevel(0)); - let view = memory::GuestAddressSpaceViewMut::from_vmcs( + let view = memory::GuestAddressSpaceView::from_vmcs( &vcpu.vmcs, - &mut vm.guest_space, + &vcpu.vm.guest_space, )?; // FIXME: The direction we read is determined by the DF flag (I think) @@ -39,7 +37,7 @@ fn emulate_outs( // FIXME: Actually test for REP for chunk in bytes.chunks_exact(exit.size as usize) { let request = PortWriteRequest::try_from(chunk)?; - vm.dispatch_event( + vcpu.vm.dispatch_event( port, DeviceEvent::PortWrite(port, request), vcpu, @@ -59,8 +57,6 @@ fn emulate_ins( exit: vmexit::IoInstructionInformation, responses: &mut ResponseEventArray, ) -> Result<()> { - let mut vm = vcpu.vm.write(); - let linear_addr = vcpu.vmcs.read_field(vmcs::VmcsField::GuestLinearAddress)?; let guest_addr = memory::GuestVirtAddr::new(linear_addr, &vcpu.vmcs)?; @@ -69,7 +65,7 @@ fn emulate_ins( let mut bytes = vec![0u8; guest_cpu.rcx as usize]; for chunk in bytes.chunks_exact_mut(exit.size as usize) { let request = PortReadRequest::try_from(chunk)?; - vm.dispatch_event( + vcpu.vm.dispatch_event( port, DeviceEvent::PortRead(port, request), vcpu, @@ -77,9 +73,9 @@ fn emulate_ins( )?; } - let mut view = memory::GuestAddressSpaceViewMut::from_vmcs( + let view = memory::GuestAddressSpaceView::from_vmcs( &vcpu.vmcs, - &mut vm.guest_space, + &vcpu.vm.guest_space, )?; view.write_bytes(guest_addr, &bytes, access)?; @@ -98,12 +94,11 @@ pub fn emulate_portio( (exit.port, exit.input, exit.size, exit.string); if !string { - let mut vm = vcpu.vm.write(); if !input { let arr = (guest_cpu.rax as u32).to_be_bytes(); let request = PortWriteRequest::try_from(&arr[4 - size as usize..])?; - vm.dispatch_event( + vcpu.vm.dispatch_event( port, DeviceEvent::PortWrite(port, request), vcpu, @@ -113,7 +108,7 @@ pub fn emulate_portio( let mut arr = [0u8; 4]; let request = PortReadRequest::try_from(&mut arr[4 - size as usize..])?; - vm.dispatch_event( + vcpu.vm.dispatch_event( port, DeviceEvent::PortRead(port, request), vcpu, diff --git a/mythril/src/interrupt/mod.rs b/mythril/src/interrupt/mod.rs index c83e1f3..8ea69d5 100644 --- a/mythril/src/interrupt/mod.rs +++ b/mythril/src/interrupt/mod.rs @@ -1,8 +1,15 @@ pub mod idt; -pub const UART_VECTOR: u8 = 36; -pub const TIMER_VECTOR: u8 = 48; -pub const IPC_VECTOR: u8 = 49; +pub mod vector { + pub const UART: u8 = 36; + pub const TIMER: u8 = 48; + pub const IPC: u8 = 49; +} + +pub mod gsi { + pub const PIT: u32 = 0; + pub const UART: u32 = 4; +} pub unsafe fn enable_interrupts() { llvm_asm!("sti" :::: "volatile"); diff --git a/mythril/src/ioapic.rs b/mythril/src/ioapic.rs index 02b0daa..9acd301 100644 --- a/mythril/src/ioapic.rs +++ b/mythril/src/ioapic.rs @@ -98,6 +98,7 @@ pub unsafe fn init_ioapics(madt: &MADT) -> Result<()> { }, _ => None, }) { + debug!("Registering IOAPIC for gsi_base = 0x{:x}", ioapic.gsi_base); ioapics.push(ioapic); } RoAfterInit::init(&IOAPICS, ioapics); diff --git a/mythril/src/kmain.rs b/mythril/src/kmain.rs index 839c511..4482591 100644 --- a/mythril/src/kmain.rs +++ b/mythril/src/kmain.rs @@ -36,27 +36,26 @@ extern "C" { // Temporary helper function to create a vm fn build_vm( + vm_id: u32, cfg: &config::UserVmConfig, info: &BootInfo, add_uart: bool, -) -> Arc> { +) -> Arc { let physical_config = if add_uart == false { vm::PhysicalDeviceConfig::default() } else { vm::PhysicalDeviceConfig { - serial: Some( + serial: RwLock::new(Some( physdev::com::Uart8250::new(0x3f8) .expect("Failed to create UART"), - ), - ps2_keyboard: None, + )), + ps2_keyboard: RwLock::new(None), } }; - let mut config = vm::VirtualMachineConfig::new( - cfg.cpus.clone(), - cfg.memory, - physical_config, - ); + let mut config = + vm::VirtualMachineConfig::new(&cfg.cpus, cfg.memory, physical_config) + .expect("Failed to create VirtualMachineConfig"); let mut acpi = acpi::rsdp::RSDPBuilder::<[_; 1024]>::new( ManagedMap::Owned(BTreeMap::new()), @@ -64,12 +63,17 @@ fn build_vm( let mut madt = acpi::madt::MADTBuilder::<[_; 8]>::new(); madt.set_ica(vm::GUEST_LOCAL_APIC_ADDR.as_u64() as u32); - madt.add_ics(acpi::madt::Ics::LocalApic { - apic_id: 0, - apic_uid: 0, - flags: acpi::madt::LocalApicFlags::ENABLED, - }) - .expect("Failed to add APIC to MADT"); + + for core in cfg.cpus.iter() { + madt.add_ics(acpi::madt::Ics::LocalApic { + // TODO(alschwalm): we should assign an actual APIC id here, + // instead of using the core id + apic_id: core.raw as u8, + apic_uid: 0, + flags: acpi::madt::LocalApicFlags::ENABLED, + }) + .expect("Failed to add APIC to MADT"); + } madt.add_ics(acpi::madt::Ics::IoApic { ioapic_id: 0, ioapic_addr: 0xfec00000 as *mut u8, @@ -153,13 +157,15 @@ fn build_vm( device_map.register_device(fw_cfg_builder.build()).unwrap(); - vm::VirtualMachine::new(cfg.cpus[0].raw, config, info) - .expect("Failed to create vm") + vm::VirtualMachine::new(vm_id, config, info).expect("Failed to create vm") } #[no_mangle] -pub extern "C" fn ap_entry(_ap_data: &ap::ApData) -> ! { - unsafe { interrupt::idt::ap_init() }; +pub extern "C" fn ap_entry(ap_data: &ap::ApData) -> ! { + unsafe { + percore::init_segment_for_core(ap_data.idx); + interrupt::idt::ap_init() + }; let local_apic = apic::LocalApic::init().expect("Failed to initialize local APIC"); @@ -171,8 +177,6 @@ pub extern "C" fn ap_entry(_ap_data: &ap::ApData) -> ! { local_apic.version() ); - unsafe { interrupt::enable_interrupts() }; - vcpu::mp_entry_point() } @@ -182,7 +186,7 @@ static LOGGER: logger::DirectLogger = logger::DirectLogger::new(); pub unsafe extern "C" fn kmain_early(multiboot_info_addr: usize) -> ! { // Setup our (com0) logger log::set_logger(&LOGGER) - .map(|()| log::set_max_level(log::LevelFilter::Info)) + .map(|()| log::set_max_level(log::LevelFilter::Debug)) .expect("Failed to set logger"); let boot_info = if IS_MULTIBOOT_BOOT == 1 { @@ -221,10 +225,6 @@ unsafe fn kmain(mut boot_info: BootInfo) -> ! { .rsdt() .expect("Failed to read RSDT"); - // Initialize the BSP local APIC - let local_apic = - apic::LocalApic::init().expect("Failed to initialize local APIC"); - let madt_sdt = rsdt.find_entry(b"APIC").expect("No MADT found"); let madt = acpi::madt::MADT::new(&madt_sdt); @@ -240,14 +240,18 @@ unsafe fn kmain(mut boot_info: BootInfo) -> ! { }) .collect::>(); - ioapic::init_ioapics(&madt).expect("Failed to initialize IOAPICs"); - ioapic::map_gsi_vector(4, interrupt::UART_VECTOR, 0) - .expect("Failed to map com0 gsi"); - percore::init_sections(apic_ids.len()) .expect("Failed to initialize per-core sections"); - let mut builder = vm::VirtualMachineBuilder::new(); + // Initialize the BSP local APIC + let local_apic = + apic::LocalApic::init().expect("Failed to initialize local APIC"); + + ioapic::init_ioapics(&madt).expect("Failed to initialize IOAPICs"); + ioapic::map_gsi_vector(interrupt::gsi::UART, interrupt::vector::UART, 0) + .expect("Failed to map com0 gsi"); + + let mut builder = vm::VirtualMachineSetBuilder::new(); let raw_cfg = boot_info .find_module("mythril.cfg") @@ -261,7 +265,7 @@ unsafe fn kmain(mut boot_info: BootInfo) -> ! { for (num, vm) in mythril_cfg.vms.into_iter().enumerate() { builder - .insert_machine(build_vm(&vm, &boot_info, num == 0)) + .insert_machine(build_vm(num as u32, &vm, &boot_info, num == 0)) .expect("Failed to insert new vm"); } @@ -269,13 +273,19 @@ unsafe fn kmain(mut boot_info: BootInfo) -> ! { debug!("AP_STARTUP address: 0x{:x}", AP_STARTUP_ADDR); - //TODO(alschwalm): Only the cores that are actually associated with a VM - // should be started for (idx, apic_id) in apic_ids.into_iter().enumerate() { if apic_id == local_apic.id() { continue; } + let core_id = percore::CoreId::from(idx as u32); + + // Do not setup cores that are not allocated to any guest + if !vm::virtual_machines().is_assigned_core_id(core_id) { + debug!("Not starting core ID '{}' because it is not assigned to a guest", core_id); + continue; + } + // Allocate a stack for the AP let stack = vec![0u8; 100 * 1024]; @@ -288,7 +298,7 @@ unsafe fn kmain(mut boot_info: BootInfo) -> ! { core::ptr::write_volatile(&mut AP_STACK_ADDR as *mut u64, stack_bottom); // Map the APIC ids to a sequential list and pass it to the AP - core::ptr::write_volatile(&mut AP_IDX as *mut u64, idx as u64); + core::ptr::write_volatile(&mut AP_IDX as *mut u64, core_id.raw as u64); // mfence to ensure that the APs see the new stack address core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); diff --git a/mythril/src/lib.rs b/mythril/src/lib.rs index 43fbe01..9e00fc5 100644 --- a/mythril/src/lib.rs +++ b/mythril/src/lib.rs @@ -49,6 +49,7 @@ pub mod time; pub mod tsc; pub mod vcpu; pub mod virtdev; +/// Top level virtual machine definition pub mod vm; pub mod vmcs; pub mod vmexit; diff --git a/mythril/src/lock/mod.rs b/mythril/src/lock/mod.rs index 7c25313..f1596ea 100644 --- a/mythril/src/lock/mod.rs +++ b/mythril/src/lock/mod.rs @@ -1 +1,9 @@ pub mod ro_after_init; + +/// Provides a hint to the processor that it is in a spin loop +#[inline(always)] +pub fn relax_cpu() { + unsafe { + llvm_asm!("rep; nop" ::: "memory"); + } +} diff --git a/mythril/src/memory.rs b/mythril/src/memory.rs index 51b6220..f178ae3 100644 --- a/mythril/src/memory.rs +++ b/mythril/src/memory.rs @@ -3,12 +3,12 @@ use crate::vmcs; use alloc::boxed::Box; use alloc::vec::Vec; use bitflags::bitflags; -use core::borrow::{Borrow, BorrowMut}; use core::convert::TryFrom; use core::default::Default; use core::fmt; use core::ops::{Add, Deref, Index, IndexMut}; use num_enum::TryFromPrimitive; +use spin::RwLock; use ux; use x86::bits64::paging::*; use x86::controlregs::Cr0; @@ -255,7 +255,7 @@ impl HostPhysFrame { } pub struct GuestAddressSpace { - root: Box, + root: RwLock>, } #[derive(Copy, Clone, Debug)] @@ -271,21 +271,26 @@ pub enum GuestAccess { impl GuestAddressSpace { pub fn new() -> Result { Ok(GuestAddressSpace { - root: Box::new(EptPml4Table::default()), + root: RwLock::new(Box::new(EptPml4Table::default())), }) } pub fn map_frame( - &mut self, + &self, guest_addr: GuestPhysAddr, host_frame: HostPhysFrame, readonly: bool, ) -> Result<()> { - map_guest_memory(&mut self.root, guest_addr, host_frame, readonly) + map_guest_memory( + &mut self.root.write(), + guest_addr, + host_frame, + readonly, + ) } pub fn map_new_frame( - &mut self, + &self, guest_addr: GuestPhysAddr, readonly: bool, ) -> Result<()> { @@ -297,7 +302,7 @@ impl GuestAddressSpace { pub fn eptp(&self) -> u64 { // //TODO: check available memory types - (&*self.root as *const _ as u64) | (4 - 1) << 3 | 6 + (&*(*self.root.read()) as *const _ as u64) | (4 - 1) << 3 | 6 } pub fn translate_linear_address( @@ -360,7 +365,7 @@ impl GuestAddressSpace { &self, addr: GuestPhysAddr, ) -> Result { - let ept_pml4e = &self.root[addr.p4_index()]; + let ept_pml4e = &self.root.read()[addr.p4_index()]; if ept_pml4e.is_unused() { return Err(Error::InvalidValue( "No PML4 entry for GuestPhysAddr".into(), @@ -440,7 +445,7 @@ impl GuestAddressSpace { } pub fn write_bytes( - &mut self, + &self, cr3: GuestPhysAddr, addr: GuestVirtAddr, mut bytes: &[u8], @@ -471,25 +476,20 @@ impl GuestAddressSpace { } } -pub type GuestAddressSpaceView<'a> = - GuestAddressSpaceWrapper<&'a GuestAddressSpace>; -pub type GuestAddressSpaceViewMut<'a> = - GuestAddressSpaceWrapper<&'a mut GuestAddressSpace>; - -pub struct GuestAddressSpaceWrapper { - space: T, +pub struct GuestAddressSpaceView<'a> { + space: &'a GuestAddressSpace, cr3: GuestPhysAddr, } -impl GuestAddressSpaceWrapper -where - T: Borrow, -{ - pub fn new(cr3: GuestPhysAddr, space: T) -> Self { +impl<'a> GuestAddressSpaceView<'a> { + pub fn new(cr3: GuestPhysAddr, space: &'a GuestAddressSpace) -> Self { Self { space, cr3 } } - pub fn from_vmcs(vmcs: &vmcs::ActiveVmcs, space: T) -> Result { + pub fn from_vmcs( + vmcs: &vmcs::ActiveVmcs, + space: &'a GuestAddressSpace, + ) -> Result { let cr3 = vmcs.read_field(vmcs::VmcsField::GuestCr3)?; let cr3 = GuestPhysAddr::new(cr3); Ok(Self { space, cr3 }) @@ -500,7 +500,7 @@ where addr: GuestVirtAddr, access: GuestAccess, ) -> Result { - self.space.borrow().frame_iter(self.cr3, addr, access) + self.space.frame_iter(self.cr3, addr, access) } pub fn read_bytes( @@ -509,9 +509,7 @@ where length: usize, access: GuestAccess, ) -> Result> { - self.space - .borrow() - .read_bytes(self.cr3, addr, length, access) + self.space.read_bytes(self.cr3, addr, length, access) } pub fn translate_linear_address( @@ -519,36 +517,24 @@ where addr: GuestVirtAddr, access: GuestAccess, ) -> Result { - self.space - .borrow() - .translate_linear_address(self.cr3, addr, access) + self.space.translate_linear_address(self.cr3, addr, access) } -} -impl GuestAddressSpaceWrapper -where - T: BorrowMut, -{ pub fn write_bytes( - &mut self, + &self, addr: GuestVirtAddr, bytes: &[u8], access: GuestAccess, ) -> Result<()> { - self.space - .borrow_mut() - .write_bytes(self.cr3, addr, bytes, access) + self.space.write_bytes(self.cr3, addr, bytes, access) } } -impl Deref for GuestAddressSpaceWrapper -where - T: Borrow, -{ - type Target = T; +impl<'a> Deref for GuestAddressSpaceView<'a> { + type Target = GuestAddressSpace; - fn deref(&self) -> &Self::Target { - &self.space + fn deref(&self) -> &'a Self::Target { + self.space } } diff --git a/mythril/src/percore.rs b/mythril/src/percore.rs index 98b9343..ecc90ca 100644 --- a/mythril/src/percore.rs +++ b/mythril/src/percore.rs @@ -11,13 +11,17 @@ use crate::error::Result; use crate::lock::ro_after_init::RoAfterInit; use alloc::vec::Vec; use core::fmt; +use x86::msr; static AP_PER_CORE_SECTIONS: RoAfterInit> = RoAfterInit::uninitialized(); +crate::declare_per_core! { + static mut CORE_ID: RoAfterInit = + RoAfterInit::uninitialized(); +} + extern "C" { - // The _value_ of the first/last byte of the .per_core section. The - // address of this symbol is the start of .per_core static PER_CORE_START: u8; static PER_CORE_END: u8; } @@ -28,21 +32,17 @@ unsafe fn per_core_section_len() -> usize { section_end as usize - section_start as usize } -unsafe fn per_core_address(symbol_addr: *const u8, core: usize) -> *const u8 { - if core == 0 { - return symbol_addr; - } - let section_len = per_core_section_len(); +unsafe fn per_core_address(symbol_addr: *const u8) -> *const u8 { let offset = symbol_addr as u64 - (&PER_CORE_START as *const _ as u64); - &AP_PER_CORE_SECTIONS[(section_len * (core - 1)) + offset as usize] - as *const u8 + let section_start = msr::rdmsr(msr::IA32_FS_BASE); + (section_start + offset) as *const u8 } /// Initialize the per-core sections /// /// This must be called after the global allocator has been -/// initialized. +/// initialized and may only be called by the BSP. pub unsafe fn init_sections(ncores: usize) -> Result<()> { let section_start = &PER_CORE_START as *const u8; let section_len = per_core_section_len(); @@ -55,9 +55,35 @@ pub unsafe fn init_sections(ncores: usize) -> Result<()> { } RoAfterInit::init(&AP_PER_CORE_SECTIONS, ap_sections); + + // Also initialize things for this core (which must be the BSP) + init_segment_for_core(0); Ok(()) } +/// Initialize this core's per-core data +/// +/// This must be called by each AP (and the BSP) before +/// the usage of any per-core variable +pub unsafe fn init_segment_for_core(core_idx: u64) { + let fs = if core_idx == 0 { + &PER_CORE_START as *const u8 as u64 + } else { + let section_len = per_core_section_len(); + (&AP_PER_CORE_SECTIONS[section_len * (core_idx - 1) as usize]) + as *const u8 as u64 + }; + + msr::wrmsr(msr::IA32_FS_BASE, fs); + + RoAfterInit::init( + crate::get_per_core_mut!(CORE_ID), + CoreId { + raw: core_idx as u32, + }, + ); +} + /// The sequential index of a core #[derive(Copy, Clone, Debug, Ord, PartialEq, PartialOrd, Eq)] pub struct CoreId { @@ -78,30 +104,20 @@ impl fmt::Display for CoreId { } /// Get this current core's sequential index +/// +/// This _must not_ be called before calling `init_segment_for_core` pub fn read_core_id() -> CoreId { - unsafe { - let value: u64; - llvm_asm!("mov [%fs], %rax" - : "={rax}"(value) - ::: "volatile"); - ((value >> 3) as u32).into() // Shift away the RPL and TI bits (they will always be 0) - } + unsafe { **crate::get_per_core!(CORE_ID) } } #[doc(hidden)] -pub unsafe fn get_pre_core_impl(t: &T) -> &T { - core::mem::transmute(per_core_address( - t as *const T as *const u8, - read_core_id().raw as usize, - )) +pub unsafe fn get_per_core_impl(t: &T) -> &T { + core::mem::transmute(per_core_address(t as *const T as *const u8)) } #[doc(hidden)] -pub unsafe fn get_pre_core_mut_impl(t: &mut T) -> &mut T { - core::mem::transmute(per_core_address( - t as *const T as *const u8, - read_core_id().raw as usize, - )) +pub unsafe fn get_per_core_mut_impl(t: &mut T) -> &mut T { + core::mem::transmute(per_core_address(t as *const T as *const u8)) } #[macro_export] @@ -109,7 +125,7 @@ macro_rules! get_per_core { ($name:ident) => { #[allow(unused_unsafe)] unsafe { - $crate::percore::get_pre_core_impl(&mut $name) + $crate::percore::get_per_core_impl(&$name) } }; } @@ -119,7 +135,7 @@ macro_rules! get_per_core_mut { ($name:ident) => { #[allow(unused_unsafe)] unsafe { - $crate::percore::get_pre_core_mut_impl(&mut $name) + $crate::percore::get_per_core_mut_impl(&mut $name) } }; } @@ -134,6 +150,12 @@ macro_rules! __declare_per_core_internal { declare_per_core!($($t)*); }; + ($(#[$attr:meta])* ($($vis:tt)*) static $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + #[link_section = ".per_core"] + $($vis)* static $N: $T = $e; + + declare_per_core!($($t)*); + }; () => () } @@ -149,5 +171,15 @@ macro_rules! declare_per_core { ($(#[$attr:meta])* pub ($($vis:tt)+) static mut $N:ident : $T:ty = $e:expr; $($t:tt)*) => { __declare_per_core_internal!($(#[$attr])* (pub ($($vis)+)) static mut $N : $T = $e; $($t)*); }; + // Rules for immutable variables + ($(#[$attr:meta])* static $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __declare_per_core_internal!($(#[$attr])* () static $N : $T = $e; $($t)*); + }; + ($(#[$attr:meta])* pub static $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __declare_per_core_internal!($(#[$attr])* (pub) static $N : $T = $e; $($t)*); + }; + ($(#[$attr:meta])* pub ($($vis:tt)+) static $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + __declare_per_core_internal!($(#[$attr])* (pub ($($vis)+)) static $N : $T = $e; $($t)*); + }; () => () } diff --git a/mythril/src/time.rs b/mythril/src/time.rs index 9c8af19..2f127c2 100644 --- a/mythril/src/time.rs +++ b/mythril/src/time.rs @@ -57,6 +57,27 @@ fn frequency() -> u64 { TIME_SRC.frequency() } +/// An interrupt to be delivered by a timer +#[derive(Clone)] +pub enum TimerInterruptType { + /// An interrupt to be delivered to the core that is running the timer. + /// For example, when virtualizing the guest local apic timer, the + /// generated interrupt is _not_ a guest GSI, but a directly delivered + /// interrupt. + Direct { + /// The interrupt vector to be delivered by the timer + vector: u8, + + /// The kind of interrupt to be delivered by the timer + kind: vcpu::InjectedInterruptType, + }, + + /// An interrupt to be delivered to the guest via a GSI. + /// For example, any hardware timer external to the core will generate + /// a GSI and be routed to a vector through the guest IO APIC + GSI(u32), +} + /// A point in time on the system in terms of the global system `TimeSource` /// /// An `Instant` can be added/subtracted with a `Duration` to produce an @@ -123,12 +144,7 @@ enum TimerMode { pub struct ReadyTimer { duration: Duration, mode: TimerMode, - - // The interrupt vector to deliver to the guest when this timer - // expires. - // TODO: Not all timers represent interrupts to deliver to the guest, - // so we will need to make this more abstract. - vector: u8, + kind: TimerInterruptType, } /// A started one-shot or periodic timer @@ -136,25 +152,25 @@ pub struct RunningTimer { duration: Duration, mode: TimerMode, started: Instant, - vector: u8, + kind: TimerInterruptType, } impl ReadyTimer { /// Create a new one-shot timer. - pub fn one_shot(duration: Duration, vector: u8) -> Self { + pub fn one_shot(duration: Duration, kind: TimerInterruptType) -> Self { Self { - duration: duration, + duration, mode: TimerMode::OneShot, - vector: vector, + kind, } } /// Create a new periodic timer. - pub fn periodic(period: Duration, vector: u8) -> Self { + pub fn periodic(period: Duration, kind: TimerInterruptType) -> Self { Self { duration: period, mode: TimerMode::Periodic, - vector: vector, + kind, } } @@ -164,7 +180,7 @@ impl ReadyTimer { duration: self.duration, mode: self.mode, started: now(), - vector: self.vector, + kind: self.kind, } } @@ -183,7 +199,7 @@ impl RunningTimer { ReadyTimer { duration: self.duration, mode: self.mode, - vector: self.vector, + kind: self.kind, } } @@ -273,7 +289,7 @@ impl TimerWheel { /// expired and will reset any periodic timers. pub fn expire_elapsed_timers( &mut self, - ) -> Result> { + ) -> Result> { let mut interrupts = vec![]; let elapsed_oneshots = self .timers @@ -288,10 +304,7 @@ impl TimerWheel { .collect::>(); for id in elapsed_oneshots { - interrupts.push(( - self.timers[&id].vector, - vcpu::InjectedInterruptType::ExternalInterrupt, - )); + interrupts.push(self.timers[&id].kind.clone()); self.timers.remove(&id); } @@ -300,10 +313,7 @@ impl TimerWheel { .iter_mut() .filter(|(_, timer)| timer.elapsed() && timer.is_periodic()) { - interrupts.push(( - timer.vector, - vcpu::InjectedInterruptType::ExternalInterrupt, - )); + interrupts.push(timer.kind.clone()); timer.reset(); } @@ -315,15 +325,15 @@ impl TimerWheel { let soonest = self .timers .values() - .map(|timer| (timer.elapses_at(), timer.vector)) - .min(); + .map(|timer| (timer.elapses_at(), &timer.kind)) + .min_by(|(time1, _), (time2, _)| time1.cmp(time2)); // TODO: we should only actually reset this if the new time // is sooner than the last time we set if let Some((when, _)) = soonest { unsafe { apic::get_local_apic_mut() - .schedule_interrupt(when, interrupt::TIMER_VECTOR); + .schedule_interrupt(when, interrupt::vector::TIMER); } } } @@ -382,10 +392,7 @@ impl TimerWheel { pub fn busy_wait(duration: core::time::Duration) { let start = now(); while now() < start + duration { - unsafe { - // Relax the cpu - llvm_asm!("rep; nop" ::: "memory"); - } + crate::lock::relax_cpu(); } } @@ -395,9 +402,10 @@ pub fn cancel_timer(id: &TimerId) -> Result<()> { if wheel.is_local_timer(id) { wheel.remove_timer(id); } else { - vm::send_vm_msg_core( + vm::virtual_machines().send_msg_core( vm::VirtualMachineMsg::CancelTimer(id.clone()), id.core_id, + true, )?; } Ok(()) @@ -406,19 +414,19 @@ pub fn cancel_timer(id: &TimerId) -> Result<()> { /// Set a one shot timer on this core pub fn set_oneshot_timer( duration: core::time::Duration, - vector: u8, + kind: TimerInterruptType, ) -> TimerId { let wheel = unsafe { get_timer_wheel_mut() }; - let timer = ReadyTimer::one_shot(duration, vector); + let timer = ReadyTimer::one_shot(duration, kind); wheel.register_timer(timer) } /// Set a periodic timer on this core pub fn set_periodic_timer( interval: core::time::Duration, - vector: u8, + kind: TimerInterruptType, ) -> TimerId { let wheel = unsafe { get_timer_wheel_mut() }; - let timer = ReadyTimer::periodic(interval, vector); + let timer = ReadyTimer::periodic(interval, kind); wheel.register_timer(timer) } diff --git a/mythril/src/vcpu.rs b/mythril/src/vcpu.rs index 2194fb4..205fb9c 100644 --- a/mythril/src/vcpu.rs +++ b/mythril/src/vcpu.rs @@ -15,7 +15,6 @@ use alloc::sync::Arc; use alloc::vec::Vec; use core::mem; use core::pin::Pin; -use spin::RwLock; use x86::controlregs::{cr0, cr3, cr4}; use x86::msr; @@ -34,12 +33,39 @@ pub fn mp_entry_point() -> ! { .expect("Failed to initialize per-core timer wheel"); } + let core_id = percore::read_core_id(); let vm = unsafe { - let id = percore::read_core_id(); - vm::get_vm_for_core_id(id) - .expect(&format!("Failed to find VM associated with {}", id)) + let vm = vm::virtual_machines() + .get_by_core_id(core_id) + .expect(&format!("Failed to find VM associated with {}", core_id)); + vm }; - let vcpu = VCpu::new(vm).expect("Failed to create vcpu"); + + let mut vcpu = VCpu::new(vm.clone()).expect("Failed to create vcpu"); + + let vm_id = vm.id; + let is_vm_bsp = vm.config.bsp_id() == core_id; + + // Increment the VM's count of ready cores + vm.notify_ready(); + + // Wait until all the cores are done with their early init + while !vm.all_cores_ready() { + crate::lock::relax_cpu(); + } + + // Cores other than this VM's BSP must wait for the INIT/SIPI to actually start + if !is_vm_bsp { + debug!("Waiting for init signal on core id '{}'", core_id); + vcpu.wait_for_init() + .expect("Error while waiting for AP init"); + } + + debug!( + "Starting core ID '{}' as part of vm id '{}'", + core_id, vm_id + ); + vcpu.launch().expect("Failed to launch vm") } @@ -63,9 +89,9 @@ pub enum InjectedInterruptType { /// ultimate handling will occur within an emulated device in the `VirtualMachine`'s /// `DeviceMap`) pub struct VCpu { - pub vm: Arc>, + pub vm: Arc, pub vmcs: vmcs::ActiveVmcs, - _local_apic: virtdev::lapic::LocalApic, + pub local_apic: virtdev::lapic::LocalApic, pending_interrupts: BTreeMap, stack: Vec, } @@ -76,7 +102,7 @@ impl VCpu { /// Note that the result must be `Pin`, as the `VCpu` pushes its own /// address on to the per-core host stack so it can be retrieved on /// VMEXIT. - pub fn new(vm: Arc>) -> Result>> { + pub fn new(vm: Arc) -> Result>> { let vmx = vmx::Vmx::enable()?; let vmcs = vmcs::Vmcs::new()?.activate(vmx)?; @@ -86,17 +112,18 @@ impl VCpu { let mut vcpu = Box::pin(Self { vm: vm, vmcs: vmcs, - _local_apic: virtdev::lapic::LocalApic::new(), + local_apic: virtdev::lapic::LocalApic::new(), stack: stack, pending_interrupts: BTreeMap::new(), }); // All VCpus in a VM must share the same address space - let eptp = vcpu.vm.read().guest_space.eptp(); + let eptp = vcpu.vm.guest_space.eptp(); vcpu.vmcs.write_field(vmcs::VmcsField::EptPointer, eptp)?; + info!("Setting eptp to 0x{:x}", eptp); // Setup access for our local apic - let apic_access_addr = vcpu.vm.read().apic_access_page.as_ptr() as u64; + let apic_access_addr = vcpu.vm.apic_access_page.as_ptr() as u64; vcpu.vmcs .write_field(vmcs::VmcsField::ApicAccessAddr, apic_access_addr)?; @@ -112,7 +139,7 @@ impl VCpu { } Self::initialize_host_vmcs(&mut vcpu.vmcs, stack_base)?; - Self::initialize_guest_vmcs(&mut vcpu.vmcs)?; + Self::initialize_guest_vmcs(&mut vcpu)?; Self::initialize_ctrl_vmcs(&mut vcpu.vmcs)?; Ok(vcpu) @@ -134,6 +161,41 @@ impl VCpu { unreachable!() } + /// Block until a StartVcpu is received by this core + /// + /// This routine will also prepare the VCpu with the information + /// received from the StartVcpu signal + pub fn wait_for_init(&mut self) -> Result<()> { + loop { + if let Some(msg) = vm::virtual_machines().recv_msg() { + match msg { + vm::VirtualMachineMsg::StartVcpu(addr) => { + debug!( + "Setting start address to 0x{:x}", + addr.as_u64() + ); + self.vmcs.write_field( + vmcs::VmcsField::GuestCsSelector, + addr.as_u64() >> 4, + )?; + self.vmcs.write_field( + vmcs::VmcsField::GuestCsBase, + 0x0000, + )?; + self.vmcs.write_field(vmcs::VmcsField::GuestRip, 0)?; + break; + } + _ => { + warn!( + "Ignoring non-startup signal on waiting guest AP" + ); + } + } + } + } + Ok(()) + } + fn initialize_host_vmcs( vmcs: &mut vmcs::ActiveVmcs, stack: u64, @@ -157,6 +219,7 @@ impl VCpu { vmcs.write_field(vmcs::VmcsField::HostSsSelector, GDT64_DATA)?; vmcs.write_field(vmcs::VmcsField::HostDsSelector, GDT64_DATA)?; vmcs.write_field(vmcs::VmcsField::HostEsSelector, GDT64_DATA)?; + vmcs.write_field(vmcs::VmcsField::HostFsSelector, GDT64_DATA)?; vmcs.write_field(vmcs::VmcsField::HostGsSelector, GDT64_DATA)?; vmcs.write_field(vmcs::VmcsField::HostTrSelector, GDT64_DATA)?; } @@ -168,11 +231,6 @@ impl VCpu { vmcs.write_field(vmcs::VmcsField::HostIdtrBase, IdtrBase::read())?; vmcs.write_field(vmcs::VmcsField::HostGdtrBase, GdtrBase::read())?; - vmcs.write_field( - vmcs::VmcsField::HostFsSelector, - (percore::read_core_id().raw as u64) << 3, // Skip the RPL and TI flags - )?; - vmcs.write_field(vmcs::VmcsField::HostFsBase, unsafe { msr::rdmsr(msr::IA32_FS_BASE) })?; @@ -193,7 +251,8 @@ impl VCpu { Ok(()) } - fn initialize_guest_vmcs(vmcs: &mut vmcs::ActiveVmcs) -> Result<()> { + fn initialize_guest_vmcs(vcpu: &mut VCpu) -> Result<()> { + let vmcs = &mut vcpu.vmcs; vmcs.write_field(vmcs::VmcsField::GuestEsSelector, 0x00)?; vmcs.write_field(vmcs::VmcsField::GuestCsSelector, 0xf000)?; vmcs.write_field(vmcs::VmcsField::GuestSsSelector, 0x00)?; @@ -233,7 +292,6 @@ impl VCpu { vmcs.write_field(vmcs::VmcsField::GuestTrArBytes, 0x008b)?; // TSS (busy) vmcs.write_field(vmcs::VmcsField::GuestInterruptibilityInfo, 0x00)?; - vmcs.write_field(vmcs::VmcsField::GuestActivityState, 0x00)?; vmcs.write_field(vmcs::VmcsField::GuestDr7, 0x00)?; vmcs.write_field(vmcs::VmcsField::GuestRsp, 0x00)?; vmcs.write_field(vmcs::VmcsField::GuestRflags, 1 << 1)?; // Reserved rflags @@ -273,6 +331,13 @@ impl VCpu { vmcs.write_field(vmcs::VmcsField::GuestRip, 0xfff0)?; + // If this is a VM BSP core, start it as active, otherwise + // it should be waiting for IPI + vmcs.write_field( + vmcs::VmcsField::GuestActivityState, + vmcs::ActivityState::Active as u64, + )?; + Ok(()) } @@ -373,6 +438,20 @@ impl VCpu { Ok(()) } + pub fn route_interrupt(&mut self, gsi: u32) -> Result<()> { + let (destination, vector, kind) = self.vm.gsi_destination(gsi)?; + if destination == percore::read_core_id() { + self.inject_interrupt(vector, kind); + Ok(()) + } else { + vm::virtual_machines().send_msg_core( + vm::VirtualMachineMsg::GuestInterrupt { vector, kind }, + destination, + true, + ) + } + } + /// Handle an arbitrary guest VMEXIT. /// /// This is the rust 'entry' point when a guest exists. @@ -391,10 +470,19 @@ impl VCpu { // Always check for expired timers unsafe { - for (vec, kind) in + for timer_event in time::get_timer_wheel_mut().expire_elapsed_timers()? { - self.inject_interrupt(vec, kind); + match timer_event { + time::TimerInterruptType::Direct { + vector, kind, .. + } => { + self.inject_interrupt(vector, kind); + } + time::TimerInterruptType::GSI(gsi) => { + self.route_interrupt(gsi)?; + } + } } } @@ -462,23 +550,42 @@ impl VCpu { Ok(()) } + fn handle_ipc(&mut self) -> Result<()> { + for msg in vm::virtual_machines().recv_all_msgs() { + match msg { + vm::VirtualMachineMsg::GrantConsole(serial) => { + *self.vm.config.physical_devices().serial.write() = + Some(serial); + } + vm::VirtualMachineMsg::CancelTimer(timer_id) => { + time::cancel_timer(&timer_id)?; + } + vm::VirtualMachineMsg::GuestInterrupt { kind, vector } => { + self.inject_interrupt(vector, kind); + } + vm::VirtualMachineMsg::StartVcpu(_) => { + warn!("Received StartVcpu signal on running VCPU"); + } + } + } + Ok(()) + } + fn handle_uart_keypress( &mut self, responses: &mut virtdev::ResponseEventArray, ) -> Result<()> { - let vm = self.vm.read(); - - let serial_info = vm + let serial_info = self + .vm .config .physical_devices() .serial + .read() .as_ref() .map(|serial| (serial.read(), serial.base_port())); - drop(vm); - let mut vm = self.vm.write(); if let Some((key, port)) = serial_info { - vm.dispatch_event( + self.vm.dispatch_event( port, virtdev::DeviceEvent::HostUartReceived(key), self, @@ -507,11 +614,17 @@ impl VCpu { guest_cpu.rdx = real_apic_base >> 32; guest_cpu.rax = real_apic_base & 0xffffffff; } - _ => unreachable!(), + msr => warn!("Attempt to read unsupported MSR 0x{:x}", msr), } self.skip_emulated_instruction()?; } - vmexit::ExitInformation::ApicAccess(_info) => { + vmexit::ExitInformation::ApicAccess(info) => { + emulate::memio::handle_apic_access( + self, + guest_cpu, + info, + &mut responses, + )?; self.skip_emulated_instruction()?; } vmexit::ExitInformation::CrAccess(info) => { @@ -544,22 +657,11 @@ impl VCpu { vmexit::ExitInformation::InterruptWindow => {} vmexit::ExitInformation::ExternalInterrupt(info) => unsafe { match info.vector { - interrupt::UART_VECTOR => { + interrupt::vector::UART => { self.handle_uart_keypress(&mut responses)? } - interrupt::IPC_VECTOR => { - let msg = - vm::recv_vm_msg().ok_or_else(|| Error::NotFound)?; - match msg { - vm::VirtualMachineMsg::GrantConsole(serial) => { - let mut vm = self.vm.write(); - vm.config.physical_devices_mut().serial = - Some(serial); - } - vm::VirtualMachineMsg::CancelTimer(timer_id) => { - time::cancel_timer(&timer_id)?; - } - } + interrupt::vector::IPC => { + self.handle_ipc()?; } _ => (), } @@ -576,34 +678,39 @@ impl VCpu { for response in responses { match response { - virtdev::DeviceEventResponse::Interrupt((vector, kind)) => { - self.inject_interrupt(vector, kind); + virtdev::DeviceEventResponse::GSI(gsi) => { + self.route_interrupt(gsi)?; } virtdev::DeviceEventResponse::NextConsole => { info!("Received Ctrl-a three times. Switching console to next VM"); - let mut vm = self.vm.write(); - let serial = vm + let serial = self + .vm .config - .physical_devices_mut() + .physical_devices() .serial + .write() .take() .ok_or_else(|| Error::NotFound)?; - let vmid = vm.id; - drop(vm); + let vmid = self.vm.id; - let next_vmid = (vmid + 1) % vm::max_vm_id(); + let next_vmid = (vmid + 1) % vm::virtual_machines().count(); - vm::send_vm_msg( + vm::virtual_machines().send_msg( vm::VirtualMachineMsg::GrantConsole(serial), next_vmid, + true, )?; - //FIXME(alschwalm): this should use the vm's bsp apicid + let next_bsp = vm::virtual_machines() + .bsp_core_id(next_vmid) + .ok_or_else(|| Error::NotFound)?; + + //FIXME(alschwalm): this should be the APIC id of the bsp, not the core id ioapic::map_gsi_vector( - 4, - interrupt::UART_VECTOR, - next_vmid as u8, + interrupt::gsi::UART, + interrupt::vector::UART, + next_bsp.raw as u8, ) .map_err(|_| { Error::DeviceError( @@ -612,8 +719,8 @@ impl VCpu { })?; } virtdev::DeviceEventResponse::GuestUartTransmitted(val) => { - let vm = self.vm.read(); - if vm.config.physical_devices().serial.is_some() { + if self.vm.config.physical_devices().serial.read().is_some() + { //TODO: This should be a write to the physical serial device let buff = &[val]; let s = alloc::string::String::from_utf8_lossy(buff); diff --git a/mythril/src/virtdev/com.rs b/mythril/src/virtdev/com.rs index 80b79b6..6d7162a 100644 --- a/mythril/src/virtdev/com.rs +++ b/mythril/src/virtdev/com.rs @@ -1,6 +1,6 @@ use crate::error::Result; +use crate::interrupt; use crate::physdev::com::*; -use crate::vcpu; use crate::virtdev::{ DeviceEvent, DeviceEventResponse, DeviceRegion, EmulatedDevice, Event, Port, }; @@ -61,13 +61,9 @@ impl EmulatedDevice for Uart8250 { fn on_event(&mut self, event: Event) -> Result<()> { match event.kind { DeviceEvent::HostUartReceived(key) => { - event.responses.push( - // IRQ4 - DeviceEventResponse::Interrupt(( - 52, - vcpu::InjectedInterruptType::ExternalInterrupt, - )), - ); + event + .responses + .push(DeviceEventResponse::GSI(interrupt::gsi::UART)); if key == 0x01 { // ctrl+a self.ctrl_a_count += 1; @@ -131,14 +127,14 @@ impl EmulatedDevice for Uart8250 { event.responses.push( DeviceEventResponse::GuestUartTransmitted(val), ); + if self .interrupt_enable_register .contains(IerFlags::THR_EMPTY_INTERRUPT) { - event.responses.push( - // IRQ4 - DeviceEventResponse::Interrupt((52, vcpu::InjectedInterruptType::ExternalInterrupt)) - ); + event.responses.push(DeviceEventResponse::GSI( + interrupt::gsi::UART, + )); } self.interrupt_identification_register = 0b10; } diff --git a/mythril/src/virtdev/ignore.rs b/mythril/src/virtdev/ignore.rs index c8a9d03..c32c42f 100644 --- a/mythril/src/virtdev/ignore.rs +++ b/mythril/src/virtdev/ignore.rs @@ -30,6 +30,8 @@ impl EmulatedDevice for IgnoredDevice { DeviceRegion::PortIo(0x2F8..=0x2F8 + 7), DeviceRegion::PortIo(0x3E8..=0x3E8 + 7), DeviceRegion::PortIo(0x2E8..=0x2E8 + 7), + // Floppy disk controller + DeviceRegion::PortIo(0x3f0..=0x3f7), ] } diff --git a/mythril/src/virtdev/lapic.rs b/mythril/src/virtdev/lapic.rs index ec06b02..0d2ba1b 100644 --- a/mythril/src/virtdev/lapic.rs +++ b/mythril/src/virtdev/lapic.rs @@ -1,8 +1,262 @@ +use crate::apic::*; +use crate::error::{Error, Result}; +use crate::memory; +use crate::percore; +use crate::vm; +use alloc::sync::Arc; +use core::convert::TryFrom; +use core::sync::atomic::AtomicU32; +use num_enum::TryFromPrimitive; + +#[derive(Debug)] +enum ApicRegisterOffset { + Simple(ApicRegisterSimpleOffset), + InterruptRequest(u16), + InterruptCommand(u16), + TriggerMode(u16), + InService(u16), +} + +#[derive(Debug, TryFromPrimitive)] +#[repr(u16)] +enum ApicRegisterSimpleOffset { + ApicId = 0x20, + ApicVersion = 0x30, + TaskPriority = 0x80, + ArbitrationPriority = 0x90, + ProcessorPriority = 0xa0, + EndOfInterrupt = 0xb0, + RemoteRead = 0xc0, + LogicalDestination = 0xd0, + DestinationFormat = 0xe0, + SpuriousInterruptVector = 0xf0, + ErrorStatus = 0x280, + LvtCorrectMachineCheckInterrupt = 0x2f0, + LvtTimer = 0x320, + LvtThermalSensor = 0x330, + LvtPerformanceMonitoringCounter = 0x340, + LvtLINT0 = 0x350, + LvtLINT1 = 0x360, + LvtError = 0x370, + TimerInitialCount = 0x380, + TimerCurrentCount = 0x390, + TimerDivideConfig = 0x3e0, +} + +/// The portion of guest local APIC state related to logical addressing +/// +/// This portion of state must be 'shared' with other cores, because the +/// state of each local APIC's logical destination registers affects how +/// logically addressed IPIs are transmitted. Therefore, this state is +/// stored in the VirtualMachine instead of in each VCpu. +pub struct LogicalApicState { + pub logical_destination: AtomicU32, + pub destination_format: AtomicU32, +} + +impl core::default::Default for LogicalApicState { + fn default() -> Self { + Self { + logical_destination: AtomicU32::new(0), + destination_format: AtomicU32::new(0), + } + } +} + +impl TryFrom for ApicRegisterOffset { + type Error = Error; + + fn try_from(value: u16) -> Result { + if value & 0b1111 != 0 { + return Err(Error::InvalidValue(format!( + "APIC register offset not aligned: 0x{:x}", + value + ))); + } + + if let Ok(simple_reg) = ApicRegisterSimpleOffset::try_from(value) { + return Ok(ApicRegisterOffset::Simple(simple_reg)); + } + + let res = match value { + 0x100..=0x170 => { + ApicRegisterOffset::InService((value - 0x100) >> 4) + } + 0x180..=0x1f0 => { + ApicRegisterOffset::TriggerMode((value - 0x180) >> 4) + } + 0x200..=0x270 => { + ApicRegisterOffset::InterruptRequest((value - 0x200) >> 4) + } + 0x300..=0x310 => { + ApicRegisterOffset::InterruptCommand((value - 0x300) >> 4) + } + offset => { + return Err(Error::InvalidValue(format!( + "Invalid APIC register offset: 0x{:x}", + offset + ))) + } + }; + + Ok(res) + } +} + #[derive(Default)] -pub struct LocalApic; +pub struct LocalApic { + icr_destination: Option, +} impl LocalApic { pub fn new() -> Self { - LocalApic {} + LocalApic { + icr_destination: None, + } + } + + fn process_sipi_request(&self, value: u32) -> Result<()> { + // TODO(alschwalm): check the destination and delivery modes to + // be sure this is actually what we should be doing. + if let Some(dest) = self.icr_destination { + let vector = value as u64 & 0xff; + let addr = memory::GuestPhysAddr::new(vector << 12); + + // FIXME(alschwalm): The destination is actually a virtual local + // apic id. We should convert that to a global core id for this. + let core_id = percore::CoreId::from(dest >> 24); + + debug!( + "Sending startup message for address = {:?} to core {}", + addr, core_id + ); + + vm::virtual_machines().send_msg_core( + vm::VirtualMachineMsg::StartVcpu(addr), + core_id, + false, + )?; + } + Ok(()) + } + + fn process_interrupt_command( + &mut self, + vm: Arc, + value: u32, + ) -> Result<()> { + let mode = DeliveryMode::try_from((value >> 8) as u8 & 0b111)?; + match mode { + // TODO: for now, just ignore the INIT signal + DeliveryMode::Init => return Ok(()), + DeliveryMode::StartUp => return self.process_sipi_request(value), + _ => (), + } + + let vector = value as u64 & 0xff; + let dst_mode = DstMode::try_from((value >> 11 & 0b1) as u8)?; + let shorthand = DstShorthand::try_from((value >> 18 & 0b11) as u8)?; + + match shorthand { + DstShorthand::AllIncludingSelf => { + warn!("Unsupported local apic command register shorthand AllIncludingSelf"); + return Ok(()); + } + DstShorthand::AllExcludingSelf => { + for core in vm.config.cpus() { + if *core == percore::read_core_id() { + continue; + } + vm::virtual_machines().send_msg_core(vm::VirtualMachineMsg::GuestInterrupt{ + kind: crate::vcpu::InjectedInterruptType::ExternalInterrupt, + vector: vector as u8 + }, *core, true)? + } + return Ok(()); + } + DstShorthand::MySelf => { + warn!("Unsupported local apic command register shorthand Self"); + return Ok(()); + } + DstShorthand::NoShorthand => (), + } + + // No shorthand was used, so we should have an ICR destination of some sort + if let Some(dest) = self.icr_destination { + match dst_mode { + DstMode::Logical => { + for core in vm.logical_apic_destination(dest)? { + // FIXME(alschwalm): we need to support sending to ourselves (I think) + if *core == percore::read_core_id() { + continue; + } + vm::virtual_machines().send_msg_core(vm::VirtualMachineMsg::GuestInterrupt{ + kind: crate::vcpu::InjectedInterruptType::ExternalInterrupt, + vector: vector as u8 + }, *core, true)? + } + } + DstMode::Physical => { + warn!("Unsupported Physical address for IPI vector=0x{:x} (dest=0x{:x}/short={:?}/mode={:?})", + vector, dest, shorthand, mode); + } + } + } else { + warn!("IPI with no icr_destination vector=0x{:x} (dest={:?}/short={:?})", + vector, dst_mode, shorthand); + } + + Ok(()) + } + + pub fn register_read(&mut self, offset: u16) -> Result { + let offset = ApicRegisterOffset::try_from(offset)?; + match offset { + ApicRegisterOffset::Simple(ApicRegisterSimpleOffset::ApicId) => { + // FIXME(alschwalm): we shouldn't really use the core id for this + Ok(percore::read_core_id().raw) + } + _ => Ok(0), + } + } + + pub fn register_write( + &mut self, + vm: Arc, + offset: u16, + value: u32, + ) -> Result<()> { + let offset = ApicRegisterOffset::try_from(offset)?; + match offset { + ApicRegisterOffset::Simple(ref simple) => match simple { + ApicRegisterSimpleOffset::EndOfInterrupt => (), + ApicRegisterSimpleOffset::LogicalDestination => { + vm.update_core_logical_destination(value); + } + _ => info!( + "Write to virtual local apic: {:?}, value=0x{:x}", + offset, value + ), + }, + ApicRegisterOffset::InterruptCommand(offset) => { + match offset { + 0 => { + self.process_interrupt_command(vm, value)?; + + // TODO(alschwalm): What is the expected behavior here? + self.icr_destination = None; + } + 1 => { + self.icr_destination = Some(value); + } + _ => unreachable!(), + } + } + _ => info!( + "Write to virtual local apic: {:?}, value=0x{:x}", + offset, value + ), + } + Ok(()) } } diff --git a/mythril/src/virtdev/mod.rs b/mythril/src/virtdev/mod.rs index 059816b..42a3bfc 100644 --- a/mythril/src/virtdev/mod.rs +++ b/mythril/src/virtdev/mod.rs @@ -1,6 +1,5 @@ use crate::error::{Error, Result}; -use crate::memory::{GuestAddressSpaceViewMut, GuestPhysAddr}; -use crate::vcpu; +use crate::memory::{GuestAddressSpaceView, GuestPhysAddr}; use alloc::collections::btree_map::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; @@ -45,19 +44,19 @@ pub enum DeviceEvent<'a> { pub enum DeviceEventResponse { GuestUartTransmitted(u8), NextConsole, - Interrupt((u8, vcpu::InjectedInterruptType)), + GSI(u32), } pub struct Event<'a> { pub kind: DeviceEvent<'a>, - pub space: GuestAddressSpaceViewMut<'a>, + pub space: GuestAddressSpaceView<'a>, pub responses: &'a mut ResponseEventArray, } impl<'a> Event<'a> { pub fn new( kind: DeviceEvent<'a>, - space: GuestAddressSpaceViewMut<'a>, + space: GuestAddressSpaceView<'a>, responses: &'a mut ResponseEventArray, ) -> Result { Ok(Event { @@ -469,6 +468,10 @@ impl<'a> MemReadRequest<'a> { pub fn as_slice(&self) -> &[u8] { self.data } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.data + } } impl<'a> fmt::Display for MemReadRequest<'a> { diff --git a/mythril/src/virtdev/pci.rs b/mythril/src/virtdev/pci.rs index ea67e33..855c211 100644 --- a/mythril/src/virtdev/pci.rs +++ b/mythril/src/virtdev/pci.rs @@ -264,15 +264,15 @@ impl EmulatedDevice for PciRootComplex { mod test { use super::*; use crate::memory::{ - GuestAddressSpace, GuestAddressSpaceViewMut, GuestPhysAddr, + GuestAddressSpace, GuestAddressSpaceView, GuestPhysAddr, }; use crate::virtdev::*; use alloc::boxed::Box; - fn define_test_view() -> GuestAddressSpaceViewMut<'static> { + fn define_test_view() -> GuestAddressSpaceView<'static> { let space: &'static mut GuestAddressSpace = Box::leak(Box::new(GuestAddressSpace::new().unwrap())); - GuestAddressSpaceViewMut::new(GuestPhysAddr::new(0), space) + GuestAddressSpaceView::new(GuestPhysAddr::new(0), space) } fn complex_ready_for_reg_read(reg: u8) -> Arc> { diff --git a/mythril/src/virtdev/pit.rs b/mythril/src/virtdev/pit.rs index f83f0f2..c517fb8 100644 --- a/mythril/src/virtdev/pit.rs +++ b/mythril/src/virtdev/pit.rs @@ -1,4 +1,5 @@ use crate::error::{Error, Result}; +use crate::interrupt; use crate::physdev::pit::*; use crate::time; use crate::virtdev::{ @@ -225,10 +226,12 @@ impl Pit8254 { // Only channel 0 produces timer interrupts if port == PIT_COUNTER_0 { - //FIXME: this value should be determined by the virtual - // PIC/APIC. Currently use the vector linux has for IRQ0 - *timer = - Some(time::set_oneshot_timer(duration, 48)); + *timer = Some(time::set_oneshot_timer( + duration, + time::TimerInterruptType::GSI( + interrupt::gsi::PIT, + ), + )); } } @@ -241,10 +244,12 @@ impl Pit8254 { *start_time = Some(time::now()); if port == PIT_COUNTER_0 { - //FIXME: this value should be determined by the virtual - // PIC/APIC. Currently use the vector linux has for IRQ0 - *timer = - Some(time::set_periodic_timer(duration, 48)); + *timer = Some(time::set_periodic_timer( + duration, + time::TimerInterruptType::GSI( + interrupt::gsi::PIT, + ), + )); } } }; diff --git a/mythril/src/virtdev/qemu_fw_cfg.rs b/mythril/src/virtdev/qemu_fw_cfg.rs index 1bb05fd..d505f47 100644 --- a/mythril/src/virtdev/qemu_fw_cfg.rs +++ b/mythril/src/virtdev/qemu_fw_cfg.rs @@ -1,6 +1,6 @@ use crate::error::{Error, Result}; use crate::memory::{ - GuestAccess, GuestAddressSpaceViewMut, GuestPhysAddr, GuestVirtAddr, + GuestAccess, GuestAddressSpaceView, GuestPhysAddr, GuestVirtAddr, PrivilegeLevel, }; use crate::virtdev::{ @@ -244,7 +244,7 @@ impl QemuFwCfg { fn perform_dma_transfer( &mut self, - mut space: GuestAddressSpaceViewMut, + space: GuestAddressSpaceView, ) -> Result<()> { let bytes = space.read_bytes( GuestVirtAddr::NoPaging(GuestPhysAddr::new(self.dma_addr)), @@ -358,7 +358,7 @@ impl QemuFwCfg { &mut self, port: Port, val: PortWriteRequest, - space: GuestAddressSpaceViewMut, + space: GuestAddressSpaceView, ) -> Result<()> { match port { Self::FW_CFG_PORT_SEL => { diff --git a/mythril/src/vm.rs b/mythril/src/vm.rs index 37a6635..d3619da 100644 --- a/mythril/src/vm.rs +++ b/mythril/src/vm.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::apic; use crate::boot_info::BootInfo; use crate::error::{Error, Result}; @@ -10,8 +12,9 @@ use crate::memory::{ use crate::percore; use crate::physdev; use crate::time; +use crate::vcpu; use crate::virtdev::{ - DeviceEvent, DeviceInteraction, DeviceMap, Event, ResponseEventArray, + self, DeviceEvent, DeviceInteraction, DeviceMap, Event, ResponseEventArray, }; use alloc::boxed::Box; use alloc::collections::BTreeMap; @@ -19,7 +22,9 @@ use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; use arraydeque::ArrayDeque; +use arrayvec::ArrayVec; use core::mem; +use core::sync::atomic::AtomicU32; use spin::RwLock; static BIOS_BLOB: &'static [u8] = include_bytes!("blob/bios.bin"); @@ -28,60 +33,69 @@ static BIOS_BLOB: &'static [u8] = include_bytes!("blob/bios.bin"); /// The location of the local apic in the guest address space pub const GUEST_LOCAL_APIC_ADDR: GuestPhysAddr = GuestPhysAddr::new(0xfee00000); -static VIRTUAL_MACHINES: RoAfterInit = +static VIRTUAL_MACHINES: RoAfterInit = RoAfterInit::uninitialized(); -pub unsafe fn init_virtual_machines(machines: VirtualMachines) { - RoAfterInit::init(&VIRTUAL_MACHINES, machines); -} +/// The maximum numer of cores that can be assigned to a single VM +pub const MAX_PER_VM_CORE_COUNT: usize = 32; + +const MAX_PENDING_MSG: usize = 100; -/// Get the virtual machine that owns the core with the given core id +/// Initialize the global VirtualMachineSet /// -/// This method is unsafe as it should almost certainly not be used (use message -/// passing instead of directly access the remote VM). -pub unsafe fn get_vm_for_core_id( - core_id: percore::CoreId, -) -> Option>> { - VIRTUAL_MACHINES.get_by_core_id(core_id) +/// This method must be called before calling 'virtual_machines' +pub unsafe fn init_virtual_machines(machines: VirtualMachineSet) { + RoAfterInit::init(&VIRTUAL_MACHINES, machines); } -//FIXME(alschwalm): this breaks if the current VM is already locked -pub fn send_vm_msg(msg: VirtualMachineMsg, vmid: u32) -> Result<()> { - VIRTUAL_MACHINES.send_msg(msg, vmid) +/// Get the global VirtualMachineSet +pub fn virtual_machines() -> &'static VirtualMachineSet { + &*VIRTUAL_MACHINES } -pub fn send_vm_msg_core( - msg: VirtualMachineMsg, - core_id: percore::CoreId, -) -> Result<()> { - VIRTUAL_MACHINES.send_msg_core(msg, core_id) -} +/// A message for inter-core or inter-VM communication +/// +/// These messages can be sent and received through the +/// VirtualMachineSet type, accessible via the 'virtual_machines' +/// method after startup +pub enum VirtualMachineMsg { + /// Transfer control of a physical serial console to another VM + GrantConsole(physdev::com::Uart8250), -pub fn recv_vm_msg() -> Option { - VIRTUAL_MACHINES.resv_msg() -} + /// Cancel a the given timer + CancelTimer(time::TimerId), -pub fn max_vm_id() -> u32 { - VIRTUAL_MACHINES.len() as u32 -} + /// Start a core at the given physical address + StartVcpu(GuestPhysAddr), -const MAX_PENDING_MSG: usize = 100; + /// Inject a guest interrupt with the given vector + GuestInterrupt { + /// The type of the injected interrupt + kind: vcpu::InjectedInterruptType, -pub enum VirtualMachineMsg { - GrantConsole(physdev::com::Uart8250), - CancelTimer(time::TimerId), + /// The injected interrupt vector + vector: u8, + }, } struct VirtualMachineContext { - vm: Arc>, + vm: Arc, + + /// The per-core RX message queue msgqueue: RwLock>, } -pub struct VirtualMachines { +/// The set of configured virtual machines +/// +/// This structure represents the set of all configured machines +/// in the hypervisor and can be used to transmit and receive +/// inter-vm or inter-core messages. +pub struct VirtualMachineSet { + machine_count: u32, map: BTreeMap, } -impl VirtualMachines { +impl VirtualMachineSet { fn context_by_core_id( &self, core_id: percore::CoreId, @@ -93,7 +107,7 @@ impl VirtualMachines { self.map .iter() .filter_map(|(_core_id, context)| { - if context.vm.read().id == id { + if context.vm.id == id { Some(context) } else { None @@ -102,26 +116,47 @@ impl VirtualMachines { .next() } - pub fn len(&self) -> usize { - self.map.len() + /// Returns the number of VMs + pub fn count(&self) -> u32 { + self.machine_count } - pub fn get_by_core_id( + /// Returns whether a given CoreId is associated with any VM + pub fn is_assigned_core_id(&self, core_id: percore::CoreId) -> bool { + self.map.contains_key(&core_id) + } + + /// Get the virtual machine that owns the core with the given core id + /// + /// This method is unsafe as it should almost certainly not be used (use message + /// passing instead of directly accessing the remote VM). + pub unsafe fn get_by_core_id( &self, core_id: percore::CoreId, - ) -> Option>> { + ) -> Option> { self.context_by_core_id(core_id) .map(|context| context.vm.clone()) } - pub fn get_by_vm_id(&self, id: u32) -> Option>> { - self.context_by_vm_id(id).map(|context| context.vm.clone()) + /// Get a VirtualMachine by its vmid + pub fn get_by_vm_id(&self, vmid: u32) -> Option> { + self.context_by_vm_id(vmid) + .map(|context| context.vm.clone()) + } + + /// Get the CoreId for the BSP core of a VM by its vmid + pub fn bsp_core_id(&self, vmid: u32) -> Option { + self.get_by_vm_id(vmid).map(|vm| vm.config.bsp_id()) } + /// Send the given message to a specific core + /// + /// If 'notify' is true, an interrupt will be sent to the recipient. pub fn send_msg_core( &self, msg: VirtualMachineMsg, core_id: percore::CoreId, + notify: bool, ) -> Result<()> { let context = self .context_by_core_id(core_id) @@ -133,6 +168,10 @@ impl VirtualMachines { )) })?; + if !notify { + return Ok(()); + } + // Transmit the IPC external interrupt vector to the other vm, so it will // process the message. unsafe { @@ -144,57 +183,90 @@ impl VirtualMachines { apic::Level::Assert, apic::DstMode::Physical, apic::DeliveryMode::Fixed, - interrupt::IPC_VECTOR, + interrupt::vector::IPC, ); } Ok(()) } - pub fn send_msg(&self, msg: VirtualMachineMsg, vm_id: u32) -> Result<()> { - //TODO(alschwalm): this should actually be the BSP core_id of the other vm - self.send_msg_core(msg, vm_id.into()) + /// Send the given message to a specific virtual machine + /// + /// The sent message will be received by the BSP of the target + /// virtual machine. If 'notify' is true, an interrupt will + /// be sent to the recipient. + pub fn send_msg( + &self, + msg: VirtualMachineMsg, + vm_id: u32, + notify: bool, + ) -> Result<()> { + let vm_bsp = self.bsp_core_id(vm_id).ok_or_else(|| { + Error::InvalidValue(format!( + "Unable to find BSP for VM id '{}'", + vm_id + )) + })?; + self.send_msg_core(msg, vm_bsp, notify) } - pub fn resv_msg(&self) -> Option { + /// Receive any pending message for the current core + pub fn recv_msg(&self) -> Option { let context = self .context_by_core_id(percore::read_core_id()) .expect("No VirtualMachineContext for apic id"); context.msgqueue.write().pop_front() } + + /// Receive all pending messages for the current core + pub fn recv_all_msgs(&self) -> impl Iterator { + let context = self + .context_by_core_id(percore::read_core_id()) + .expect("No VirtualMachineContext for apic id"); + let pending_messages = context.msgqueue.write().split_off(0); + pending_messages.into_iter() + } } -pub struct VirtualMachineBuilder { - // Mapping of core_id to VirtualMachine - map: BTreeMap>>, +/// A structure to build up the set of VirtualMachines +pub struct VirtualMachineSetBuilder { + /// The number of virtual machines added to the builder + machine_count: u32, + + /// Mapping of core_id to VirtualMachine + map: BTreeMap>, } -impl VirtualMachineBuilder { +impl VirtualMachineSetBuilder { + /// Returns a new VirtualMachineSetBuilder pub fn new() -> Self { - VirtualMachineBuilder { + Self { + machine_count: 0, map: BTreeMap::new(), } } - pub fn insert_machine( - &mut self, - vm: Arc>, - ) -> Result<()> { - for cpu in vm.read().config.cpus() { + /// Add a VirtualMachine to the set + pub fn insert_machine(&mut self, vm: Arc) -> Result<()> { + self.machine_count += 1; + for cpu in vm.config.cpus() { self.map.insert(percore::CoreId::from(*cpu), vm.clone()); } Ok(()) } + /// Get the virtual machine that owns the core with the given core id pub fn get_by_core_id( &self, core_id: percore::CoreId, - ) -> Option>> { + ) -> Option> { self.map.get(&core_id).map(|vm| vm.clone()) } - pub fn finalize(self) -> VirtualMachines { - VirtualMachines { + /// Finish building the VirtualMachineSet + pub fn finalize(self) -> VirtualMachineSet { + VirtualMachineSet { + machine_count: self.machine_count, map: self .map .into_iter() @@ -212,18 +284,19 @@ impl VirtualMachineBuilder { } } +/// A set of physical hardware that may be attached to a VM #[derive(Default)] pub struct PhysicalDeviceConfig { /// The physical serial connection for this VM (if any). - pub serial: Option, + pub serial: RwLock>, /// The physical ps2 keyboard connection for this VM (if any). - pub ps2_keyboard: Option, + pub ps2_keyboard: RwLock>, } /// A configuration for a `VirtualMachine` pub struct VirtualMachineConfig { - cpus: Vec, + cpus: ArrayVec<[percore::CoreId; MAX_PER_VM_CORE_COUNT]>, images: Vec<(String, GuestPhysAddr)>, virtual_devices: DeviceMap, physical_devices: PhysicalDeviceConfig, @@ -238,23 +311,25 @@ impl VirtualMachineConfig { /// * `cpus` - A list of the cores used by the VM (by APIC id) /// * `memory` - The amount of VM memory (in MB) pub fn new( - cpus: Vec, + cpus: &[percore::CoreId], memory: u64, physical_devices: PhysicalDeviceConfig, - ) -> VirtualMachineConfig { - VirtualMachineConfig { - cpus: cpus, + ) -> Result { + let mut cpu_array = ArrayVec::new(); + cpu_array.try_extend_from_slice(cpus)?; + Ok(VirtualMachineConfig { + cpus: cpu_array, images: vec![], virtual_devices: DeviceMap::default(), physical_devices: physical_devices, memory: memory, - } + }) } /// Specify that the given image 'path' should be mapped to the given address /// - /// The precise meaning of `image` will vary by platform. This will be a - /// value suitable to be passed to `VmServices::read_file`. + /// The precise meaning of `image` will vary by platform. On multiboot2 platforms + /// it is a module. pub fn map_image( &mut self, image: String, @@ -274,16 +349,19 @@ impl VirtualMachineConfig { &mut self.virtual_devices } + /// Access the configurations physical hardware pub fn physical_devices(&self) -> &PhysicalDeviceConfig { &self.physical_devices } - pub fn physical_devices_mut(&mut self) -> &mut PhysicalDeviceConfig { - &mut self.physical_devices + /// Get the list of CoreIds assicated with this VM + pub fn cpus(&self) -> &ArrayVec<[percore::CoreId; MAX_PER_VM_CORE_COUNT]> { + &self.cpus } - pub fn cpus(&self) -> &Vec { - &self.cpus + /// Get the CoreId of the BSP for this VM + pub fn bsp_id(&self) -> percore::CoreId { + self.cpus[0] } } @@ -304,6 +382,13 @@ pub struct VirtualMachine { /// /// See section 29.4 of the Intel software developer's manual pub apic_access_page: Raw4kPage, + + /// Portions of the per-core Local APIC state needed for logical addressing + pub logical_apic_state: + BTreeMap, + + /// The number of vcpus that are up and waiting to start + cpus_ready: AtomicU32, } impl VirtualMachine { @@ -315,34 +400,67 @@ impl VirtualMachine { id: u32, config: VirtualMachineConfig, info: &BootInfo, - ) -> Result>> { + ) -> Result> { let guest_space = Self::setup_ept(&config, info)?; - let vm = Arc::new(RwLock::new(Self { + // Prepare the portion of per-core local apic state that is stored at the + // VM level (as needed for logical addressing) + let mut logical_apic_states = BTreeMap::new(); + for core in config.cpus.iter() { + logical_apic_states.insert( + core.clone(), + virtdev::lapic::LogicalApicState::default(), + ); + } + + let vm = Arc::new(Self { id: id, config: config, guest_space: guest_space, apic_access_page: Raw4kPage([0u8; 4096]), - })); + logical_apic_state: logical_apic_states, + cpus_ready: AtomicU32::new(0), + }); // Map the guest local apic addr to the access page. This will be set in each // core's vmcs let apic_frame = memory::HostPhysFrame::from_start_address( - memory::HostPhysAddr::new( - vm.read().apic_access_page.as_ptr() as u64 - ), - )?; - vm.write().guest_space.map_frame( - GUEST_LOCAL_APIC_ADDR, - apic_frame, - false, + memory::HostPhysAddr::new(vm.apic_access_page.as_ptr() as u64), )?; + vm.guest_space + .map_frame(GUEST_LOCAL_APIC_ADDR, apic_frame, false)?; Ok(vm) } + /// Notify this VirtualMachine that the current core is ready to start + /// + /// Each core associated with this VirtualMachine must call this method + /// in order for 'all_cores_ready' to return true. Cores must _not_ + /// invoke this method more than once. + pub fn notify_ready(&self) { + self.cpus_ready + .fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } + + /// Returns true when all VirtualMachine cores have called 'notify_ready' + pub fn all_cores_ready(&self) -> bool { + self.cpus_ready.load(core::sync::atomic::Ordering::SeqCst) + == self.config.cpus.len() as u32 + } + + /// Process the given DeviceEvent on the virtual hardware matching 'ident' + /// + /// # Arguments + /// + /// * `ident` - A DeviceInteraction like a Port I/O port or address used to + /// find the relevant device. + /// * `kind` - The DeviceEvent kind to dispatch + /// * `vcpu` - A handle to the current vcpu + /// * `responses` - A ResponseEventArray for any responses from the virtual + /// hardware pub fn dispatch_event( - &mut self, + &self, ident: impl DeviceInteraction + core::fmt::Debug, kind: DeviceEvent, vcpu: &crate::vcpu::VCpu, @@ -356,9 +474,9 @@ impl VirtualMachine { Error::MissingDevice("Unable to dispatch event".into()) })?; - let space = crate::memory::GuestAddressSpaceViewMut::from_vmcs( + let space = crate::memory::GuestAddressSpaceView::from_vmcs( &vcpu.vmcs, - &mut self.guest_space, + &self.guest_space, )?; let event = Event::new(kind, space, responses)?; @@ -366,6 +484,69 @@ impl VirtualMachine { dev.write().on_event(event) } + /// Returns an iterator of the CoreIds that are logically addressed by the given mask + /// + /// # Arguments + /// + /// * `mask` - The APIC ICR address contents (e.g., the upper 32 bits) for a + /// logically addressed IPC + pub fn logical_apic_destination( + &self, + mask: u32, + ) -> Result> { + // FIXME: currently we only support the 'Flat Model' logical mode + // (so we just ignore the destination format register). See 10.6.2.2 + // of Volume 3A of the Intel software developer's manual + Ok(self.config.cpus.iter().filter(move |core| { + let apic_state = self + .logical_apic_state + .get(core) + .expect("Missing logical state for core"); + // TODO(alschwalm): This may not need to be as strict as SeqCst + let destination = apic_state + .logical_destination + .load(core::sync::atomic::Ordering::SeqCst); + destination & mask != 0 + })) + } + + /// Notify the VirtualMachine of a change in the Logical Destination register + /// for the current core + pub fn update_core_logical_destination(&self, dest: u32) { + let apic_state = self + .logical_apic_state + .get(&percore::read_core_id()) + .expect("Missing logical state for core"); + apic_state + .logical_destination + .store(dest, core::sync::atomic::Ordering::SeqCst); + } + + /// Resolve a guest GSI to a specific CoreId, vector and interrupt type + pub fn gsi_destination( + &self, + gsi: u32, + ) -> Result<(percore::CoreId, u8, vcpu::InjectedInterruptType)> { + //TODO(alschwalm): For now just route the UART interrupts to the BSP, + // but this should ulimately do actual interrupt routing based on the + // guest IO APICs. For now just blindly translate GSI to vector based + // on this basic formula. + let vector = (gsi + 48) as u8; + if gsi == interrupt::gsi::UART { + Ok(( + self.config.bsp_id(), + vector, + vcpu::InjectedInterruptType::ExternalInterrupt, + )) + } else { + Ok(( + percore::read_core_id(), + vector, + vcpu::InjectedInterruptType::ExternalInterrupt, + )) + } + } + fn map_data( image: &[u8], addr: &GuestPhysAddr, @@ -469,10 +650,11 @@ mod test { let phys_config = PhysicalDeviceConfig::default(); let config = VirtualMachineConfig::new( - vec![percore::CoreId::from(1)], + &[percore::CoreId::from(1)], 0, phys_config, - ); + ) + .unwrap(); VirtualMachine::new(0, config, &info).unwrap(); } } diff --git a/mythril/src/vmcs.rs b/mythril/src/vmcs.rs index a10ebb9..e0db02c 100644 --- a/mythril/src/vmcs.rs +++ b/mythril/src/vmcs.rs @@ -4,6 +4,7 @@ use crate::vmx; use alloc::boxed::Box; use bitflags::bitflags; use core::fmt; +use num_enum::TryFromPrimitive; use x86::msr::rdmsr; #[allow(dead_code)] @@ -285,6 +286,15 @@ bitflags! { } } +#[derive(Copy, Clone, TryFromPrimitive)] +#[repr(u32)] +pub enum ActivityState { + Active = 0, + Hlt = 1, + Shutdown = 2, + WaitForSipi = 3, +} + fn vmcs_write_with_fixed( field: VmcsField, value: u64, diff --git a/mythril/src/vmexit.rs b/mythril/src/vmexit.rs index 1380254..32e5bd5 100644 --- a/mythril/src/vmexit.rs +++ b/mythril/src/vmexit.rs @@ -11,7 +11,6 @@ extern "C" { } #[repr(C)] -#[repr(packed)] #[derive(Clone, Copy, Debug)] pub struct GuestCpuState { pub cr2: u64, diff --git a/rust-toolchain b/rust-toolchain index a66b243..f2291f5 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-08-13 +nightly-2020-11-16 diff --git a/scripts/mythril-run.sh b/scripts/mythril-run.sh index c6306f4..54c88eb 100755 --- a/scripts/mythril-run.sh +++ b/scripts/mythril-run.sh @@ -23,7 +23,7 @@ grub-mkrescue -d /usr/lib/grub/i386-pc -o os.iso _isofiles qemu-system-x86_64 -enable-kvm \ -cpu host \ - -smp cores=2,threads=1,sockets=1 \ + -smp cores=6,threads=1,sockets=1 \ -serial stdio \ -display none \ -cdrom os.iso \ @@ -31,4 +31,4 @@ qemu-system-x86_64 -enable-kvm \ -debugcon file:debug.log \ -no-reboot \ -global isa-debugcon.iobase=0x402 \ - -m 1G "${@:2}" + -m 2G "${@:2}" diff --git a/scripts/mythril.cfg b/scripts/mythril.cfg index e8ac63b..b635967 100644 --- a/scripts/mythril.cfg +++ b/scripts/mythril.cfg @@ -2,18 +2,18 @@ "version": 1, "vms": [ { - "memory": 256, - "cpus": [0], + "memory": 1024, + "cpus": [0, 1, 2, 3], "kernel": "kernel", "initramfs": "initramfs", - "cmdline": "earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0" + "cmdline": "loglevel=8 earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0" }, { - "memory": 256, - "cpus": [1], + "memory": 512, + "cpus": [4, 5], "kernel": "kernel", "initramfs": "initramfs", - "cmdline": "earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0" + "cmdline": "loglevel=8 earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0" } ] }