From 7e568a35e16354f9a8eb8b02b7c9123b003d5990 Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Fri, 20 Dec 2024 15:35:00 -0800 Subject: [PATCH] igvmbuilder: enable long mode paging in the initial VSM context The VSM environment requires CR0.PG to be set when entering VTL 2 for the first time. In this environment, the IGVM file must be built with initial page tables that map the low 4 GB, which permits stage 2 to start with paging enabled. This also requires that stage 2 must start in long mode, because the paging mode cannot be changed once the VM starts executing (if CR0.PG must always be set, then it is not possible to change from 3-level PAE tables to 4-level tables). Stage 2 will still start executing in 32-bit mode as usual, and will rebuild the page tables in its own memory area, as it always has, but that code can now execute with paging enabled as required by VSM. Signed-off-by: Jon Lange --- igvmbuilder/src/gpa_map.rs | 3 ++ igvmbuilder/src/igvm_builder.rs | 16 ++++++++- igvmbuilder/src/main.rs | 1 + igvmbuilder/src/paging.rs | 60 +++++++++++++++++++++++++++++++++ igvmbuilder/src/vmsa.rs | 26 ++++++++++---- 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 igvmbuilder/src/paging.rs diff --git a/igvmbuilder/src/gpa_map.rs b/igvmbuilder/src/gpa_map.rs index b54be4ab9..ac6fc038e 100644 --- a/igvmbuilder/src/gpa_map.rs +++ b/igvmbuilder/src/gpa_map.rs @@ -66,6 +66,7 @@ pub struct GpaMap { pub guest_context: GpaRange, pub kernel: GpaRange, pub vmsa: GpaRange, + pub init_page_tables: GpaRange, } impl GpaMap { @@ -73,6 +74,7 @@ impl GpaMap { options: &CmdOptions, firmware: &Option>, ) -> Result> { + // 0x010000-0x010FFF: initial page tables for VSM platforms // 0x800000-0x804FFF: zero-filled (must be pre-validated) // 0x805000-0x805FFF: initial stage 2 stack page // 0x806000-0x806FFF: Secrets page @@ -167,6 +169,7 @@ impl GpaMap { guest_context, kernel, vmsa, + init_page_tables: GpaRange::new(0x10000, 2 * PAGE_SIZE_4K)?, }; if options.verbose { println!("GPA Map: {gpa_map:#X?}"); diff --git a/igvmbuilder/src/igvm_builder.rs b/igvmbuilder/src/igvm_builder.rs index 4604bed27..367405078 100644 --- a/igvmbuilder/src/igvm_builder.rs +++ b/igvmbuilder/src/igvm_builder.rs @@ -26,6 +26,7 @@ use zerocopy::IntoBytes; use crate::cmd_options::{CmdOptions, Hypervisor}; use crate::cpuid::SnpCpuidPage; use crate::firmware::{parse_firmware, Firmware}; +use crate::paging::construct_init_page_tables; use crate::platform::PlatformMask; use crate::stage2_stack::Stage2Stack; use crate::vmsa::{construct_native_start_context, construct_start_context, construct_vmsa}; @@ -130,7 +131,11 @@ impl IgvmBuilder { // Construct a native context object to capture the start context. let start_rip = self.gpa_map.stage2_image.get_start(); let start_rsp = self.gpa_map.stage2_stack.get_end() - size_of::() as u64; - let start_context = construct_start_context(start_rip, start_rsp); + let start_context = construct_start_context( + start_rip, + start_rsp, + self.gpa_map.init_page_tables.get_start(), + ); self.build_directives(¶m_block, &start_context)?; self.build_initialization()?; @@ -513,6 +518,15 @@ impl IgvmBuilder { ); } + if COMPATIBILITY_MASK.contains(VSM_COMPATIBILITY_MASK) { + // Include initial page tables. + construct_init_page_tables( + self.gpa_map.init_page_tables.get_start(), + VSM_COMPATIBILITY_MASK, + &mut self.directives, + ); + } + Ok(()) } diff --git a/igvmbuilder/src/main.rs b/igvmbuilder/src/main.rs index b447ae451..7423a8cbf 100644 --- a/igvmbuilder/src/main.rs +++ b/igvmbuilder/src/main.rs @@ -16,6 +16,7 @@ mod gpa_map; mod igvm_builder; mod igvm_firmware; mod ovmf_firmware; +mod paging; mod platform; mod stage2_stack; mod vmsa; diff --git a/igvmbuilder/src/paging.rs b/igvmbuilder/src/paging.rs new file mode 100644 index 000000000..adf57eab7 --- /dev/null +++ b/igvmbuilder/src/paging.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2024 Microsoft Corporation +// +// Author: Jon Lange + +use igvm::IgvmDirectiveHeader; +use igvm_defs::{IgvmPageDataFlags, IgvmPageDataType, PAGE_SIZE_4K}; + +use zerocopy::{Immutable, IntoBytes}; + +#[derive(Clone, Copy, Immutable, IntoBytes)] +struct PageTablePage { + ptes: [u64; 512], +} + +#[derive(Clone, Copy, Default)] +struct InitPageTables { + pages: [PageTablePage; 2], +} + +impl Default for PageTablePage { + fn default() -> Self { + Self { ptes: [0; 512] } + } +} + +pub fn construct_init_page_tables( + init_page_table_gpa: u64, + compatibility_mask: u32, + directives: &mut Vec, +) { + let mut page_tables: InitPageTables = InitPageTables::default(); + + // The initial page tables comparise a single PML4E that points to a page + // that includes entries which map the low 4 GB of the address space + // with an identity map of 1 GB pages. + // This PML4E is present, writable, accesed, and dirty. + page_tables.pages[0].ptes[0] = 0x63 | (init_page_table_gpa + PAGE_SIZE_4K); + + for i in 0..4 { + // This PTE is present, writable, accesed, dirty, and large page. + page_tables.pages[1].ptes[i] = 0xE3 | ((i as u64) << 30); + } + + for (i, data) in page_tables.pages.iter().enumerate() { + // Allocate a byte vector to contain a copy of the initial page table + // data. + let mut page_table_data = Vec::::new(); + page_table_data.extend_from_slice(data.as_bytes()); + + directives.push(IgvmDirectiveHeader::PageData { + gpa: init_page_table_gpa + (i as u64) * PAGE_SIZE_4K, + compatibility_mask, + flags: IgvmPageDataFlags::new(), + data_type: IgvmPageDataType::NORMAL, + data: page_table_data, + }); + } +} diff --git a/igvmbuilder/src/vmsa.rs b/igvmbuilder/src/vmsa.rs index 8816062c6..ddc9c0f5c 100644 --- a/igvmbuilder/src/vmsa.rs +++ b/igvmbuilder/src/vmsa.rs @@ -12,7 +12,11 @@ use zerocopy07::FromZeroes; use crate::cmd_options::{Hypervisor, SevExtraFeatures}; -pub fn construct_start_context(start_rip: u64, start_rsp: u64) -> Vec { +pub fn construct_start_context( + start_rip: u64, + start_rsp: u64, + initial_cr3: u64, +) -> Vec { let mut vec: Vec = Vec::new(); // Establish CS as a 32-bit code selector. @@ -38,10 +42,18 @@ pub fn construct_start_context(start_rip: u64, start_rsp: u64) -> Vec { - vmsa.cr0 = *r; + // Remove CR0.PG. + vmsa.cr0 = *r & !0x8000_0000; } X86Register::Cr3(r) => { vmsa.cr3 = *r; @@ -249,7 +262,8 @@ pub fn construct_vmsa( vmsa.cr4 = *r; } X86Register::Efer(r) => { - vmsa.efer = *r; + // Remove EFER.LMA and EFER.LME. + vmsa.efer = *r & !0x500; } X86Register::Pat(r) => { vmsa.pat = *r;