From d3e5da951929086ca8c14608dbdda2395e1bab13 Mon Sep 17 00:00:00 2001 From: memN0ps <89628341+memN0ps@users.noreply.github.com> Date: Thu, 15 Feb 2024 01:08:50 +1300 Subject: [PATCH] vmxon works --- driver/src/main.rs | 35 +- driver/src/virtualize.rs | 77 ++++ hypervisor/src/intel/capture.rs | 90 +++++ hypervisor/src/intel/ept/mod.rs | 3 + hypervisor/src/intel/ept/mtrr.rs | 266 +++++++++++++ hypervisor/src/intel/ept/paging.rs | 588 +++++++++++++++++++++++++++++ hypervisor/src/intel/mod.rs | 21 ++ hypervisor/src/intel/page.rs | 9 + hypervisor/src/intel/paging.rs | 169 +++++++++ hypervisor/src/intel/shared.rs | 59 +++ hypervisor/src/intel/support.rs | 142 +++++++ hypervisor/src/intel/vm.rs | 26 ++ hypervisor/src/intel/vmcs.rs | 28 ++ hypervisor/src/intel/vmx.rs | 28 ++ hypervisor/src/intel/vmxon.rs | 114 ++++++ hypervisor/src/lib.rs | 6 + hypervisor/src/vmm.rs | 29 ++ setup.ps1 | 20 + 18 files changed, 1704 insertions(+), 6 deletions(-) create mode 100644 driver/src/virtualize.rs create mode 100644 hypervisor/src/intel/capture.rs create mode 100644 hypervisor/src/intel/ept/mod.rs create mode 100644 hypervisor/src/intel/ept/mtrr.rs create mode 100644 hypervisor/src/intel/ept/paging.rs create mode 100644 hypervisor/src/intel/mod.rs create mode 100644 hypervisor/src/intel/page.rs create mode 100644 hypervisor/src/intel/paging.rs create mode 100644 hypervisor/src/intel/shared.rs create mode 100644 hypervisor/src/intel/support.rs create mode 100644 hypervisor/src/intel/vm.rs create mode 100644 hypervisor/src/intel/vmcs.rs create mode 100644 hypervisor/src/intel/vmx.rs create mode 100644 hypervisor/src/intel/vmxon.rs create mode 100644 setup.ps1 diff --git a/driver/src/main.rs b/driver/src/main.rs index 0b50c0a..21844b8 100644 --- a/driver/src/main.rs +++ b/driver/src/main.rs @@ -1,18 +1,41 @@ #![no_main] #![no_std] -use log::*; -use uefi::prelude::*; +extern crate alloc; + +use { + log::*, + uefi::prelude::*, + hypervisor::vmm::is_hypervisor_present, + crate::{virtualize::virtualize_system, capture::{capture_registers, GuestRegisters}}, +}; + +pub mod virtualize; #[entry] fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { - com_logger::builder().filter(LevelFilter::Debug).setup(); + // Initialize the COM2 port logger with level filter set to Info. + com_logger::builder().base(0x2f8).filter(LevelFilter::Trace).setup(); uefi_services::init(&mut system_table).unwrap(); - info!("Hello, world!"); + info!("The Matrix is an illusion"); + + // Capture the register values to be used as an initial state of the VM. + let mut regs = GuestRegisters::default(); + unsafe { capture_registers(&mut regs) } + + // Since we captured RIP just above, the VM will start running from here. + // Check if our hypervisor is already loaded. If so, done, otherwise, continue + // installing the hypervisor. + if !is_hypervisor_present() { + debug!("Virtualizing the system"); + virtualize_system(®s, &system_table); + } + + info!("The hypervisor has been installed successfully!"); - system_table.boot_services().stall(10_000_000); + //system_table.boot_services().stall(10_000_000); Status::SUCCESS -} \ No newline at end of file +} diff --git a/driver/src/virtualize.rs b/driver/src/virtualize.rs new file mode 100644 index 0000000..af569db --- /dev/null +++ b/driver/src/virtualize.rs @@ -0,0 +1,77 @@ +use { + alloc::alloc::{alloc_zeroed, handle_alloc_error}, + core::{alloc::Layout, arch::global_asm}, + hypervisor::{ + intel::{ + capture::GuestRegisters, page::Page + }, + vmm::start_hypervisor + }, + log::debug, + uefi::{ + proto::loaded_image::LoadedImage, + table::{Boot, SystemTable}, + }, +}; + +pub fn virtualize_system(regs: &GuestRegisters, system_table: &SystemTable) { + let boot_service = system_table.boot_services(); + + // Open the loaded image protocol to get the current image base and image size. + let loaded_image = boot_service. + open_protocol_exclusive::(boot_service.image_handle()) + .unwrap(); + + // Get the current image base and image size. + let (image_base, image_size) = loaded_image.info(); + + let image_base = image_base as usize; + + let image_range = image_base..image_base + image_size as usize; + debug!("Image base: {:#x?}", image_range); + + // Prevent relocation by zapping the Relocation Table in the PE header. UEFI + // keeps the list of runtime drivers and applies patches into their code and + // data according to relocation information, as address translation switches + // from physical-mode to virtual-mode when the OS starts. This causes a problem + // with us because the host part keeps running under physical-mode, as the + // host has its own page tables. Relocation ends up breaking the host code. + // The easiest way is prevented this from happening is to nullify the relocation + // table. + unsafe { + *((image_base + 0x128) as *mut u32) = 0; + *((image_base + 0x12c) as *mut u32) = 0; + } + + // Allocate separate stack space. This is never freed. + let layout = Layout::array::(0x10).unwrap(); + + let stack = unsafe { alloc_zeroed(layout) }; + + if stack.is_null() { + handle_alloc_error(layout); + } + + let stack_base = stack as u64 + layout.size() as u64 - 0x10; + debug!("Stack range: {:#x?}", stack_base..stack as u64); + + unsafe { switch_stack(regs, start_hypervisor as usize, stack_base) }; +} + +extern "efiapi" { + /// Jumps to the landing code with the new stack pointer. + fn switch_stack(regs: &GuestRegisters, landing_code: usize, stack_base: u64) -> !; +} + +global_asm!(r#" +// The module containing the `switch_stack` function. + +// Jumps to the landing code with the new stack pointer. +// +// fn switch_stack(regs: &GuestRegisters, landing_code: usize, stack_base: u64) -> ! +.global switch_stack +switch_stack: + xchg bx, bx + mov rsp, r8 + jmp rdx +"#); \ No newline at end of file diff --git a/hypervisor/src/intel/capture.rs b/hypervisor/src/intel/capture.rs new file mode 100644 index 0000000..dd09721 --- /dev/null +++ b/hypervisor/src/intel/capture.rs @@ -0,0 +1,90 @@ +use core::arch::global_asm; + +extern "efiapi" { + /// Captures current general purpose registers, RFLAGS, RSP, and RIP. + pub fn capture_registers(registers: &mut GuestRegisters); +} + +/// The collection of the guest general purpose register values. +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct GuestRegisters { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rflags: u64, + rsp: u64, + rip: u64, +} + +global_asm!(r#" +// The module containing the `capture_registers` function. + +// Offsets to each field in the GuestRegisters struct. +.set registers_rax, 0x0 +.set registers_rbx, 0x8 +.set registers_rcx, 0x10 +.set registers_rdx, 0x18 +.set registers_rdi, 0x20 +.set registers_rsi, 0x28 +.set registers_rbp, 0x30 +.set registers_r8, 0x38 +.set registers_r9, 0x40 +.set registers_r10, 0x48 +.set registers_r11, 0x50 +.set registers_r12, 0x58 +.set registers_r13, 0x60 +.set registers_r14, 0x68 +.set registers_r15, 0x70 +.set registers_rflags, 0x78 +.set registers_rsp, 0x80 +.set registers_rip, 0x88 + +// Captures current general purpose registers, RFLAGS, RSP, and RIP. +// +// extern "efiapi" fn capture_registers(registers: &mut GuestRegisters) +.global capture_registers +capture_registers: + // Capture general purpose registers. + mov [rcx + registers_rax], rax + mov [rcx + registers_rbx], rbx + mov [rcx + registers_rcx], rcx + mov [rcx + registers_rdx], rdx + mov [rcx + registers_rsi], rsi + mov [rcx + registers_rdi], rdi + mov [rcx + registers_rbp], rbp + mov [rcx + registers_r8], r8 + mov [rcx + registers_r9], r9 + mov [rcx + registers_r10], r10 + mov [rcx + registers_r11], r11 + mov [rcx + registers_r12], r12 + mov [rcx + registers_r13], r13 + mov [rcx + registers_r14], r14 + mov [rcx + registers_r15], r15 + + // Capture RFLAGS, RSP, and RIP. + pushfq + pop rax + mov [rcx + registers_rflags], rax + + mov rax, rsp + add rax, 8 + mov [rcx + registers_rsp], rax + + mov rax, [rsp] + mov [rcx + registers_rip], rax + + ret +"#); diff --git a/hypervisor/src/intel/ept/mod.rs b/hypervisor/src/intel/ept/mod.rs new file mode 100644 index 0000000..c6114a2 --- /dev/null +++ b/hypervisor/src/intel/ept/mod.rs @@ -0,0 +1,3 @@ +// pub mod hooks; +pub mod mtrr; +pub mod paging; \ No newline at end of file diff --git a/hypervisor/src/intel/ept/mtrr.rs b/hypervisor/src/intel/ept/mtrr.rs new file mode 100644 index 0000000..0267507 --- /dev/null +++ b/hypervisor/src/intel/ept/mtrr.rs @@ -0,0 +1,266 @@ +//! A module for handling Memory Type Range Registers (MTRRs) in x86 systems. +//! It provides functionality to build a map of MTRRs and their corresponding memory ranges +//! and types, following the specifications of the Intel® 64 and IA-32 Architectures Software Developer's Manual: 12.11 MEMORY TYPE RANGE REGISTERS (MTRRS) +//! +//! Credits to Neri https://github.com/neri/maystorm/blob/develop/system/src/arch/x64/cpu.rs + +use { + alloc::vec::Vec, + x86::msr::{IA32_MTRRCAP, IA32_MTRR_PHYSBASE0, IA32_MTRR_PHYSMASK0}, + crate::intel::support::rdmsr, +}; + +/// Represents the different types of memory as defined by MTRRs. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemoryType { + /// Memory type: Uncacheable (UC) + Uncacheable = 0, + /// Memory type: Write-combining (WC) + WriteCombining = 1, + /// Memory type: Write-through (WT) + WriteThrough = 4, + /// Memory type: Write-protected (WP) + WriteProtected = 5, + /// Memory type: Write-back (WB) + WriteBack = 6, +} + +/// Represents a Mttr range descriptor. +pub struct Mtrr { + descriptors: Vec, +} + +impl Mtrr { + /// Builds a map of the MTRR memory ranges currently in use. + /// + /// # Returns + /// A vector of `MtrrRangeDescriptor` representing each enabled memory range. + pub fn new() -> Self { + let mut descriptors = Vec::new(); + + for index in Self::indexes() { + let item = Self::get(index); + + // Skip Write Back type as it's the default memory type. + if item.is_enabled && item.mem_type != MemoryType::WriteBack { + let end_address = Self::calculate_end_address(item.base.pa(), item.mask); + + let descriptor = MtrrRangeDescriptor { + base_address: item.base.pa(), + end_address, + memory_type: item.mem_type, + }; + + descriptors.push(descriptor); + log::trace!( + "MTRR Range: Base=0x{:x} End=0x{:x} Type={:?}", + descriptor.base_address, + descriptor.end_address, + descriptor.memory_type + ); + } + } + + log::trace!("Total MTRR Ranges Committed: {}", descriptors.len()); + Self { descriptors } + } + + /// Finds the memory type for a given physical address range based on the MTRR map. + /// + /// This method examines the MTRR map to find the appropriate memory type for the + /// specified physical address range. It respects the precedence of different memory + /// types, with Uncacheable (UC) having the highest precedence. + /// If no matching range is found, it defaults to WriteBack. + /// + /// # Arguments + /// * `mtrr_map` - The MTRR map to search within. + /// * `range` - The physical address range for which to find the memory type. + /// + /// # Returns + /// The memory type for the given address range, or a default of WriteBack if no matching range is found. + pub fn find(&mut self, range: core::ops::Range) -> Option { + // Initialize a variable to store the memory type, initially set to None. + let mut memory_type: Option = None; + + // Iterate through each MTRR range descriptor in the map. + for descriptor in self.descriptors.iter_mut() { + // Check if the provided range falls within the current descriptor's range. + if range.start >= descriptor.base_address && range.end <= descriptor.end_address { + // Based on the memory type of the descriptor, set the memory type. + match descriptor.memory_type { + // If Uncacheable, return immediately as it has the highest precedence. + MemoryType::Uncacheable => return Some(MemoryType::Uncacheable), + + // For other types, set the memory type if it is not already set. + // Or if it's a less strict type compared to the existing one. + MemoryType::WriteCombining => memory_type = Some(MemoryType::WriteCombining), + MemoryType::WriteThrough => memory_type = Some(MemoryType::WriteThrough), + MemoryType::WriteProtected => memory_type = Some(MemoryType::WriteProtected), + MemoryType::WriteBack => memory_type = Some(MemoryType::WriteBack), + } + } + } + + // Return the found memory type or default to WriteBack if no specific type was found. + memory_type.or(Some(MemoryType::WriteBack)) + } + + /// Calculates the end address of an MTRR memory range. + /// + /// # Arguments + /// * `base` - The base address of the memory range. + /// * `mask` - The mask defining the size of the range. + /// + /// # Returns + /// The end address of the memory range. + fn calculate_end_address(base: u64, mask: u64) -> u64 { + let first_set_bit = Self::bit_scan_forward(mask); + let size = 1 << first_set_bit; + base + size - 1 + } + + /// Performs a Bit Scan Forward (BSF) operation to find the index of the first set bit. + /// + /// # Arguments + /// * `value` - The value to scan. + /// + /// # Returns + /// The index of the first set bit. + fn bit_scan_forward(value: u64) -> u64 { + let result: u64; + unsafe { core::arch::asm!("bsf {}, {}", out(reg) result, in(reg) value) }; + result + } + + /// Retrieves the count of variable range MTRRs. + /// + /// Reads the IA32_MTRRCAP MSR to determine the number of variable range MTRRs + /// supported by the processor. This information is used to iterate over all + /// variable MTRRs in the system. + /// + /// # Returns + /// The number of variable range MTRRs. + /// + /// # Reference + /// Intel® 64 and IA-32 Architectures Software Developer's Manual: 12.11.1 MTRR Feature Identification + /// - Figure 12-5. IA32_MTRRCAP Register + pub fn count() -> usize { + rdmsr(IA32_MTRRCAP) as usize & 0xFF + } + + /// Creates an iterator over the MTRR indexes. + /// + /// This iterator allows for iterating over all variable range MTRRs in the system, + /// facilitating access to each MTRR's configuration. + /// + /// # Returns + /// An iterator over the range of MTRR indexes. + pub fn indexes() -> impl Iterator { + (0..Self::count() as u8).into_iter().map(|v| MtrrIndex(v)) + } + + /// Retrieves the configuration for a specific MTRR. + /// + /// Reads the base and mask MSRs for the MTRR specified by `index` and constructs + /// an `MtrrItem` representing its configuration. + /// + /// # Arguments + /// * `index` - The index of the MTRR to retrieve. + /// + /// # Returns + /// An `MtrrItem` representing the specified MTRR's configuration. + pub fn get(index: MtrrIndex) -> MtrrItem { + let base = rdmsr(Self::ia32_mtrrphys_base(index)); + let mask = rdmsr(Self::ia32_mtrrphys_mask(index)); + MtrrItem::from_raw(base, mask) + } + + /// Calculates the base MSR address for a given MTRR index. + /// + /// # Arguments + /// * `n` - The MTRR index. + /// + /// # Returns + /// The base MSR address for the MTRR. + pub fn ia32_mtrrphys_base(n: MtrrIndex) -> u32 { + IA32_MTRR_PHYSBASE0 + n.0 as u32 * 2 + } + + /// Calculates the mask MSR address for a given MTRR index. + /// + /// # Arguments + /// * `n` - The MTRR index. + /// + /// # Returns + /// The mask MSR address for the MTRR. + pub fn ia32_mtrrphys_mask(n: MtrrIndex) -> u32 { + IA32_MTRR_PHYSMASK0 + n.0 as u32 * 2 + } + + /// Converts a raw memory type value into an MemoryType enum variant. + /// + /// # Arguments + /// * `value` - The raw memory type value. + /// + /// # Returns + /// The corresponding `MemoryType` enum variant. + /// + /// # Safety + /// This function is unsafe because it uses `transmute` which can lead to undefined behavior + /// if `value` does not correspond to a valid variant of `MemoryType`. + pub const fn from_raw(value: u8) -> MemoryType { + unsafe { core::mem::transmute(value) } + } +} + +/// Represents an index into the array of variable MTRRs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct MtrrIndex(pub u8); + +/// Describes a specific MTRR memory range. +#[derive(Debug, Clone, Copy)] +pub struct MtrrRangeDescriptor { + /// The base address of the memory range. + pub base_address: u64, + /// The end address of the memory range. + pub end_address: u64, + /// The memory type associated with this range. + pub memory_type: MemoryType, +} + +/// Represents the configuration of a single MTRR. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MtrrItem { + /// The physical base address for this MTRR. + pub base: u64, + /// The mask that determines the size and enablement of the MTRR. + pub mask: u64, + /// The memory type (caching behavior) of this MTRR. + pub mem_type: MemoryType, + /// Flag indicating whether this MTRR is enabled. + pub is_enabled: bool, +} + +impl MtrrItem { + /// Mask for filtering the relevant address bits, aligning to page size (4 KB). + const ADDR_MASK: u64 = !0xFFF; + + /// Constructs an MtrrItem from raw MSR values. + /// + /// # Arguments + /// * `base` - The base address read from the IA32_MTRR_PHYSBASE MSR. + /// * `mask` - The mask read from the IA32_MTRR_PHYSMASK MSR. + /// + /// # Returns + /// A new `MtrrItem` representing the MSR's configuration. + pub fn from_raw(base: u64, mask: u64) -> Self { + let mem_type = Mtrr::from_raw(base as u8); + let is_enabled = (mask & 0x800) != 0; + Self { + base: base & Self::ADDR_MASK, + mask: mask & Self::ADDR_MASK, + mem_type, + is_enabled, + } + } +} \ No newline at end of file diff --git a/hypervisor/src/intel/ept/paging.rs b/hypervisor/src/intel/ept/paging.rs new file mode 100644 index 0000000..be9493e --- /dev/null +++ b/hypervisor/src/intel/ept/paging.rs @@ -0,0 +1,588 @@ +//! Intel® 64 and IA-32 Architectures Software Developer's Manual: 29.3 THE EXTENDED PAGE TABLE MECHANISM (EPT) +//! The extended page-table mechanism (EPT) is a feature that can be used to support the virtualization of physical memory. +//! When EPT is in use, certain addresses that would normally be treated as physical addresses (and used to access memory) are instead treated as guest-physical addresses +//! Guest-physical addresses are translated by traversing a set of EPT paging structures to produce physical addresses that are used to access memory. +//! +//! Credits to the work by Satoshi (https://github.com/tandasat/Hello-VT-rp/blob/main/hypervisor/src/intel_vt/epts.rs) and Matthias (https://github.com/not-matthias/amd_hypervisor/blob/main/hypervisor/src/svm/nested_page_table.rs). + +use { + crate::{ + error::HypervisorError, + intel::ept::mtrr::{MemoryType, Mtrr}, + }, + bitfield::bitfield, + bitflags::bitflags, + core::ptr::addr_of, + x86::bits64::paging::{ + pd_index, pdpt_index, pml4_index, pt_index, VAddr, BASE_PAGE_SHIFT, BASE_PAGE_SIZE, + LARGE_PAGE_SIZE, PAGE_SIZE_ENTRIES, + }, +}; + +bitflags! { + /// Represents the different access permissions for an EPT entry. + #[derive(Debug, Clone, Copy)] + pub struct AccessType: u8 { + /// The EPT entry allows read access. + const READ = 0b001; + /// The EPT entry allows write access. + const WRITE = 0b010; + /// The EPT entry allows execute access. + const EXECUTE = 0b100; + /// The EPT entry allows read and write access. + const READ_WRITE = Self::READ.bits() | Self::WRITE.bits(); + /// The EPT entry allows read and execute access. + const READ_EXECUTE = Self::READ.bits() | Self::EXECUTE.bits(); + /// The EPT entry allows write and execute access. + const WRITE_EXECUTE = Self::WRITE.bits() | Self::EXECUTE.bits(); + /// The EPT entry allows read, write, and execute access. + const READ_WRITE_EXECUTE = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits(); + } +} + +pub const _512GB: u64 = 512 * 1024 * 1024 * 1024; +pub const _1GB: u64 = 1024 * 1024 * 1024; +pub const _2MB: usize = 2 * 1024 * 1024; +pub const _4KB: usize = 4 * 1024; + +/// Represents the entire Extended Page Table structure. +/// +/// EPT is a set of nested page tables similar to the standard x86-64 paging mechanism. +/// It consists of 4 levels: PML4, PDPT, PD, and PT. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 29.3.2 EPT Translation Mechanism +#[repr(C, align(4096))] +pub struct Ept { + /// Page Map Level 4 (PML4) Table. + pml4: Pml4, + /// Page Directory Pointer Table (PDPT). + pdpt: Pdpt, + /// Array of Page Directory Table (PDT). + pd: [Pd; 512], + /// A two-dimensional array of Page Tables (PT). + pt: [[Pt; 512]; 512], +} + +impl Ept { + /// Creates an identity map for 2MB pages in the Extended Page Tables (EPT). + /// + /// Similar to `identity_4kb`, but maps larger 2MB pages for better performance in some scenarios + /// + /// # Arguments + /// + /// * `access_type`: The type of access allowed for these pages (read, write, execute). + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn identity_2mb(&mut self, access_type: AccessType) -> Result<(), HypervisorError> { + log::trace!("Creating identity map for 2MB pages"); + + let mut mtrr = Mtrr::new(); + + for pa in (0.._512GB).step_by(_2MB) { + self.map_2mb(pa, pa, access_type, &mut mtrr)?; + } + + Ok(()) + } + + /// Creates an identity map for 4KB pages in the Extended Page Tables (EPT). + /// + /// An identity map means every guest physical address maps directly to the same host physical address. + /// + /// # Arguments + /// + /// * `access_type`: The type of access allowed for these pages (read, write, execute). + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn identity_4kb(&mut self, access_type: AccessType) -> Result<(), HypervisorError> { + log::trace!("Creating identity map for 4KB pages"); + + let mut mtrr = Mtrr::new(); + + for pa in (0.._512GB).step_by(BASE_PAGE_SIZE) { + self.map_4kb(pa, pa, access_type, &mut mtrr)?; + } + + Ok(()) + } + + /// Maps a single 2MB page in the EPT. + /// + /// # Arguments + /// + /// * `guest_pa`: The guest physical address to map. + /// * `host_pa`: The host physical address to map to. + /// * `access_type`: The type of access allowed for this page (read, write, execute). + /// * `mtrr`: The Memory Type Range Registers (MTRR) to use for this page. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn map_2mb( + &mut self, + guest_pa: u64, + host_pa: u64, + access_type: AccessType, + mtrr: &mut Mtrr, + ) -> Result<(), HypervisorError> { + self.map_pml4(guest_pa, access_type)?; + self.map_pdpt(guest_pa, access_type)?; + self.map_pde(guest_pa, host_pa, access_type, mtrr)?; + + Ok(()) + } + + /// Maps a single 4KB page in the EPT. + /// + /// # Arguments + /// * `guest_pa`: The guest physical address to map. + /// * `host_pa`: The host physical address to map to. + /// * `access_type`: The type of access allowed for this page (read, write, execute). + /// * `mtrr`: The Memory Type Range Registers (MTRR) to use for this page. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn map_4kb( + &mut self, + guest_pa: u64, + host_pa: u64, + access_type: AccessType, + mtrr: &mut Mtrr, + ) -> Result<(), HypervisorError> { + self.map_pml4(guest_pa, access_type)?; + self.map_pdpt(guest_pa, access_type)?; + self.map_pdt(guest_pa, access_type)?; + self.map_pt(guest_pa, host_pa, access_type, mtrr)?; + + Ok(()) + } + + /// Updates the PML4 entry corresponding to the provided guest physical address. + /// + /// # Arguments + /// + /// * `guest_pa`: The guest physical address whose corresponding PML4 entry will be updated. + /// * `access_type`: The type of access allowed for the region covered by this PML4 entry. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + fn map_pml4(&mut self, guest_pa: u64, access_type: AccessType) -> Result<(), HypervisorError> { + let pml4_index = pml4_index(VAddr::from(guest_pa)); + let pml4_entry = &mut self.pml4.0.entries[pml4_index]; + + if !pml4_entry.readable() { + pml4_entry.set_readable(access_type.contains(AccessType::READ)); + pml4_entry.set_writable(access_type.contains(AccessType::WRITE)); + pml4_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + pml4_entry.set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); + } + + Ok(()) + } + + /// Updates the PDPT entry corresponding to the provided guest physical address. + /// + /// # Arguments + /// * `guest_pa`: The guest physical address whose corresponding PDPT entry will be updated. + /// * `access_type`: The type of access allowed for the region covered by this PDPT entry. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + fn map_pdpt(&mut self, guest_pa: u64, access_type: AccessType) -> Result<(), HypervisorError> { + let pdpt_index = pdpt_index(VAddr::from(guest_pa)); + let pdpt_entry = &mut self.pdpt.0.entries[pdpt_index]; + + if !pdpt_entry.readable() { + pdpt_entry.set_readable(access_type.contains(AccessType::READ)); + pdpt_entry.set_writable(access_type.contains(AccessType::WRITE)); + pdpt_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + pdpt_entry.set_pfn(addr_of!(self.pd[pdpt_index]) as u64 >> BASE_PAGE_SHIFT); + } + + Ok(()) + } + + /// Updates the PDT entry corresponding to the provided guest physical address. + /// + /// # Arguments + /// + /// * `guest_pa`: The guest physical address whose corresponding PDT entry will be updated. + /// * `access_type`: The type of access allowed for the region covered by this PDT entry. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + fn map_pdt(&mut self, guest_pa: u64, access_type: AccessType) -> Result<(), HypervisorError> { + let pdpt_index = pdpt_index(VAddr::from(guest_pa)); + let pd_index = pd_index(VAddr::from(guest_pa)); + let pd_entry = &mut self.pd[pdpt_index].0.entries[pd_index]; + + if !pd_entry.readable() { + pd_entry.set_readable(access_type.contains(AccessType::READ)); + pd_entry.set_writable(access_type.contains(AccessType::WRITE)); + pd_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + pd_entry.set_pfn(addr_of!(self.pt[pdpt_index][pd_index]) as u64 >> BASE_PAGE_SHIFT); + } + + Ok(()) + } + + /// Updates the PD entry corresponding to the provided guest physical address for 2MB page mapping. + /// + /// # Arguments + /// * `guest_pa`: The guest physical address whose corresponding PD entry will be updated. + /// * `host_pa`: The host physical address to map to. + /// * `access_type`: The type of access allowed for this 2MB page. + /// * `mtrr`: The Memory Type Range Registers (MTRR) to use for this page. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + fn map_pde( + &mut self, + guest_pa: u64, + host_pa: u64, + access_type: AccessType, + mtrr: &mut Mtrr, + ) -> Result<(), HypervisorError> { + let pdpt_index = pdpt_index(VAddr::from(guest_pa)); + let pd_index = pd_index(VAddr::from(guest_pa)); + let pd_entry = &mut self.pd[pdpt_index].0.entries[pd_index]; + + let memory_type = mtrr + .find(guest_pa..guest_pa + LARGE_PAGE_SIZE as u64) + .unwrap_or(MemoryType::Uncacheable); + + if !pd_entry.readable() { + pd_entry.set_readable(access_type.contains(AccessType::READ)); + pd_entry.set_writable(access_type.contains(AccessType::WRITE)); + pd_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + pd_entry.set_memory_type(memory_type as u64); + pd_entry.set_large(true); + pd_entry.set_pfn(host_pa >> BASE_PAGE_SHIFT); + } else { + log::warn!( + "Attempted to map an already-mapped 2MB page: {:x}", + guest_pa + ); + } + + Ok(()) + } + + /// Updates the PT entry corresponding to the provided guest physical address for 4KB page mapping. + /// + /// # Arguments + /// * `guest_pa`: The guest physical address whose corresponding PT entry will be updated. + /// * `host_pa`: The host physical address to map to. + /// * `access_type`: The type of access allowed for this 4KB page. + /// * `mtrr`: The Memory Type Range Registers (MTRR) to use for this page. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + fn map_pt( + &mut self, + guest_pa: u64, + host_pa: u64, + access_type: AccessType, + mtrr: &mut Mtrr, + ) -> Result<(), HypervisorError> { + let pdpt_index = pdpt_index(VAddr::from(guest_pa)); + let pd_index = pd_index(VAddr::from(guest_pa)); + let pt_index = pt_index(VAddr::from(guest_pa)); + let pt_entry = &mut self.pt[pdpt_index][pd_index].0.entries[pt_index]; + + let memory_type = mtrr + .find(guest_pa..guest_pa + BASE_PAGE_SIZE as u64) + .unwrap_or(MemoryType::Uncacheable); + + if !pt_entry.readable() { + pt_entry.set_readable(access_type.contains(AccessType::READ)); + pt_entry.set_writable(access_type.contains(AccessType::WRITE)); + pt_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + pt_entry.set_memory_type(memory_type as u64); + pt_entry.set_pfn(host_pa >> BASE_PAGE_SHIFT); + } else { + log::warn!( + "Attempted to map an already-mapped 4KB page: {:x}", + guest_pa + ); + } + + Ok(()) + } + + /// Modifies the access permissions for a page within the extended page table (EPT). + /// + /// This function adjusts the permissions of either a 2MB or a 4KB page based on its alignment. + /// It is the responsibility of the caller to ensure that the `guest_pa` is aligned to the size + /// of the page they intend to modify. + /// + /// # Arguments + /// + /// * `guest_pa` - Guest physical address of the page whose permissions are to be changed. + /// * `access_type` - The new access permissions to set for the page. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn change_page_flags( + &mut self, + guest_pa: u64, + access_type: AccessType, + ) -> Result<(), HypervisorError> { + let guest_pa = VAddr::from(guest_pa); + + if !guest_pa.is_large_page_aligned() && !guest_pa.is_base_page_aligned() { + log::error!("Page is not aligned: {:#x}", guest_pa); + return Err(HypervisorError::UnalignedAddressError); + } + + let pdpt_index = pdpt_index(guest_pa); + let pd_index = pd_index(guest_pa); + let pt_index = pt_index(guest_pa); + + let pd_entry = &mut self.pd[pdpt_index].0.entries[pd_index]; + + if pd_entry.large() { + log::trace!("Changing the permissions of a 2mb page"); + pd_entry.set_readable(access_type.contains(AccessType::READ)); + pd_entry.set_writable(access_type.contains(AccessType::WRITE)); + pd_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + } else { + log::trace!("Changing the permissions of a 4kb page"); + + let pt_entry = &mut self.pt[pdpt_index][pd_index].0.entries[pt_index]; + pt_entry.set_readable(access_type.contains(AccessType::READ)); + pt_entry.set_writable(access_type.contains(AccessType::WRITE)); + pt_entry.set_executable(access_type.contains(AccessType::EXECUTE)); + } + + Ok(()) + } + + /// Splits a large 2MB page into 512 smaller 4KB pages for a given guest physical address. + /// + /// This is necessary to apply more granular hooks and reduce the number of + /// page faults that occur when the guest tries to access a page that is hooked. + /// + /// # Arguments + /// + /// * `guest_pa`: The guest physical address within the 2MB page that needs to be split. + /// * `access_type`: The type of access allowed for the newly created 4KB pages. + /// + /// # Returns + /// + /// A `Result<(), HypervisorError>` indicating if the operation was successful. + pub fn split_2mb_to_4kb( + &mut self, + guest_pa: u64, + access_type: AccessType, + ) -> Result<(), HypervisorError> { + log::trace!("Splitting 2mb page into 4kb pages: {:x}", guest_pa); + + let guest_pa = VAddr::from(guest_pa); + + let pdpt_index = pdpt_index(guest_pa); + let pd_index = pd_index(guest_pa); + let pd_entry = &mut self.pd[pdpt_index].0.entries[pd_index]; + + // We can only split large pages and not page directories. + // If it's a page directory, it is already split. + // + if !pd_entry.large() { + log::trace!("Page is already split: {:x}.", guest_pa); + return Err(HypervisorError::PageAlreadySplit); + } + + // Unmap the 2MB page by resetting the page directory entry. + Self::unmap_2mb(pd_entry); + + let mut mtrr = Mtrr::new(); + + // Map the unmapped physical memory again to 4KB pages. + for i in 0..PAGE_SIZE_ENTRIES { + let pa = (guest_pa.as_usize() + i * BASE_PAGE_SIZE) as u64; + self.map_4kb(pa, pa, access_type, &mut mtrr)?; + } + + Ok(()) + } + + /// Remaps the given guest physical address and changes it to the given host physical address. + /// + /// # Arguments + /// + /// * `guest_pa`: The guest physical address to remap. + /// * `host_pa`: The host physical address to remap to. + /// * `access_type`: The type of access allowed for this page (read, write, execute). + /// * `mtrr`: The Memory Type Range Registers (MTRR) to use for this page. + /// Credits: Jess / jessiep_ + pub fn remap_page( + &mut self, + guest_pa: u64, + host_pa: u64, + access_type: AccessType, + ) -> Result<(), HypervisorError> { + let mut mtrr = Mtrr::new(); + + self.map_pt(guest_pa, host_pa, access_type, &mut mtrr)?; + + Ok(()) + } + + /// Unmaps a 2MB page by clearing the corresponding page directory entry. + /// + /// This function clears the entry, effectively removing any mapping for the 2MB page. + /// It's used when transitioning a region of memory from a single large page to multiple smaller pages or simply freeing the page. + /// + /// # Arguments + /// + /// * `entry`: Mutable reference to the page directory entry to unmap. + pub fn unmap_2mb(entry: &mut Entry) { + if !entry.readable() { + // The page is already not present; no action needed. + return; + } + + // Unmap the large page and clear the flags + entry.set_readable(false); + entry.set_writable(false); + entry.set_executable(false); + entry.set_memory_type(0); + entry.set_large(false); + entry.set_pfn(0); // Reset the Page Frame Number + } + + /// Unmaps a 4KB page, typically involved in deconstructing finer-grained page tables. + /// + /// This function wraps the unmap_2mb function, as the actual unmap logic is similar. + /// It's used for unmap operations specifically targeting 4KB pages. + /// + /// # Arguments + /// + /// * `entry`: Mutable reference to the page directory entry of the 4KB page to unmap. + #[allow(dead_code)] + fn unmap_4kb(entry: &mut Entry) { + // Delegate to the unmap_2mb function as the unmap logic is the same. + Self::unmap_2mb(entry); + } + + /// Creates an Extended Page Table Pointer (EPTP) with a Write-Back memory type and a 4-level page walk. + /// + /// This function is used in the setup of Intel VT-x virtualization, specifically for configuring the EPT. + /// It encodes the provided physical base address of the EPT PML4 table into the EPTP format, setting + /// the memory type to Write-Back and indicating a 4-level page walk. + /// + /// # Returns + /// A `Result` containing the configured EPTP value. Returns an error if + /// the base address is not properly aligned. + /// + /// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 28.2.6 EPT Paging-Structure Entries + pub fn create_eptp_with_wb_and_4lvl_walk(&self) -> Result { + // Get the virtual address of the PML4 table for EPT. + let addr = addr_of!(self.pml4) as u64; + + // Get the physical address of the PML4 table for EPT. + let ept_pml4_base_addr = addr; + + // Represents the EPT page walk length for Intel VT-x, specifically for a 4-level page walk. + // The value is 3 (encoded as '3 << 3' in EPTP) because the EPTP encoding requires "number of levels minus one". + const EPT_PAGE_WALK_LENGTH_4: u64 = 3 << 3; + + // Represents the memory type setting for Write-Back (WB) in the EPTP. + const EPT_MEMORY_TYPE_WB: u64 = MemoryType::WriteBack as u64; + + // Check if the base address is 4KB aligned (the lower 12 bits should be zero). + if ept_pml4_base_addr.trailing_zeros() >= 12 { + // Construct the EPTP with the page walk length and memory type for WB. + Ok(ept_pml4_base_addr | EPT_PAGE_WALK_LENGTH_4 | EPT_MEMORY_TYPE_WB) + } else { + Err(HypervisorError::InvalidEptPml4BaseAddress) + } + } +} + +/// Represents an EPT PML4 Entry (PML4E) that references a Page-Directory-Pointer Table. +/// +/// PML4 is the top level in the EPT paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: Table 29-1. Format of an EPT PML4 Entry (PML4E) that References an EPT Page-Directory-Pointer Table +#[derive(Debug, Clone, Copy)] +struct Pml4(Table); + +/// Represents an EPT Page-Directory-Pointer-Table Entry (PDPTE) that references an EPT Page Directory. +/// +/// PDPTEs are part of the second level in the EPT paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: Table 29-3. Format of an EPT Page-Directory-Pointer-Table Entry (PDPTE) that References an EPT Page Directory +#[derive(Debug, Clone, Copy)] +struct Pdpt(Table); + +/// Represents an EPT Page-Directory Entry (PDE) that references an EPT Page Table. +/// +/// PDEs are part of the third level in the EPT paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: Table 29-5. Format of an EPT Page-Directory Entry (PDE) that References an EPT Page Table +#[derive(Debug, Clone, Copy)] +struct Pd(Table); + +/// Represents an EPT Page-Table Entry (PTE) that maps a 4-KByte Page. +/// +/// PTEs are the lowest level in the EPT paging hierarchy and are used to map individual +/// pages to guest-physical addresses. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: Format of an EPT Page-Table Entry that Maps a 4-KByte Page +#[derive(Debug, Clone, Copy)] +struct Pt(Table); + +/// General struct to represent a table in the EPT paging structure. +/// +/// This struct is used as a basis for PML4, PDPT, PD, and PT. It contains an array of entries +/// where each entry can represent different levels of the EPT hierarchy. +#[repr(C, align(4096))] +#[derive(Debug, Clone, Copy)] +struct Table { + entries: [Entry; 512], +} + +bitfield! { + /// Represents an Extended Page Table Entry (EPT Entry). + /// + /// EPT entries are used in Intel VT-x virtualization to manage memory access + /// permissions and address mapping for virtual machines. + /// + /// # Fields + /// + /// * `readable` - If set, the memory region can be read. + /// * `writable` - If set, the memory region can be written to. + /// * `executable` - If set, code can be executed from the memory region. + /// * `memory_type` - The memory type (e.g., WriteBack, Uncacheable). + /// * `large` - If set, this entry maps a large page. + /// * `pfn` - The Page Frame Number, indicating the physical address. + /// * `verify_guest_paging` - Additional flag for guest paging verification. + /// * `paging_write_access` - Additional flag for paging write access. + /// + /// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 29.3.2 EPT Translation Mechanism + #[derive(Clone, Copy)] + pub struct Entry(u64); + impl Debug; + + // Flag definitions for an EPT entry. + pub readable, set_readable: 0; + pub writable, set_writable: 1; + pub executable, set_executable: 2; + pub memory_type, set_memory_type: 5, 3; + pub large, set_large: 7; + pub pfn, set_pfn: 51, 12; + pub verify_guest_paging, set_verify_guest_paging: 57; + pub paging_write_access, set_paging_write_access: 58; +} \ No newline at end of file diff --git a/hypervisor/src/intel/mod.rs b/hypervisor/src/intel/mod.rs new file mode 100644 index 0000000..5fa39ae --- /dev/null +++ b/hypervisor/src/intel/mod.rs @@ -0,0 +1,21 @@ +pub mod capture; +//pub mod controls; +//pub mod descriptor; +pub mod ept; +//pub mod events; +//pub mod invept; +//pub mod invvpid; +//pub mod msr_bitmap; +pub mod paging; +//pub mod segmentation; +//pub mod shared_data; +pub mod support; +pub mod vm; +pub mod vmcs; +//pub mod vmerror; +//pub mod vmexit; +//pub mod vmlaunch; +//pub mod vmm; +//pub mod vmstack; +pub mod vmx; +pub mod vmxon; diff --git a/hypervisor/src/intel/page.rs b/hypervisor/src/intel/page.rs new file mode 100644 index 0000000..7004970 --- /dev/null +++ b/hypervisor/src/intel/page.rs @@ -0,0 +1,9 @@ +use x86::bits64::paging::BASE_PAGE_SIZE; + +/// The structure representing a single memory page (4KB). +// +// This does not _always_ have to be allocated at the page aligned address, but +// very often it is, so let us specify the alignment. +#[derive(Debug, Clone, Copy)] +#[repr(C, align(4096))] +pub struct Page([u8; BASE_PAGE_SIZE]); \ No newline at end of file diff --git a/hypervisor/src/intel/paging.rs b/hypervisor/src/intel/paging.rs new file mode 100644 index 0000000..b0d8209 --- /dev/null +++ b/hypervisor/src/intel/paging.rs @@ -0,0 +1,169 @@ +//! Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.2 HIERARCHICAL PAGING STRUCTURES: AN OVERVIEW +//! This section covers the standard paging mechanism as used in x86-64 architecture. +//! Standard paging controls how virtual memory addresses are translated to physical memory addresses. +//! +//! Credits to the work by Satoshi in their 'Hello-VT-rp' project for assistance and a clear implementation of this Paging Structure: +//! https://github.com/tandasat/Hello-VT-rp/blob/main/hypervisor/src/paging_structures.rs + +use { + crate::error::HypervisorError, + bitfield::bitfield, + core::ptr::addr_of, + x86::current::paging::{BASE_PAGE_SHIFT, LARGE_PAGE_SIZE}, +}; + +/// Represents the entire Page Tables structure for the hypervisor. +/// +/// The Page Tables mechanism is crucial for virtual memory management in x86-64 architecture. +/// It consists of four levels of tables: PML4, PDPT, PD, and PT, which together facilitate the translation of virtual to physical addresses. +/// +/// Each level of the Page Tables plays a role in this translation process: +/// - PML4 (Page Map Level 4) is the highest level and points to the next level. +/// - PDPT (Page Directory Pointer Table) points to Page Directories. +/// - PD (Page Directory) contains entries that either point to Page Tables or map large pages (2MB). +/// - PT (Page Table) contains entries that map standard 4KB pages. +/// +/// This structure is aligned to 4096 bytes (4KB), which is the size of a standard page in x86-64. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 4-LEVEL PAGING AND 5-LEVEL PAGING +#[repr(C, align(4096))] +pub struct PageTables { + /// Page Map Level 4 (PML4) Table. + pml4: Pml4, + /// Page Directory Pointer Table (PDPT). + pdpt: Pdpt, + /// Array of Page Directory Table (PDT). + pd: [Pd; 512], +} + +impl PageTables { + /// Builds a basic identity map for the page tables. + /// + /// This setup ensures that each virtual address directly maps to the same physical address, + /// a common setup for the initial stages of an operating system or hypervisor. + pub fn build_identity(&mut self) { + // Configure the first entry in the PML4 table. + // Set it to present and writable, pointing to the base of the PDPT. + self.pml4.0.entries[0].set_present(true); + self.pml4.0.entries[0].set_writable(true); + self.pml4.0.entries[0].set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); + + // Start mapping physical addresses from 0. + let mut pa = 0; + + // Iterate over each PDPT entry. + for (i, pdpte) in self.pdpt.0.entries.iter_mut().enumerate() { + // Set each PDPT entry to present and writable, + // pointing to the corresponding page directory (PD). + pdpte.set_present(true); + pdpte.set_writable(true); + pdpte.set_pfn( + addr_of!(self.pd[i]) as u64 >> BASE_PAGE_SHIFT, + ); + + // Configure each entry in the PD to map a large page (e.g., 2MB). + for pde in &mut self.pd[i].0.entries { + // Set each PD entry to present, writable, and as a large page. + // Point it to the corresponding physical address. + pde.set_present(true); + pde.set_writable(true); + pde.set_large(true); + pde.set_pfn(pa >> BASE_PAGE_SHIFT); + + // Increment the physical address by the size of a large page. + pa += LARGE_PAGE_SIZE as u64; + } + } + } + + /// Gets the physical address of the PML4 table, ensuring it is 4KB aligned. + /// + /// This method is typically used to retrieve the address to be loaded into CR3. + /// + /// # Returns + /// A `Result` containing the 4KB-aligned physical address of the PML4 table + /// or an error if the address is not aligned. + /// + /// # Errors + /// Returns `HypervisorError::InvalidCr3BaseAddress` if the address is not 4KB aligned. + pub fn get_pml4_pa(&self) -> Result { + // Retrieve the virtual address of the PML4 table. + let addr = addr_of!(self.pml4) as u64; + + // Get the physical address of the PML4 table for CR3. + let pa = addr; + + // Check if the base address is 4KB aligned (the lower 12 bits should be zero). + if pa.trailing_zeros() >= BASE_PAGE_SHIFT as u32 { + Ok(pa) + } else { + Err(HypervisorError::InvalidCr3BaseAddress) + } + } +} + +/// Represents a PML4 Entry (PML4E) that references a Page-Directory-Pointer Table. +/// +/// PML4 is the top level in the standard x86-64 paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 Paging +#[derive(Debug, Clone, Copy)] +struct Pml4(Table); + +/// Represents a Page-Directory-Pointer-Table Entry (PDPTE) that references a Page Directory. +/// +/// PDPTEs are part of the second level in the standard x86-64 paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 Paging +#[derive(Debug, Clone, Copy)] +struct Pdpt(Table); + +/// Represents a Page-Directory Entry (PDE) that references a Page Table. +/// +/// PDEs are part of the third level in the standard x86-64 paging hierarchy. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 Paging +#[derive(Debug, Clone, Copy)] +struct Pd(Table); + +/// Represents a Page-Table Entry (PTE) that maps a 4-KByte Page. +/// +/// PTEs are the lowest level in the standard x86-64 paging hierarchy and are used to map individual +/// pages to physical addresses. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 Paging +#[derive(Debug, Clone, Copy)] +struct Pt(Table); + +/// General struct to represent a table in the standard paging structure. +/// +/// This struct is used as a basis for PML4, PDPT, PD, and PT. It contains an array of entries +/// where each entry can represent different levels of the paging hierarchy. +#[repr(C, align(4096))] +#[derive(Debug, Clone, Copy)] +struct Table { + entries: [Entry; 512], +} + +bitfield! { + /// Represents a Page Table Entry in standard paging. + /// + /// These entries are used to manage memory access and address mapping. + /// + /// # Fields + /// + /// * `present` - If set, the memory region is accessible. + /// * `writable` - If set, the memory region can be written to. + /// * `large` - If set, this entry maps a large page. + /// * `pfn` - The Page Frame Number, indicating the physical address. + /// + /// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 4.5 Paging + #[derive(Clone, Copy)] + pub struct Entry(u64); + impl Debug; + + present, set_present: 0; + writable, set_writable: 1; + large, set_large: 7; + pfn, set_pfn: 51, 12; +} \ No newline at end of file diff --git a/hypervisor/src/intel/shared.rs b/hypervisor/src/intel/shared.rs new file mode 100644 index 0000000..64830d4 --- /dev/null +++ b/hypervisor/src/intel/shared.rs @@ -0,0 +1,59 @@ +//! A crate for managing hypervisor functionality, particularly focused on +//! Extended Page Tables (EPT) and Model-Specific Register (MSR) bitmaps. +//! Includes support for primary and optional secondary EPTs. + +use { + crate::{ + error::HypervisorError, + intel::{ + ept::paging::Ept, + }, + }, + alloc::boxed::Box, +}; + +/// Represents shared data structures for hypervisor operations. +/// +/// This struct manages the MSR (Model-Specific Register) bitmap and Extended Page Tables (EPT) +/// for the hypervisor, enabling memory virtualization and control over certain processor features. +#[repr(C)] +pub struct SharedData { + /// The primary Extended Page Table. + pub primary_ept: Box, + + /// The pointer to the primary EPT (Extended Page Table Pointer). + pub primary_eptp: u64, + + /// The secondary Extended Page Table. + pub secondary_ept: Box, + + /// The pointer to the secondary EPT (Extended Page Table Pointer). + pub secondary_eptp: u64, +} + +impl SharedData { + /// Creates a new instance of `SharedData` with primary and optionally secondary EPTs. + /// + /// This function initializes the MSR bitmap and sets up the EPTs. + /// + /// # Arguments + /// + /// * `primary_ept`: The primary EPT to be used. + /// * `secondary_ept`: The secondary EPT to be used if the feature is enabled. + /// + /// # Returns + /// A result containing a boxed `SharedData` instance or an error of type `HypervisorError`. + pub fn new(primary_ept: Box, secondary_ept: Box) -> Result, HypervisorError> { + log::trace!("Initializing shared data"); + + let primary_eptp = primary_ept.create_eptp_with_wb_and_4lvl_walk()?; + let secondary_eptp = secondary_ept.create_eptp_with_wb_and_4lvl_walk()?; + + Ok(Box::new(Self { + primary_ept, + primary_eptp, + secondary_ept, + secondary_eptp, + })) + } +} diff --git a/hypervisor/src/intel/support.rs b/hypervisor/src/intel/support.rs new file mode 100644 index 0000000..44ad5f4 --- /dev/null +++ b/hypervisor/src/intel/support.rs @@ -0,0 +1,142 @@ +#![allow(dead_code)] + +use { + core::arch::asm, + x86::{ + controlregs::{Cr0, Cr4, Xcr0}, + dtables::DescriptorTablePointer, + }, + crate::error::HypervisorError, +}; + +/// Enable VMX operation. +pub fn vmxon(vmxon_region: u64) { + unsafe { x86::bits64::vmx::vmxon(vmxon_region).unwrap() }; +} + +/// Disable VMX operation. +pub fn vmxoff() -> Result<(), HypervisorError> { + match unsafe { x86::bits64::vmx::vmxoff() } { + Ok(_) => Ok(()), + Err(_) => Err(HypervisorError::VMXOFFFailed), + } +} + +/// Clear VMCS. +pub fn vmclear(vmcs_region: u64) { + unsafe { x86::bits64::vmx::vmclear(vmcs_region).unwrap() }; +} + +/// Load current VMCS pointer. +pub fn vmptrld(vmcs_region: u64) { + unsafe { x86::bits64::vmx::vmptrld(vmcs_region).unwrap() } +} + +/* +/// Return current VMCS pointer. +#[allow(dead_code)] +pub fn vmptrst() -> *const Vmcs { + unsafe { x86::bits64::vmx::vmptrst().unwrap() as *const Vmcs } +} +*/ + +/// Read a specified field from a VMCS. +pub fn vmread(field: u32) -> u64 { + unsafe { x86::bits64::vmx::vmread(field) }.unwrap_or(0) +} + +/// Write to a specified field in a VMCS. +pub fn vmwrite>(field: u32, val: T) + where + u64: From, +{ + unsafe { x86::bits64::vmx::vmwrite(field, u64::from(val)) }.unwrap(); +} + + +/// Write to Extended Control Register XCR0. Only supported if CR4_ENABLE_OS_XSAVE is set. +pub fn xsetbv(val: Xcr0) { + unsafe { x86::controlregs::xcr0_write(val) }; +} + +/// Write back all modified cache contents to memory and invalidate the caches. +#[inline(always)] +pub fn wbinvd() { + unsafe { + asm!("wbinvd", options(nostack, nomem)); + } +} + +/// Returns the timestamp counter value. +pub fn rdtsc() -> u64 { + unsafe { core::arch::x86_64::_rdtsc() } +} + +/// Reads an MSR. +pub fn rdmsr(msr: u32) -> u64 { + unsafe { x86::msr::rdmsr(msr) } +} + +/// Writes a value to an MSR. +pub fn wrmsr(msr: u32, value: u64) { + unsafe { x86::msr::wrmsr(msr, value) }; +} + +/// Reads the CR0 register. +pub fn cr0() -> Cr0 { + unsafe { x86::controlregs::cr0() } +} + +/// Writes a value to the CR0 register. +pub fn cr0_write(val: Cr0) { + unsafe { x86::controlregs::cr0_write(val) }; +} + +/// Reads the CR3 register. +pub fn cr3() -> u64 { + unsafe { x86::controlregs::cr3() } +} + +/// Reads the CR4 register. +pub fn cr4() -> Cr4 { + unsafe { x86::controlregs::cr4() } +} + +/// Writes a value to the CR4 register. +pub fn cr4_write(val: Cr4) { + unsafe { x86::controlregs::cr4_write(val) }; +} + +/// Disables maskable interrupts. +pub fn cli() { + unsafe { x86::irq::disable() }; +} + +/// Halts execution of the processor. +pub fn hlt() { + unsafe { x86::halt() }; +} + +/// Reads 8-bits from an IO port. +pub fn inb(port: u16) -> u8 { + unsafe { x86::io::inb(port) } +} + +/// Writes 8-bits to an IO port. +pub fn outb(port: u16, val: u8) { + unsafe { x86::io::outb(port, val) }; +} + +/// Reads the IDTR register. +pub fn sidt() -> DescriptorTablePointer { + let mut idtr = DescriptorTablePointer::::default(); + unsafe { x86::dtables::sidt(&mut idtr) }; + idtr +} + +/// Reads the GDTR. +pub fn sgdt() -> DescriptorTablePointer { + let mut gdtr = DescriptorTablePointer::::default(); + unsafe { x86::dtables::sgdt(&mut gdtr) }; + gdtr +} \ No newline at end of file diff --git a/hypervisor/src/intel/vm.rs b/hypervisor/src/intel/vm.rs new file mode 100644 index 0000000..d8f2bd9 --- /dev/null +++ b/hypervisor/src/intel/vm.rs @@ -0,0 +1,26 @@ +use alloc::boxed::Box; +use crate::intel::capture::GuestRegisters; +use crate::intel::ept::paging::Ept; +use crate::intel::paging::PageTables; +use crate::intel::vmcs::Vmcs; + +pub struct Vm { + //vmcs: Box, + + pub host_paging_structures: Box, + + pub ept: Box, + + /// The guest's general-purpose registers state. + pub guest_registers: GuestRegisters, +} + +impl Vm { + pub fn new() -> Self { + let mut vmcs = Box::::default(); + + let mut host_paging = Box::::new_zeroed(); + + host_paging.build_identity(); + } +} \ No newline at end of file diff --git a/hypervisor/src/intel/vmcs.rs b/hypervisor/src/intel/vmcs.rs new file mode 100644 index 0000000..7558b71 --- /dev/null +++ b/hypervisor/src/intel/vmcs.rs @@ -0,0 +1,28 @@ +use x86::bits64::paging::BASE_PAGE_SIZE; +use crate::intel::support::rdmsr; + +/// Represents the VMCS region in memory. +/// +/// The VMCS region is essential for VMX operations on the CPU. +/// This structure offers methods for setting up the VMCS region, adjusting VMCS entries, +/// and performing related tasks. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 25.2 FORMAT OF THE VMCS REGION +#[repr(C, align(4096))] +pub struct Vmcs { + pub revision_id: u32, + pub abort_indicator: u32, + pub reserved: [u8; BASE_PAGE_SIZE - 8], +} +const _: () = assert_eq!(core::mem::size_of::(), BASE_PAGE_SIZE); + + +impl Default for Vmcs { + fn default() -> Self { + Self { + revision_id: rdmsr(x86::msr::IA32_VMX_BASIC) as u32, + abort_indicator: 0, + reserved: [0; BASE_PAGE_SIZE - 8], + } + } +} \ No newline at end of file diff --git a/hypervisor/src/intel/vmx.rs b/hypervisor/src/intel/vmx.rs new file mode 100644 index 0000000..663d8b7 --- /dev/null +++ b/hypervisor/src/intel/vmx.rs @@ -0,0 +1,28 @@ +use bit_field::BitField; +use crate::error::HypervisorError; +use crate::intel::support::vmxon; +use crate::intel::vmxon::Vmxon; + +pub struct Vmx { + pub vmxon_region: Vmxon, +} + +impl Vmx { + pub fn new() -> Self { + Self { + vmxon_region: Vmxon::default(), + } + } + + pub fn enable(&mut self) -> Result<(), HypervisorError> { + log::trace!("Setting up VMXON region"); + self.setup_vmxon()?; + log::trace!("VMXON region setup successfully!"); + + log::trace!("Executing VMXON instruction"); + vmxon(&mut self.vmxon_region as *mut _ as _); + log::trace!("VMXON executed successfully!"); + + Ok(()) + } +} \ No newline at end of file diff --git a/hypervisor/src/intel/vmxon.rs b/hypervisor/src/intel/vmxon.rs new file mode 100644 index 0000000..39c52c2 --- /dev/null +++ b/hypervisor/src/intel/vmxon.rs @@ -0,0 +1,114 @@ +use { + crate::{ + error::HypervisorError, + intel::support::rdmsr, + }, + bitfield::BitMut, + x86::{msr, current::paging::BASE_PAGE_SIZE}, + x86_64::registers::control::Cr4, + x86::controlregs, +}; +use crate::intel::vmcs::Vmcs; + +/// A representation of the VMXON region in memory. +/// +/// The VMXON region is essential for enabling VMX operations on the CPU. +/// This structure offers methods for setting up the VMXON region, enabling VMX operations, +/// and performing related tasks. +/// +/// Reference: Intel® 64 and IA-32 Architectures Software Developer's Manual: 25.11.5 VMXON Region +#[repr(C, align(4096))] +pub struct Vmxon { + pub revision_id: u32, + pub data: [u8; BASE_PAGE_SIZE - 4], +} +const _: () = assert_eq!(core::mem::size_of::(), BASE_PAGE_SIZE); + +impl Default for Vmxon { + fn default() -> Self { + Self { + revision_id: rdmsr(msr::IA32_VMX_BASIC) as u32, + data: [0; BASE_PAGE_SIZE - 4], + } + } +} + +impl Vmxon { + /// Enables VMX operation by setting appropriate bits and executing the VMXON instruction. + pub fn setup_vmxon(&mut self) -> Result<(), HypervisorError> { + log::trace!("Enabling Virtual Machine Extensions (VMX)"); + Self::enable_vmx_operation(); + log::trace!("VMX enabled"); + + log::trace!("Adjusting IA32_FEATURE_CONTROL MSR"); + Self::adjust_feature_control_msr()?; + log::trace!("IA32_FEATURE_CONTROL MSR adjusted"); + + log::trace!("Setting CR0 bits"); + Self::set_cr0_bits(); + log::trace!("CR0 bits set"); + + log::trace!("Setting CR4 bits"); + Self::set_cr4_bits(); + log::trace!("CR4 bits set"); + + self.revision_id.set_bit(31, false); + + Ok(()) + } + + /// Enables VMX operation by setting appropriate bits and executing the VMXON instruction. + fn enable_vmx_operation() { + const CR4_VMX_ENABLE_BIT: usize = 13; + let mut cr4 = Cr4::read_raw(); + cr4.set_bit(CR4_VMX_ENABLE_BIT, true); + unsafe { Cr4::write_raw(cr4) }; + } + + /// Sets the lock bit in IA32_FEATURE_CONTROL if necessary. + fn adjust_feature_control_msr() -> Result<(), HypervisorError> { + const VMX_LOCK_BIT: u64 = 1 << 0; + const VMXON_OUTSIDE_SMX: u64 = 1 << 2; + + let ia32_feature_control = unsafe { msr::rdmsr(msr::IA32_FEATURE_CONTROL) }; + + if (ia32_feature_control & VMX_LOCK_BIT) == 0 { + unsafe { + msr::wrmsr( + msr::IA32_FEATURE_CONTROL, + VMXON_OUTSIDE_SMX | VMX_LOCK_BIT | ia32_feature_control, + ) + }; + } else if (ia32_feature_control & VMXON_OUTSIDE_SMX) == 0 { + return Err(HypervisorError::VMXBIOSLock); + } + + Ok(()) + } + + /// Modifies CR0 to set and clear mandatory bits. + fn set_cr0_bits() { + let ia32_vmx_cr0_fixed0 = unsafe { msr::rdmsr(msr::IA32_VMX_CR0_FIXED0) }; + let ia32_vmx_cr0_fixed1 = unsafe { msr::rdmsr(msr::IA32_VMX_CR0_FIXED1) }; + + let mut cr0 = unsafe { controlregs::cr0() }; + + cr0 |= controlregs::Cr0::from_bits_truncate(ia32_vmx_cr0_fixed0 as usize); + cr0 &= controlregs::Cr0::from_bits_truncate(ia32_vmx_cr0_fixed1 as usize); + + unsafe { controlregs::cr0_write(cr0) }; + } + + /// Modifies CR4 to set and clear mandatory bits. + fn set_cr4_bits() { + let ia32_vmx_cr4_fixed0 = unsafe { msr::rdmsr(msr::IA32_VMX_CR4_FIXED0) }; + let ia32_vmx_cr4_fixed1 = unsafe { msr::rdmsr(msr::IA32_VMX_CR4_FIXED1) }; + + let mut cr4 = Cr4::read_raw(); + + cr4 |= ia32_vmx_cr4_fixed0; + cr4 &= ia32_vmx_cr4_fixed1; + + unsafe { Cr4::write_raw(cr4) }; + } +} \ No newline at end of file diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 1f121c3..dbe987b 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -14,4 +14,10 @@ extern crate alloc; extern crate static_assertions; pub mod error; +pub mod intel; +//pub mod vcpu; +//pub mod vmcs; +//pub mod vmerror; +//pub mod vmexit; +//pub mod vmlaunch; pub mod vmm; diff --git a/hypervisor/src/vmm.rs b/hypervisor/src/vmm.rs index e69de29..df19566 100644 --- a/hypervisor/src/vmm.rs +++ b/hypervisor/src/vmm.rs @@ -0,0 +1,29 @@ +use log::*; +use x86::cpuid::cpuid; +use crate::intel::vmx::Vmx; + +pub const CPUID_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x4000_0000; +pub const VENDOR_NAME: u32 = 0x5441_4c48; // "HLAT" + +pub fn start_hypervisor() -> ! { + debug!("Starting hypervisor"); + + let mut vmx = Vmx::new(); + + match vmx.enable() { + Ok(_) => debug!("VMX enabled"), + Err(e) => error!("Failed to enable VMX: {:?}", e), + }; + + let vm = Vm::new(); + + loop { + //vmx.vmlaunch(); + } +} + +/// Checks if this hypervisor is already installed. +pub fn is_hypervisor_present() -> bool { + let regs = cpuid!(CPUID_VENDOR_AND_MAX_FUNCTIONS); + (regs.ebx == regs.ecx) && (regs.ecx == regs.edx) && (regs.edx == VENDOR_NAME) +} \ No newline at end of file diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 0000000..c09ea17 --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,20 @@ +# Ensure the EFI\Boot directory structure exists on the USB drive D:\ and create if necessary +$efiBootDir = "D:\EFI\Boot" +New-Item -ItemType Directory -Path $efiBootDir -Force + +# Copy the EFI application to the EFI\Boot directory, renaming it to bootx64.efi +$efiFilePath = "C:\Users\memN0ps\Documents\GitHub\illusion-rs\target\x86_64-unknown-uefi\debug\illusion.efi" +Copy-Item -Path "$efiFilePath" -Destination "$efiBootDir\bootx64.efi" + +# Print the contents of the D:\ drive to verify the copy operation +Get-ChildItem -Path D:\ -Recurse + +# Define the path to the VMX file and vmrun.exe +$vmxPath = "C:\Users\memN0ps\Documents\Virtual Machines\Windows-10-UEFI-Dev\Windows-10-UEFI-Dev.vmx" +$vmrunPath = "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" + +# Append configuration to the VMX file for booting into firmware setup on next boot +Add-Content -Path "$vmxPath" -Value "bios.forceSetupOnce = `"`TRUE`"" + +# Start the VMware VM and open the GUI. Attempt to boot to firmware (if supported). +& "$vmrunPath" -T ws start "$vmxPath" gui