From fcd2f802362edeecc324d89f16f942036e183639 Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Fri, 16 Aug 2024 11:04:54 +0000 Subject: [PATCH 1/6] feat: add page backup functionality --- igvmbuilder/src/gpa_map.rs | 4 +- kernel/src/address.rs | 6 + kernel/src/mm/alloc.rs | 4 +- kernel/src/mm/mod.rs | 3 + kernel/src/mm/set.rs | 45 +++++++ kernel/src/protocols/backup.rs | 209 +++++++++++++++++++++++++++++++++ kernel/src/protocols/core.rs | 53 ++++++++- kernel/src/protocols/mod.rs | 2 + kernel/src/requests.rs | 4 +- kernel/src/sev/utils.rs | 23 +++- kernel/src/types.rs | 2 +- 11 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 kernel/src/mm/set.rs create mode 100644 kernel/src/protocols/backup.rs diff --git a/igvmbuilder/src/gpa_map.rs b/igvmbuilder/src/gpa_map.rs index 9e4b0c81f2..0747762ab6 100644 --- a/igvmbuilder/src/gpa_map.rs +++ b/igvmbuilder/src/gpa_map.rs @@ -142,8 +142,8 @@ impl GpaMap { // Calculate the kernel size and base. let kernel = match options.hypervisor { Hypervisor::Qemu => { - // Place the kernel area at 512 GB with a size of 16 MB. - GpaRange::new(0x0000008000000000, 0x01000000)? + // Place the kernel area at 512 GB with a size of 1 GB (to backup pages). + GpaRange::new(0x0000008000000000, 0x40000000)? } Hypervisor::HyperV => { // Place the kernel area at 64 MB with a size of 16 MB. diff --git a/kernel/src/address.rs b/kernel/src/address.rs index 9936d29505..6df8671d1e 100644 --- a/kernel/src/address.rs +++ b/kernel/src/address.rs @@ -5,6 +5,7 @@ // Author: Carlos López use crate::types::{PAGE_SHIFT, PAGE_SIZE}; +use crate::mm::PAGE_SIZE_2M; use core::fmt; use core::ops; @@ -55,6 +56,11 @@ pub trait Address: Self::from(self.bits() & !(PAGE_SIZE - 1)) } + #[inline] + fn page_align_2m(&self) -> Self { + Self::from(self.bits() & !(PAGE_SIZE_2M - 1)) + } + #[inline] fn is_aligned(&self, align: InnerAddr) -> bool { (self.bits() & (align - 1)) == 0 diff --git a/kernel/src/mm/alloc.rs b/kernel/src/mm/alloc.rs index c7b1b2f21c..c9e3bd7fde 100644 --- a/kernel/src/mm/alloc.rs +++ b/kernel/src/mm/alloc.rs @@ -40,8 +40,8 @@ impl From for SvsmError { } } -/// Maximum order of page allocations (up to 128kb) -pub const MAX_ORDER: usize = 6; +// Maximum order of page allocations (up to 1 GiB) (2^(MAX_ORDER-1)*4KiB) +pub const MAX_ORDER: usize = 19; /// Calculates the order of a given size for page allocation. /// diff --git a/kernel/src/mm/mod.rs b/kernel/src/mm/mod.rs index 9583287154..bc2c68d55c 100644 --- a/kernel/src/mm/mod.rs +++ b/kernel/src/mm/mod.rs @@ -17,6 +17,7 @@ pub mod stack; pub mod validate; pub mod virtualrange; pub mod vm; +pub mod set; pub use address_space::*; pub use guestmem::GuestPtr; @@ -29,3 +30,5 @@ pub use pagetable::PageTablePart; pub use alloc::{allocate_file_page, allocate_file_page_ref, PageRef}; pub use mappings::{mmap_kernel, mmap_user, munmap_kernel, munmap_user, VMMappingGuard}; + +pub use set::Set; \ No newline at end of file diff --git a/kernel/src/mm/set.rs b/kernel/src/mm/set.rs new file mode 100644 index 0000000000..7bf5bcff1d --- /dev/null +++ b/kernel/src/mm/set.rs @@ -0,0 +1,45 @@ +extern crate alloc; +use alloc::collections::BTreeSet; +use crate::locking::SpinLock; +use crate::address::PhysAddr; +use crate::types::PageSize; + +#[derive(Debug)] +pub struct Set { + set: SpinLock> +} + +impl Set { + pub const fn new() -> Self { + Self { + set: SpinLock::new(BTreeSet::new()) + } + } + + pub fn insert_addr(&self, value: PhysAddr, size: PageSize) { + log::info!("Inserting address {:#x} with size {:?}", value, size); + let mut guard = self.set.lock(); + guard.insert((value, size)); + } + + pub fn remove_addr(&self, value: PhysAddr, size: PageSize) -> bool { + let mut guard = self.set.lock(); + guard.remove(&(value, size)) + } + + pub fn contains_addr(&self, value: PhysAddr, size: PageSize) -> bool { + let guard = self.set.lock(); + guard.contains(&(value, size)) + } + + pub fn iter_addresses(&self) -> impl Iterator { + let guard = self.set.lock(); + let cloned_set = guard.clone(); + cloned_set.into_iter() + } + + pub fn size(&self) -> usize { + let guard = self.set.lock(); + guard.len() + } +} diff --git a/kernel/src/protocols/backup.rs b/kernel/src/protocols/backup.rs new file mode 100644 index 0000000000..7fb0b17e40 --- /dev/null +++ b/kernel/src/protocols/backup.rs @@ -0,0 +1,209 @@ +use crate::address::PhysAddr; +use crate::error::SvsmError; +use crate::protocols::errors::SvsmReqError; +use crate::protocols::RequestParams; +use crate::mm::set::Set; +use crate::sev::utils::rmp_set_read_only; +use crate::types::{PageSize, PAGE_SIZE, PAGE_SIZE_2M}; +use crate::mm::virtualrange::{VIRT_ALIGN_2M, VIRT_ALIGN_4K}; +use crate::mm::PerCPUPageMappingGuard; +use crate::mm::guestmem::read_u8; +use crate::mm::{writable_phys_addr, PageBox}; +use crate::utils::zero_mem_region; +use crate::locking::SpinLock; + +extern crate alloc; +use alloc::vec::Vec; + +use core::mem::MaybeUninit; +use core::ptr::NonNull; + +const SVSM_FULL_BACKUP: u32 = 0; +const SVSM_RESTORE: u32 = 1; +const SVSM_ENABLE_COPY_ON_WRITE: u32 = 2; +// TODO use after implementing partial backup +//const SVSM_PARTIAL_RESTORE: u32 = 3; + +#[repr(packed)] +#[derive(Copy, Clone)] +struct MemPage4K { + phys_addr: PhysAddr, + data: [u8; PAGE_SIZE], +} + +pub static PAGES_TO_BACKUP: Set = Set::new(); +pub static PAGES_TO_CLEAR: Set = Set::new(); + +pub static BACKUP_CREATED: SpinLock = SpinLock::new(false); + +static BACKUP_PAGES: SpinLock> = SpinLock::new(Vec::new()); +static ZERO_PAGES: SpinLock> = SpinLock::new(Vec::new()); + + +pub fn backup_protocol_request(request: u32, _params: &mut RequestParams) -> Result<(), SvsmReqError> { + match request { + SVSM_FULL_BACKUP => create_full_backup(), + SVSM_RESTORE => restore_pages_from_backup(), + SVSM_ENABLE_COPY_ON_WRITE => enable_copy_on_write(), + _ => Err(SvsmReqError::unsupported_call()), + } +} + +fn create_full_backup() -> Result<(), SvsmReqError> { + if *(BACKUP_CREATED.lock()) { + log::info!("Backup already exists. No new backup will be created."); + return Ok(()); + } + PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80e000usize), PageSize::Regular); // CPUID Page + PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80d000usize), PageSize::Regular); // Secrets Page + PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80f000usize), PageSize::Regular); // CAA Page + + log::info!("Starting to backup pages..."); + let mut total_size = 0; + let mut skipped = 0; + for (phys_addr, size) in PAGES_TO_BACKUP.iter_addresses() { + let (size_backed_up, size_skipped) = backup_page(phys_addr, size)?; + total_size += size_backed_up; + skipped += size_skipped; + } + log::info!("Backed up: {} Byte", total_size); + log::info!("Skipped: {} Byte", skipped); + + *(BACKUP_CREATED.lock()) = true; + log::info!("Successfully backed up pages."); + Ok(()) +} + +fn backup_page(paddr: PhysAddr, size: PageSize) -> Result<(u64, u64), SvsmError> { + match size { + PageSize::Regular => { + let success = backup_4k_page(paddr)?; + if success { + return Ok((PAGE_SIZE as u64, 0)) + } else { + return Ok((0, PAGE_SIZE as u64)) + } + } + PageSize::Huge => { + let mut start_addr: PhysAddr = paddr; + let mut backup_size = 0; + for i in 0..(PAGE_SIZE_2M/PAGE_SIZE) { + let success = backup_4k_page(start_addr)?; + if success { + backup_size += PAGE_SIZE as u64; + } + start_addr = paddr + i * PAGE_SIZE; + } + return Ok((backup_size, PAGE_SIZE_2M as u64 - backup_size)); + } + } + // TODO verify that data is private (for guest) +} + +fn backup_4k_page(paddr: PhysAddr) -> Result { + let guard = PerCPUPageMappingGuard::create(paddr, paddr+PAGE_SIZE, VIRT_ALIGN_4K)?; + let virt_addr = guard.virt_addr(); + + let mut backup = false; + let page_box_uninit: PageBox> = PageBox::try_new_uninit()?; + let page_box: PageBox = unsafe { page_box_uninit.assume_init() }; + let ref_page = PageBox::leak(page_box); + ref_page.phys_addr = paddr; + let mut zero = true; + for i in 0..(PAGE_SIZE){ + ref_page.data[i] = read_u8(virt_addr)?; + if ref_page.data[i] != 0 { + zero = false; + } + } + if zero { + let _ = unsafe {PageBox::from_raw(NonNull::from(ref_page))}; + let mut guard = ZERO_PAGES.lock(); + guard.push(paddr); + } + else { + let mut guard = BACKUP_PAGES.lock(); + guard.push(ref_page); + backup = true; + } + Ok(backup) +} + +fn restore_pages_from_backup() -> Result<(), SvsmReqError> { + log::info!("Starting to restore pages from backup"); + + log::info!("Restoring non-empty pages..."); + let guard = BACKUP_PAGES.lock(); + for page_src in guard.iter() { + restore_page(page_src)?; + } + + log::info!("Restoring empty pages..."); + let guard = ZERO_PAGES.lock(); + for &paddr in guard.iter() { + zero_page(paddr)?; + } + + // TODO reset additional pages used by adding them to page to clear + log::info!("Zeroing new pages..."); + for (_paddr, _size) in PAGES_TO_CLEAR.iter_addresses() { + // TODO + } + + log::info!("Successfully restored pages from backup"); + Ok(()) +} + +fn restore_page(page_src: &MemPage4K) -> Result<(), SvsmError> { + let paddr_dest = page_src.phys_addr; + if !writable_phys_addr(paddr_dest) { + log::info!("Skipping page {:#x}", paddr_dest); + return Ok(()); + } + let guard_cpu = PerCPUPageMappingGuard::create(paddr_dest, paddr_dest+PAGE_SIZE, VIRT_ALIGN_4K)?; + let virt_addr = guard_cpu.virt_addr(); + for i in 0..(PAGE_SIZE){ + let data = page_src.data[i]; + unsafe { + virt_addr.as_mut_ptr::().add(i).write(data); + } + } + log::info!("Restored page {:#x}", paddr_dest); + Ok(()) +} + +fn zero_page(paddr: PhysAddr) -> Result<(), SvsmError> { + if !writable_phys_addr(paddr){ + log::info!("Skipping page {:#x}", paddr); + return Ok(()); + } + let guard_cpu = PerCPUPageMappingGuard::create(paddr, paddr+PAGE_SIZE, VIRT_ALIGN_4K)?; + let virt_addr = guard_cpu.virt_addr(); + zero_mem_region(virt_addr, virt_addr+PAGE_SIZE); + log::info!("Zeroed page {:#x}", paddr); + Ok(()) +} + +fn enable_copy_on_write() -> Result<(), SvsmReqError> { + log::info!("Starting to enable copy-on-write..."); + for (phys_addr, size) in PAGES_TO_BACKUP.iter_addresses() { + set_read_only(phys_addr, size)?; + } + log::info!("Successfully enabled copy-on-write for validated pages"); + Ok(()) +} + +fn set_read_only(paddr: PhysAddr, size: PageSize) -> Result<(), SvsmError> { + let guard = match size { + PageSize::Huge => { + PerCPUPageMappingGuard::create(paddr, paddr+PAGE_SIZE_2M, VIRT_ALIGN_2M)? + } + PageSize::Regular => { + PerCPUPageMappingGuard::create(paddr, paddr+PAGE_SIZE, VIRT_ALIGN_4K)? + } + }; + let virt_addr = guard.virt_addr(); + rmp_set_read_only(virt_addr, size)?; + log::info!("Set read-only for page {:#x}, size {:?}", paddr, size); + Ok(()) +} \ No newline at end of file diff --git a/kernel/src/protocols/core.rs b/kernel/src/protocols/core.rs index 3db0fa0481..b1e3d27605 100644 --- a/kernel/src/protocols/core.rs +++ b/kernel/src/protocols/core.rs @@ -16,6 +16,7 @@ use crate::mm::{valid_phys_address, writable_phys_addr, GuestPtr}; use crate::protocols::apic::{APIC_PROTOCOL, APIC_PROTOCOL_VERSION_MAX, APIC_PROTOCOL_VERSION_MIN}; use crate::protocols::errors::SvsmReqError; use crate::protocols::RequestParams; +use crate::protocols::backup::{PAGES_TO_BACKUP, BACKUP_CREATED}; use crate::requests::SvsmCaa; use crate::sev::utils::{ pvalidate, rmp_clear_guest_vmsa, rmp_grant_guest_access, rmp_revoke_guest_access, @@ -247,7 +248,7 @@ fn core_configure_vtom(params: &mut RequestParams) -> Result<(), SvsmReqError> { } fn core_pvalidate_one(entry: u64, flush: &mut bool) -> Result<(), SvsmReqError> { - let (page_size_bytes, valign, huge) = match entry & 3 { + let (page_size_bytes, valign, size) = match entry & 3 { 0 => (PAGE_SIZE, VIRT_ALIGN_4K, PageSize::Regular), 1 => (PAGE_SIZE_2M, VIRT_ALIGN_2M, PageSize::Huge), _ => return Err(SvsmReqError::invalid_parameter()), @@ -278,10 +279,10 @@ fn core_pvalidate_one(entry: u64, flush: &mut bool) -> Result<(), SvsmReqError> if valid == PvalidateOp::Invalid { *flush |= true; - rmp_revoke_guest_access(vaddr, huge)?; + rmp_revoke_guest_access(vaddr, size)?; } - pvalidate(vaddr, huge, valid).or_else(|err| match err { + pvalidate(vaddr, size, valid).or_else(|err| match err { SvsmError::SevSnp(SevSnpError::FAIL_UNCHANGED(_)) if ign_cf => Ok(()), _ => Err(err), })?; @@ -318,9 +319,51 @@ fn core_pvalidate_one(entry: u64, flush: &mut bool) -> Result<(), SvsmReqError> } else { log::warn!("Not clearing possible read-only page at PA {:#x}", paddr); } - rmp_grant_guest_access(vaddr, huge)?; + rmp_grant_guest_access(vaddr, size)?; + } + if *(BACKUP_CREATED.lock()) { + // TODO implement + return Err(SvsmReqError::unsupported_call()); + } else { + match valid { + PvalidateOp::Valid => PAGES_TO_BACKUP.insert_addr(paddr, size), + PvalidateOp::Invalid => { + log::info!("Attemt to remove page from backup: {:#x}, size: {:?}", paddr, size); + match size { + PageSize::Regular => { + if PAGES_TO_BACKUP.contains_addr(paddr, size){ + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); + log::info!("Removed page from backup: {:#x}, size: {:?}", paddr, size); + } else if PAGES_TO_BACKUP.contains_addr(paddr.page_align_2m(), PageSize::Huge){ + // Split huge page into regular pages and add them to set + // Then remove the huge page and the regular page specified by paddr from the set + log::info!("Splitting page from backup: {:#x}, size: {:?}", paddr.page_align_2m(), size); + let base_addr = paddr.page_align_2m(); + for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ + PAGES_TO_BACKUP.insert_addr(base_addr + (i * PAGE_SIZE), PageSize::Regular); + } + PAGES_TO_BACKUP.remove_addr(base_addr, PageSize::Huge); + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); + log::info!("Removed page from backup: {:#x}", paddr); + } + }, + PageSize::Huge => { + // Scenario: 1. insert address 0x58000000 with size Huge, + // 2. insert 0x58000000 with size Regular, + // 3. insert 0x58001000 with size Regular, + // .... + // 4. remove 0x58000000 with size Huge + // --> pages 0x58000000, 0x58001000 ... with size Regular have to be removed too + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Huge); + for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ + PAGES_TO_BACKUP.remove_addr(paddr + (i * PAGE_SIZE), PageSize::Regular); + } + log::info!("Removed page from backup {:#x}, size: {:?}", paddr.page_align_2m(), PageSize::Huge); + } + } + } + }; } - Ok(()) } diff --git a/kernel/src/protocols/mod.rs b/kernel/src/protocols/mod.rs index 6166fa91a4..0d1a932891 100644 --- a/kernel/src/protocols/mod.rs +++ b/kernel/src/protocols/mod.rs @@ -7,6 +7,7 @@ pub mod apic; pub mod core; pub mod errors; +pub mod backup; #[cfg(all(feature = "mstpm", not(test)))] pub mod vtpm; @@ -16,6 +17,7 @@ use cpuarch::vmsa::{GuestVMExit, VMSA}; pub const SVSM_CORE_PROTOCOL: u32 = 0; pub const SVSM_VTPM_PROTOCOL: u32 = 2; pub const SVSM_APIC_PROTOCOL: u32 = 3; +pub const SVSM_CUSTOM_PROTOCOL: u32 = 4; #[derive(Debug, Default, Clone, Copy)] pub struct RequestParams { diff --git a/kernel/src/requests.rs b/kernel/src/requests.rs index 25f1be202d..46845c8da3 100644 --- a/kernel/src/requests.rs +++ b/kernel/src/requests.rs @@ -10,12 +10,13 @@ use crate::error::SvsmError; use crate::mm::GuestPtr; use crate::protocols::apic::apic_protocol_request; use crate::protocols::core::core_protocol_request; +use crate::protocols::backup::backup_protocol_request; use crate::protocols::errors::{SvsmReqError, SvsmResultCode}; use crate::sev::ghcb::switch_to_vmpl; #[cfg(all(feature = "mstpm", not(test)))] use crate::protocols::{vtpm::vtpm_protocol_request, SVSM_VTPM_PROTOCOL}; -use crate::protocols::{RequestParams, SVSM_APIC_PROTOCOL, SVSM_CORE_PROTOCOL}; +use crate::protocols::{RequestParams, SVSM_APIC_PROTOCOL, SVSM_CORE_PROTOCOL, SVSM_CUSTOM_PROTOCOL}; use crate::sev::vmsa::VMSAControl; use crate::types::GUEST_VMPL; use crate::utils::halt; @@ -111,6 +112,7 @@ fn request_loop_once( #[cfg(all(feature = "mstpm", not(test)))] SVSM_VTPM_PROTOCOL => vtpm_protocol_request(request, params).map(|_| true), SVSM_APIC_PROTOCOL => apic_protocol_request(request, params).map(|_| true), + SVSM_CUSTOM_PROTOCOL => backup_protocol_request(request, params).map(|_| true), _ => Err(SvsmReqError::unsupported_protocol()), } } diff --git a/kernel/src/sev/utils.rs b/kernel/src/sev/utils.rs index 796f808a0c..ff04a340a2 100644 --- a/kernel/src/sev/utils.rs +++ b/kernel/src/sev/utils.rs @@ -119,7 +119,9 @@ pub fn pvalidate(vaddr: VirtAddr, size: PageSize, valid: PvalidateOp) -> Result< } let changed = cf == 0; - + if valid == PvalidateOp::Invalid { + log::info!("Make invalid {:#x}", rax); + } match ret { 0 if changed => Ok(()), 0 if !changed => Err(SevSnpError::FAIL_UNCHANGED(0x10).into()), @@ -184,6 +186,7 @@ bitflags::bitflags! { const BIT_VMSA = 1u64 << 16; const NONE = 0; const RWX = Self::READ.bits() | Self::WRITE.bits() | Self::X_USER.bits() | Self::X_SUPER.bits(); + const RX = Self::READ.bits() | Self::X_USER.bits() | Self::X_SUPER.bits(); const VMSA = Self::READ.bits() | Self::BIT_VMSA.bits(); } } @@ -238,6 +241,24 @@ pub fn rmp_revoke_guest_access(vaddr: VirtAddr, size: PageSize) -> Result<(), Sv Ok(()) } +pub fn rmp_set_read_only(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { + // guest has access to all VMPL levels >= guest VMPL + for vmpl in RMPFlags::GUEST_VMPL.bits()..=RMPFlags::VMPL3.bits() { + let vmpl = RMPFlags::from_bits_truncate(vmpl); + rmp_adjust(vaddr, vmpl | RMPFlags::RX, size)?; + } + Ok(()) +} + +pub fn rmp_set_read_write(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { + // guest has access to all VMPL levels >= guest VMPL + for vmpl in RMPFlags::GUEST_VMPL.bits()..=RMPFlags::VMPL3.bits() { + let vmpl = RMPFlags::from_bits_truncate(vmpl); + rmp_adjust(vaddr, vmpl | RMPFlags::RWX, size)?; + } + Ok(()) +} + pub fn rmp_grant_guest_access(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { rmp_adjust(vaddr, RMPFlags::GUEST_VMPL | RMPFlags::RWX, size) } diff --git a/kernel/src/types.rs b/kernel/src/types.rs index e77ba8b0b4..a7a48c9dad 100644 --- a/kernel/src/types.rs +++ b/kernel/src/types.rs @@ -12,7 +12,7 @@ pub const PAGE_SHIFT_2M: usize = 21; pub const PAGE_SIZE: usize = 1 << PAGE_SHIFT; pub const PAGE_SIZE_2M: usize = PAGE_SIZE * 512; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord )] pub enum PageSize { Regular, Huge, From 50a5a1209a23e94e9cc0c1b58afd810f53f96836 Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Fri, 16 Aug 2024 15:31:06 +0000 Subject: [PATCH 2/6] fix: backup pages correctly --- kernel/src/protocols/backup.rs | 38 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/kernel/src/protocols/backup.rs b/kernel/src/protocols/backup.rs index 7fb0b17e40..f59bf44157 100644 --- a/kernel/src/protocols/backup.rs +++ b/kernel/src/protocols/backup.rs @@ -24,11 +24,9 @@ const SVSM_ENABLE_COPY_ON_WRITE: u32 = 2; // TODO use after implementing partial backup //const SVSM_PARTIAL_RESTORE: u32 = 3; -#[repr(packed)] -#[derive(Copy, Clone)] -struct MemPage4K { +struct MemPage4K<'a> { phys_addr: PhysAddr, - data: [u8; PAGE_SIZE], + data: &'a mut [u8; PAGE_SIZE], } pub static PAGES_TO_BACKUP: Set = Set::new(); @@ -36,7 +34,7 @@ pub static PAGES_TO_CLEAR: Set = Set::new(); pub static BACKUP_CREATED: SpinLock = SpinLock::new(false); -static BACKUP_PAGES: SpinLock> = SpinLock::new(Vec::new()); +static BACKUP_PAGES: SpinLock>> = SpinLock::new(Vec::new()); static ZERO_PAGES: SpinLock> = SpinLock::new(Vec::new()); @@ -54,9 +52,6 @@ fn create_full_backup() -> Result<(), SvsmReqError> { log::info!("Backup already exists. No new backup will be created."); return Ok(()); } - PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80e000usize), PageSize::Regular); // CPUID Page - PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80d000usize), PageSize::Regular); // Secrets Page - PAGES_TO_BACKUP.insert_addr(PhysAddr::from(0x80f000usize), PageSize::Regular); // CAA Page log::info!("Starting to backup pages..."); let mut total_size = 0; @@ -105,16 +100,16 @@ fn backup_4k_page(paddr: PhysAddr) -> Result { let virt_addr = guard.virt_addr(); let mut backup = false; - let page_box_uninit: PageBox> = PageBox::try_new_uninit()?; - let page_box: PageBox = unsafe { page_box_uninit.assume_init() }; + let page_box_uninit: PageBox> = PageBox::try_new_uninit()?; + let page_box: PageBox<[u8; PAGE_SIZE]> = unsafe { page_box_uninit.assume_init() }; let ref_page = PageBox::leak(page_box); - ref_page.phys_addr = paddr; let mut zero = true; for i in 0..(PAGE_SIZE){ - ref_page.data[i] = read_u8(virt_addr)?; - if ref_page.data[i] != 0 { + let byte = read_u8(virt_addr+i)?; + if byte != 0 { zero = false; } + ref_page[i] = byte; } if zero { let _ = unsafe {PageBox::from_raw(NonNull::from(ref_page))}; @@ -123,7 +118,10 @@ fn backup_4k_page(paddr: PhysAddr) -> Result { } else { let mut guard = BACKUP_PAGES.lock(); - guard.push(ref_page); + guard.push(MemPage4K { + phys_addr: paddr, + data: ref_page, + }); backup = true; } Ok(backup) @@ -145,6 +143,7 @@ fn restore_pages_from_backup() -> Result<(), SvsmReqError> { } // TODO reset additional pages used by adding them to page to clear + // TODO flush TLB? log::info!("Zeroing new pages..."); for (_paddr, _size) in PAGES_TO_CLEAR.iter_addresses() { // TODO @@ -154,7 +153,7 @@ fn restore_pages_from_backup() -> Result<(), SvsmReqError> { Ok(()) } -fn restore_page(page_src: &MemPage4K) -> Result<(), SvsmError> { +fn restore_page(page_src: &MemPage4K<'_>) -> Result<(), SvsmError> { let paddr_dest = page_src.phys_addr; if !writable_phys_addr(paddr_dest) { log::info!("Skipping page {:#x}", paddr_dest); @@ -162,11 +161,8 @@ fn restore_page(page_src: &MemPage4K) -> Result<(), SvsmError> { } let guard_cpu = PerCPUPageMappingGuard::create(paddr_dest, paddr_dest+PAGE_SIZE, VIRT_ALIGN_4K)?; let virt_addr = guard_cpu.virt_addr(); - for i in 0..(PAGE_SIZE){ - let data = page_src.data[i]; - unsafe { - virt_addr.as_mut_ptr::().add(i).write(data); - } + unsafe { + virt_addr.as_mut_ptr::<[u8; PAGE_SIZE]>().write( *page_src.data); } log::info!("Restored page {:#x}", paddr_dest); Ok(()) @@ -206,4 +202,4 @@ fn set_read_only(paddr: PhysAddr, size: PageSize) -> Result<(), SvsmError> { rmp_set_read_only(virt_addr, size)?; log::info!("Set read-only for page {:#x}, size {:?}", paddr, size); Ok(()) -} \ No newline at end of file +} From 92541787d49187f2182bd8a66287d1e4cb566222 Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Thu, 22 Aug 2024 09:35:42 +0000 Subject: [PATCH 3/6] chore: refactor RMP modification --- kernel/src/sev/utils.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/kernel/src/sev/utils.rs b/kernel/src/sev/utils.rs index ff04a340a2..62dcda4695 100644 --- a/kernel/src/sev/utils.rs +++ b/kernel/src/sev/utils.rs @@ -234,27 +234,22 @@ pub fn rmp_adjust(addr: VirtAddr, flags: RMPFlags, size: PageSize) -> Result<(), } pub fn rmp_revoke_guest_access(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { - for vmpl in RMPFlags::GUEST_VMPL.bits()..=RMPFlags::VMPL3.bits() { - let vmpl = RMPFlags::from_bits_truncate(vmpl); - rmp_adjust(vaddr, vmpl | RMPFlags::NONE, size)?; - } - Ok(()) + modify_guest_flags(vaddr, size, RMPFlags::NONE) } pub fn rmp_set_read_only(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { - // guest has access to all VMPL levels >= guest VMPL - for vmpl in RMPFlags::GUEST_VMPL.bits()..=RMPFlags::VMPL3.bits() { - let vmpl = RMPFlags::from_bits_truncate(vmpl); - rmp_adjust(vaddr, vmpl | RMPFlags::RX, size)?; - } - Ok(()) + modify_guest_flags(vaddr, size, RMPFlags::RX) } pub fn rmp_set_read_write(vaddr: VirtAddr, size: PageSize) -> Result<(), SvsmError> { - // guest has access to all VMPL levels >= guest VMPL + modify_guest_flags(vaddr, size, RMPFlags::RWX) +} + +// Guest has access to all VMPL levels >= guest VMPL +fn modify_guest_flags(vaddr: VirtAddr, size: PageSize, flags: RMPFlags) -> Result<(), SvsmError> { for vmpl in RMPFlags::GUEST_VMPL.bits()..=RMPFlags::VMPL3.bits() { let vmpl = RMPFlags::from_bits_truncate(vmpl); - rmp_adjust(vaddr, vmpl | RMPFlags::RWX, size)?; + rmp_adjust(vaddr, vmpl | flags, size)?; } Ok(()) } From 4461f40b3c668f4d9329951593ecc199604ffa0b Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Thu, 22 Aug 2024 09:58:47 +0000 Subject: [PATCH 4/6] chore: code refactoring --- kernel/src/mm/mod.rs | 2 +- kernel/src/protocols/core.rs | 98 ++++++++++++++++++++---------------- kernel/src/sev/utils.rs | 5 +- 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/kernel/src/mm/mod.rs b/kernel/src/mm/mod.rs index bc2c68d55c..ff837991e8 100644 --- a/kernel/src/mm/mod.rs +++ b/kernel/src/mm/mod.rs @@ -31,4 +31,4 @@ pub use alloc::{allocate_file_page, allocate_file_page_ref, PageRef}; pub use mappings::{mmap_kernel, mmap_user, munmap_kernel, munmap_user, VMMappingGuard}; -pub use set::Set; \ No newline at end of file +pub use set::Set; diff --git a/kernel/src/protocols/core.rs b/kernel/src/protocols/core.rs index b1e3d27605..6d1d1f75eb 100644 --- a/kernel/src/protocols/core.rs +++ b/kernel/src/protocols/core.rs @@ -247,6 +247,60 @@ fn core_configure_vtom(params: &mut RequestParams) -> Result<(), SvsmReqError> { } } +fn update_pages_to_backup_invalid(paddr: PhysAddr, size: PageSize) -> Result<(), SvsmReqError> { + log::info!("Attemt to remove page from backup: {:#x}, size: {:?}", paddr, size); + + match size { + PageSize::Regular => { + if PAGES_TO_BACKUP.contains_addr(paddr, size){ + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); + log::info!("Removed page from backup: {:#x}, size: {:?}", paddr, size); + + } else if PAGES_TO_BACKUP.contains_addr(paddr.page_align_2m(), PageSize::Huge){ + + // Split huge page into regular pages and add them to set + // Then remove the huge page and the regular page specified by paddr from the set + log::info!("Splitting page from backup: {:#x}, size: {:?}", paddr.page_align_2m(), size); + let base_addr = paddr.page_align_2m(); + for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ + PAGES_TO_BACKUP.insert_addr(base_addr + (i * PAGE_SIZE), PageSize::Regular); + } + PAGES_TO_BACKUP.remove_addr(base_addr, PageSize::Huge); + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); + log::info!("Removed page from backup: {:#x}", paddr); + } + }, + PageSize::Huge => { + // Scenario: 1. insert address 0x58000000 with size Huge, + // 2. insert 0x58000000 with size Regular, + // 3. insert 0x58001000 with size Regular, + // .... + // 4. remove 0x58000000 with size Huge + // --> pages 0x58000000, 0x58001000 ... with size Regular have to be removed too + PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Huge); + for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ + PAGES_TO_BACKUP.remove_addr(paddr + (i * PAGE_SIZE), PageSize::Regular); + } + log::info!("Removed page from backup {:#x}, size: {:?}", paddr.page_align_2m(), PageSize::Huge); + } + } + Ok(()) + +} + +fn update_pages_to_backup(paddr: PhysAddr, size: PageSize, valid: PvalidateOp) -> Result<(), SvsmReqError> { + if *(BACKUP_CREATED.lock()) { + // TODO implement + return Err(SvsmReqError::unsupported_call()); + } else { + match valid { + PvalidateOp::Valid => PAGES_TO_BACKUP.insert_addr(paddr, size), + PvalidateOp::Invalid => update_pages_to_backup_invalid(paddr, size)?, + }; + } + Ok(()) +} + fn core_pvalidate_one(entry: u64, flush: &mut bool) -> Result<(), SvsmReqError> { let (page_size_bytes, valign, size) = match entry & 3 { 0 => (PAGE_SIZE, VIRT_ALIGN_4K, PageSize::Regular), @@ -321,49 +375,7 @@ fn core_pvalidate_one(entry: u64, flush: &mut bool) -> Result<(), SvsmReqError> } rmp_grant_guest_access(vaddr, size)?; } - if *(BACKUP_CREATED.lock()) { - // TODO implement - return Err(SvsmReqError::unsupported_call()); - } else { - match valid { - PvalidateOp::Valid => PAGES_TO_BACKUP.insert_addr(paddr, size), - PvalidateOp::Invalid => { - log::info!("Attemt to remove page from backup: {:#x}, size: {:?}", paddr, size); - match size { - PageSize::Regular => { - if PAGES_TO_BACKUP.contains_addr(paddr, size){ - PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); - log::info!("Removed page from backup: {:#x}, size: {:?}", paddr, size); - } else if PAGES_TO_BACKUP.contains_addr(paddr.page_align_2m(), PageSize::Huge){ - // Split huge page into regular pages and add them to set - // Then remove the huge page and the regular page specified by paddr from the set - log::info!("Splitting page from backup: {:#x}, size: {:?}", paddr.page_align_2m(), size); - let base_addr = paddr.page_align_2m(); - for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ - PAGES_TO_BACKUP.insert_addr(base_addr + (i * PAGE_SIZE), PageSize::Regular); - } - PAGES_TO_BACKUP.remove_addr(base_addr, PageSize::Huge); - PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Regular); - log::info!("Removed page from backup: {:#x}", paddr); - } - }, - PageSize::Huge => { - // Scenario: 1. insert address 0x58000000 with size Huge, - // 2. insert 0x58000000 with size Regular, - // 3. insert 0x58001000 with size Regular, - // .... - // 4. remove 0x58000000 with size Huge - // --> pages 0x58000000, 0x58001000 ... with size Regular have to be removed too - PAGES_TO_BACKUP.remove_addr(paddr, PageSize::Huge); - for i in 0..(PAGE_SIZE_2M / PAGE_SIZE){ - PAGES_TO_BACKUP.remove_addr(paddr + (i * PAGE_SIZE), PageSize::Regular); - } - log::info!("Removed page from backup {:#x}, size: {:?}", paddr.page_align_2m(), PageSize::Huge); - } - } - } - }; - } + update_pages_to_backup(paddr, size, valid)?; Ok(()) } diff --git a/kernel/src/sev/utils.rs b/kernel/src/sev/utils.rs index 62dcda4695..6e8e795359 100644 --- a/kernel/src/sev/utils.rs +++ b/kernel/src/sev/utils.rs @@ -119,9 +119,7 @@ pub fn pvalidate(vaddr: VirtAddr, size: PageSize, valid: PvalidateOp) -> Result< } let changed = cf == 0; - if valid == PvalidateOp::Invalid { - log::info!("Make invalid {:#x}", rax); - } + match ret { 0 if changed => Ok(()), 0 if !changed => Err(SevSnpError::FAIL_UNCHANGED(0x10).into()), @@ -173,6 +171,7 @@ pub fn raw_vmgexit() { } bitflags::bitflags! { + #[derive(Clone, Copy)] pub struct RMPFlags: u64 { const VMPL0 = 0; const VMPL1 = 1; From 4dbefc7aed9ff3f3327d3087e71980210a7a0572 Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Fri, 23 Aug 2024 15:33:39 +0000 Subject: [PATCH 5/6] fix: handle error because of size mismatch --- kernel/src/protocols/backup.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/kernel/src/protocols/backup.rs b/kernel/src/protocols/backup.rs index f59bf44157..94fec1db8a 100644 --- a/kernel/src/protocols/backup.rs +++ b/kernel/src/protocols/backup.rs @@ -4,6 +4,7 @@ use crate::protocols::errors::SvsmReqError; use crate::protocols::RequestParams; use crate::mm::set::Set; use crate::sev::utils::rmp_set_read_only; +use crate::sev::SevSnpError; use crate::types::{PageSize, PAGE_SIZE, PAGE_SIZE_2M}; use crate::mm::virtualrange::{VIRT_ALIGN_2M, VIRT_ALIGN_4K}; use crate::mm::PerCPUPageMappingGuard; @@ -199,7 +200,22 @@ fn set_read_only(paddr: PhysAddr, size: PageSize) -> Result<(), SvsmError> { } }; let virt_addr = guard.virt_addr(); - rmp_set_read_only(virt_addr, size)?; + match rmp_set_read_only(virt_addr, size) { + Ok(_) => (), + Err(e) => { + if let SvsmError::SevSnp(SevSnpError::FAIL_SIZEMISMATCH(_)) = e { + if size == PageSize::Huge{ + for i in 0..PAGE_SIZE_2M/PAGE_SIZE { + set_read_only(paddr+i*PAGE_SIZE, PageSize::Regular)?; + } + return Ok(()); + } + + } + log::error!("An error occurred while trying to set read-only: {:?}", e); + return Err(e); + }, + } log::info!("Set read-only for page {:#x}, size {:?}", paddr, size); Ok(()) } From 0ca0930561e25c95f712c285e055613f17132c93 Mon Sep 17 00:00:00 2001 From: Rico Finkbeiner Date: Fri, 23 Aug 2024 17:15:31 +0000 Subject: [PATCH 6/6] wip: add method for debugging --- kernel/src/protocols/backup.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kernel/src/protocols/backup.rs b/kernel/src/protocols/backup.rs index 94fec1db8a..0afe9dfe15 100644 --- a/kernel/src/protocols/backup.rs +++ b/kernel/src/protocols/backup.rs @@ -44,7 +44,9 @@ pub fn backup_protocol_request(request: u32, _params: &mut RequestParams) -> Res SVSM_FULL_BACKUP => create_full_backup(), SVSM_RESTORE => restore_pages_from_backup(), SVSM_ENABLE_COPY_ON_WRITE => enable_copy_on_write(), - _ => Err(SvsmReqError::unsupported_call()), + // TODO delete, for debugging purposes only + x => enable_copy_on_write_addr(x), + //_ => Err(SvsmReqError::unsupported_call()), } } @@ -181,6 +183,14 @@ fn zero_page(paddr: PhysAddr) -> Result<(), SvsmError> { Ok(()) } +// TODO delete, for debugging purposes only +fn enable_copy_on_write_addr(paddr: u32) -> Result<(), SvsmReqError> { + let phys_addr = PhysAddr::from((paddr as usize)) + 0x100000000; + log::info!("Enable copy-on-write for page {:#x}...", phys_addr); + set_read_only(phys_addr, PageSize::Regular)?; + Ok(()) +} + fn enable_copy_on_write() -> Result<(), SvsmReqError> { log::info!("Starting to enable copy-on-write..."); for (phys_addr, size) in PAGES_TO_BACKUP.iter_addresses() {