diff --git a/Cargo.lock b/Cargo.lock index d338c4dc0..d46d1c83a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,9 +1122,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "modular-bitfield" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0d5274763b5572c8f29dadb5cd0bc59de64c805de433c1b556075f733b0a1a" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" dependencies = [ "modular-bitfield-impl", "static_assertions", @@ -1132,9 +1132,9 @@ dependencies = [ [[package]] name = "modular-bitfield-impl" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eec4327f127d4d18c54c8bfbf7b05d74cc9a1befdcc6283a241238ffbc84c6" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" dependencies = [ "proc-macro2", "quote", diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index 11d0cdb20..e4e604e2c 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -22,7 +22,7 @@ rangemap = { version = "1.5.1", features = ["const_fn"] } thiserror = { version = "2.0.6", default-features = false } num_enum = { version = "0.7.3", default-features = false } once_cell = { version = "1.20.2", default-features = false, features = ["alloc", "race"] } -modular-bitfield = { version = "0.12.0", default-features = false } +modular-bitfield = { version = "0.13.1", default-features = false } hashbrown = "0.15.2" elf = { version = "0.8.0", default-features = false } cms = { version = "0.2.3", default-features = false, features = ["alloc"] } diff --git a/litebox_platform_lvbs/src/mshv/error.rs b/litebox_platform_lvbs/src/mshv/error.rs index 5d6274be5..ddfe51056 100644 --- a/litebox_platform_lvbs/src/mshv/error.rs +++ b/litebox_platform_lvbs/src/mshv/error.rs @@ -152,6 +152,21 @@ pub enum VsmError { #[error("symbol name contains invalid UTF-8")] SymbolNameInvalidUtf8, + + #[error("invalid API attribute")] + ApiAttrInvalid, + + #[error("invalid symbol info type")] + SymbolInfoTypeInvalid, + + #[error("invalid permissions info type")] + PermInfoTypeInvalid, + + #[error("invalid patch type")] + PatchTypeInvalid, + + #[error("invalid data page")] + DataPageInvalid, } impl From for VsmError { @@ -217,7 +232,12 @@ impl From for Errno { | VsmError::SymbolNameInvalidUtf8 | VsmError::SymbolNameNoTerminator | VsmError::CertificateDerLengthInvalid { .. } - | VsmError::CertificateParseFailed => Errno::EINVAL, + | VsmError::CertificateParseFailed + | VsmError::ApiAttrInvalid + | VsmError::SymbolInfoTypeInvalid + | VsmError::PermInfoTypeInvalid + | VsmError::DataPageInvalid + | VsmError::PatchTypeInvalid => Errno::EINVAL, // Signature verification failures delegate to VerificationError's Errno mapping VsmError::SignatureVerificationFailed(e) => Errno::from(e), diff --git a/litebox_platform_lvbs/src/mshv/heki.rs b/litebox_platform_lvbs/src/mshv/heki.rs index 9b6d3f7e7..8ff081e8f 100644 --- a/litebox_platform_lvbs/src/mshv/heki.rs +++ b/litebox_platform_lvbs/src/mshv/heki.rs @@ -7,12 +7,13 @@ use crate::{ }; use core::mem; use litebox::utils::TruncateExt; +use modular_bitfield::Specifier; use num_enum::TryFromPrimitive; use x86_64::{ - PhysAddr, VirtAddr, + PhysAddr, structures::paging::{PageSize, Size4KiB}, }; -use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq)] @@ -43,8 +44,9 @@ pub(crate) fn mem_attr_to_hv_page_prot_flags(attr: MemAttr) -> HvPageProtFlags { flags } -#[derive(Default, Debug, TryFromPrimitive, PartialEq)] -#[repr(u64)] +#[derive(Default, Debug, TryFromPrimitive, PartialEq, Specifier)] +#[bits = 16] +#[repr(u16)] pub enum HekiKdataType { SystemCerts = 0, RevocationCerts = 1, @@ -53,22 +55,36 @@ pub enum HekiKdataType { KernelData = 4, PatchInfo = 5, KexecTrampoline = 6, + SymbolInfo = 7, + ModuleInfo = 8, + PermInfo = 9, + KexecInfo = 10, + DataPage = 0xff, #[default] - Unknown = 0xffff_ffff_ffff_ffff, + Unknown = 0xffff, +} + +#[derive(Debug, TryFromPrimitive, PartialEq)] +#[repr(u16)] +pub enum HekiSymbolInfoType { + SymbolTable = 0, + GplSymbolTable = 1, + SymbolStringTable = 2, + Unknown = 0xffff, } #[derive(Default, Debug, TryFromPrimitive, PartialEq)] -#[repr(u64)] +#[repr(u16)] pub enum HekiKexecType { KexecImage = 0, KexecKernelBlob = 1, KexecPages = 2, #[default] - Unknown = 0xffff_ffff_ffff_ffff, + Unknown = 0xffff, } #[derive(Clone, Copy, Default, Debug, TryFromPrimitive, PartialEq)] -#[repr(u64)] +#[repr(u16)] pub enum ModMemType { Text = 0, Data = 1, @@ -80,7 +96,7 @@ pub enum ModMemType { ElfBuffer = 7, Patch = 8, #[default] - Unknown = 0xffff_ffff_ffff_ffff, + Unknown = 0xffff, } pub(crate) fn mod_mem_type_to_mem_attr(mod_mem_type: ModMemType) -> MemAttr { @@ -104,149 +120,6 @@ pub(crate) fn mod_mem_type_to_mem_attr(mod_mem_type: ModMemType) -> MemAttr { mem_attr } -/// `HekiRange` is a generic container for various types of memory ranges. -/// It has an `attributes` field which can be interpreted differently based on the context like -/// `MemAttr`, `KdataType`, `ModMemType`, or `KexecType`. -#[derive(Default, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] -#[repr(C, packed)] -pub struct HekiRange { - pub va: u64, - pub pa: u64, - pub epa: u64, - pub attributes: u64, -} - -impl HekiRange { - #[inline] - pub fn is_aligned(&self, align: U) -> bool - where - U: Into + Copy, - { - let va = self.va; - let pa = self.pa; - let epa = self.epa; - - VirtAddr::new(va).is_aligned(align) - && PhysAddr::new(pa).is_aligned(align) - && PhysAddr::new(epa).is_aligned(align) - } - - #[inline] - pub fn mem_attr(&self) -> Option { - let attr = self.attributes; - MemAttr::from_bits(attr) - } - - #[inline] - pub fn mod_mem_type(&self) -> ModMemType { - let attr = self.attributes; - ModMemType::try_from(attr).unwrap_or(ModMemType::Unknown) - } - - #[inline] - pub fn heki_kdata_type(&self) -> HekiKdataType { - let attr = self.attributes; - HekiKdataType::try_from(attr).unwrap_or(HekiKdataType::Unknown) - } - - #[inline] - pub fn heki_kexec_type(&self) -> HekiKexecType { - let attr = self.attributes; - HekiKexecType::try_from(attr).unwrap_or(HekiKexecType::Unknown) - } - - pub fn is_valid(&self) -> bool { - let va = self.va; - let pa = self.pa; - let epa = self.epa; - let Ok(pa) = PhysAddr::try_new(pa) else { - return false; - }; - let Ok(epa) = PhysAddr::try_new(epa) else { - return false; - }; - !(VirtAddr::try_new(va).is_err() - || epa < pa - || (self.mem_attr().is_none() - && self.heki_kdata_type() == HekiKdataType::Unknown - && self.heki_kexec_type() == HekiKexecType::Unknown - && self.mod_mem_type() == ModMemType::Unknown)) - } -} - -impl core::fmt::Debug for HekiRange { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let va = self.va; - let pa = self.pa; - let epa = self.epa; - let attr = self.attributes; - f.debug_struct("HekiRange") - .field("va", &format_args!("{va:#x}")) - .field("pa", &format_args!("{pa:#x}")) - .field("epa", &format_args!("{epa:#x}")) - .field("attr", &format_args!("{attr:#x}")) - .field("type", &format_args!("{:?}", self.heki_kdata_type())) - .field("size", &format_args!("{:?}", self.epa - self.pa)) - .finish() - } -} - -#[expect(clippy::cast_possible_truncation)] -pub const HEKI_MAX_RANGES: usize = - ((PAGE_SIZE as u32 - u64::BITS * 3 / 8) / core::mem::size_of::() as u32) as usize; - -#[derive(Clone, Copy, FromBytes, Immutable, KnownLayout)] -#[repr(align(4096))] -#[repr(C)] -pub struct HekiPage { - /// Pointer to next page (stored as u64 since we don't dereference it) - pub next: u64, - pub next_pa: u64, - pub nranges: u64, - pub ranges: [HekiRange; HEKI_MAX_RANGES], - pad: u64, -} - -impl HekiPage { - pub fn new() -> Self { - // Safety: all fields are valid when zeroed (u64 zeros, array of zeroed HekiRange) - Self::new_zeroed() - } - - pub fn is_valid(&self) -> bool { - if PhysAddr::try_new(self.next_pa).is_err() { - return false; - } - let Some(nranges) = usize::try_from(self.nranges) - .ok() - .filter(|&n| n <= HEKI_MAX_RANGES) - else { - return false; - }; - for heki_range in &self.ranges[..nranges] { - if !heki_range.is_valid() { - return false; - } - } - true - } -} - -impl Default for HekiPage { - fn default() -> Self { - Self::new_zeroed() - } -} - -impl<'a> IntoIterator for &'a HekiPage { - type Item = &'a HekiRange; - type IntoIter = core::slice::Iter<'a, HekiRange>; - - fn into_iter(self) -> Self::IntoIter { - self.ranges[..usize::try_from(self.nranges).unwrap_or(0)].iter() - } -} - #[derive(Default, Clone, Copy, Debug, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct HekiPatch { @@ -294,12 +167,12 @@ impl HekiPatch { } } -#[derive(Default, Clone, Copy, Debug, PartialEq)] -#[repr(u32)] +#[derive(Default, Clone, Copy, Debug, PartialEq, TryFromPrimitive)] +#[repr(u16)] pub enum HekiPatchType { JumpLabel = 0, #[default] - Unknown = 0xffff_ffff, + Unknown = 0xffff, } #[derive(Clone, Copy, Debug, FromBytes, Immutable, KnownLayout)] @@ -329,6 +202,7 @@ impl HekiPatchInfo { } } +#[derive(FromBytes, KnownLayout, Immutable)] #[repr(C)] #[allow(clippy::struct_field_names)] // TODO: Account for kernel config changing the size and meaning of the field members @@ -361,37 +235,3 @@ impl HekiKernelSymbol { } } } - -#[repr(C)] -#[allow(clippy::struct_field_names)] -pub struct HekiKernelInfo { - pub ksymtab_start: *const HekiKernelSymbol, - pub ksymtab_end: *const HekiKernelSymbol, - pub ksymtab_gpl_start: *const HekiKernelSymbol, - pub ksymtab_gpl_end: *const HekiKernelSymbol, - // Skip unused arch info -} - -impl HekiKernelInfo { - const KINFO_LEN: usize = mem::size_of::(); - - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < Self::KINFO_LEN { - return Err(VsmError::BufferTooSmall("HekiKernelInfo")); - } - - #[allow(clippy::cast_ptr_alignment)] - let kinfo_ptr = bytes.as_ptr().cast::(); - assert!(kinfo_ptr.is_aligned(), "kinfo_ptr is not aligned"); - - // SAFETY: Casting from vtl0 buffer that contained the struct - unsafe { - Ok(HekiKernelInfo { - ksymtab_start: (*kinfo_ptr).ksymtab_start, - ksymtab_end: (*kinfo_ptr).ksymtab_end, - ksymtab_gpl_start: (*kinfo_ptr).ksymtab_gpl_start, - ksymtab_gpl_end: (*kinfo_ptr).ksymtab_gpl_end, - }) - } - } -} diff --git a/litebox_platform_lvbs/src/mshv/heki_data.rs b/litebox_platform_lvbs/src/mshv/heki_data.rs new file mode 100644 index 000000000..c4e571875 --- /dev/null +++ b/litebox_platform_lvbs/src/mshv/heki_data.rs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! VTL0 data format and parsing +#![allow(clippy::trivially_copy_pass_by_ref)] + +use modular_bitfield::{ + bitfield, + prelude::{B16, B32}, +}; +use zerocopy::{FromBytes, Immutable, KnownLayout}; + +use crate::mshv::{ + error::VsmError, + heki::{HekiKdataType, HekiKexecType, HekiPatchType, HekiSymbolInfoType, ModMemType}, + vtl1_mem_layout::PAGE_SIZE, +}; + +#[bitfield(bits = 64)] +#[derive(Debug, Clone, Copy, Default)] +#[repr(u64)] +pub struct HekiDataApiAttr { + #[skip(setters)] + pub data_type: HekiKdataType, + #[skip(setters)] + pub size: B16, // Size of data page or buffer will be < 2^16-1 + pub flags: B32, // Custom flags per api +} + +/// `HekiDataRange` describes a range of VTL0 memory, its associated +/// context-specific type and memory attributes. +#[repr(C)] +#[derive(FromBytes, Immutable, KnownLayout)] +pub struct HekiDataRange { + pub va: u64, + pub pa: u64, + pub size: u32, + data_type: u16, + pub mem_attr: u16, +} + +impl core::fmt::Debug for HekiDataRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let data_type = self.data_type; + let va = self.va; + let pa = self.pa; + let size = self.size; + let attr = self.mem_attr; + f.debug_struct("HekiDataRange") + .field("va", &format_args!("{va:#x}")) + .field("pa", &format_args!("{pa:#x}")) + .field("epa", &format_args!("{:#x}", pa + u64::from(size))) + .field("attr", &format_args!("{attr:#x}")) + .field("type", &format_args!("{data_type}")) + .field("size", &format_args!("{size}")) + .finish() + } +} + +impl HekiDataRange { + #[inline] + pub fn heki_symbol_info_type(&self) -> HekiSymbolInfoType { + HekiSymbolInfoType::try_from(self.data_type).unwrap_or(HekiSymbolInfoType::Unknown) + } + + #[inline] + pub fn heki_kdata_type(&self) -> HekiKdataType { + HekiKdataType::try_from(self.data_type).unwrap_or(HekiKdataType::Unknown) + } + + #[inline] + pub fn heki_mod_mem_type(&self) -> ModMemType { + ModMemType::try_from(self.data_type).unwrap_or(ModMemType::Unknown) + } + + #[inline] + pub fn heki_kexec_type(&self) -> HekiKexecType { + HekiKexecType::try_from(self.data_type).unwrap_or(HekiKexecType::Unknown) + } + + #[inline] + pub fn heki_patch_type(&self) -> HekiPatchType { + HekiPatchType::try_from(self.data_type).unwrap_or(HekiPatchType::Unknown) + } +} + +#[repr(C)] +#[derive(FromBytes, Immutable, KnownLayout)] +pub struct HekiDataHdr { + data_type: u16, + range_count: u16, + rsvd: u32, + next: u64, + next_pa: u64, +} + +#[repr(C)] +#[derive(FromBytes, Immutable, KnownLayout)] +pub struct HekiDataPage { + hdr: HekiDataHdr, + range: [HekiDataRange; Self::MAX_RANGE_COUNT], +} + +impl core::fmt::Debug for HekiDataPage { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let data_type = + HekiKdataType::try_from(self.hdr.data_type).unwrap_or(HekiKdataType::Unknown); + let count = self.hdr.range_count; + let next = self.hdr.next_pa; + f.debug_struct("HekiDataPage") + .field("type", &format_args!("{data_type:#?}")) + .field("ranges", &format_args!("{count}")) + .field("next", &format_args!("{next:#x}")) + .finish_non_exhaustive() + } +} + +impl HekiDataPage { + const SIZE: usize = PAGE_SIZE; + const MAX_RANGE_COUNT: usize = + (Self::SIZE - size_of::()) / size_of::(); + + pub fn try_from_bytes(bytes: &[u8]) -> Result<&Self, VsmError> { + let (data_page, _) = + HekiDataPage::ref_from_prefix(bytes).map_err(|_| VsmError::DataPageInvalid)?; + Ok(data_page) + } + + pub fn kdata_type(&self) -> HekiKdataType { + HekiKdataType::try_from(self.hdr.data_type).unwrap_or(HekiKdataType::Unknown) + } + + pub fn next_page_params(&self) -> Option<(u64, u64, usize)> { + if self.hdr.next_pa != 0 { + Some((self.hdr.next, self.hdr.next_pa, Self::SIZE)) + } else { + None + } + } +} + +impl<'a> IntoIterator for &'a HekiDataPage { + type Item = (u16, &'a [HekiDataRange]); + type IntoIter = HekiDataPageIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + HekiDataPageIter { + range_index: 0, + range: &self.range[0..self.hdr.range_count as usize], + } + } +} + +pub struct HekiDataPageIter<'a> { + range_index: usize, + range: &'a [HekiDataRange], +} + +impl<'a> Iterator for HekiDataPageIter<'a> { + type Item = (u16, &'a [HekiDataRange]); + + fn next(&mut self) -> Option { + if self.range_index == self.range.len() { + None + } else { + let start_index = self.range_index; + let range_type = self.range[start_index].data_type; + let mut end_index: usize = start_index + 1; + + while end_index < self.range.len() { + if self.range[end_index].data_type == range_type { + end_index += 1; + } else { + break; + } + } + self.range_index = end_index; + Some((range_type, &self.range[start_index..end_index])) + } + } +} diff --git a/litebox_platform_lvbs/src/mshv/mod.rs b/litebox_platform_lvbs/src/mshv/mod.rs index 0acd5e29e..b75ac7844 100644 --- a/litebox_platform_lvbs/src/mshv/mod.rs +++ b/litebox_platform_lvbs/src/mshv/mod.rs @@ -5,6 +5,7 @@ pub mod error; pub(crate) mod heki; +pub(crate) mod heki_data; pub mod hvcall; mod hvcall_mm; mod hvcall_vp; diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index c338e8b1b..e466a804c 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -3,6 +3,8 @@ //! VSM functions +use crate::mshv::heki::{HekiPatchType, HekiSymbolInfoType}; +use crate::mshv::heki_data::{HekiDataApiAttr, HekiDataPage, HekiDataRange}; #[cfg(debug_assertions)] use crate::mshv::mem_integrity::parse_modinfo; use crate::mshv::ringbuffer::set_ringbuffer; @@ -25,9 +27,8 @@ use crate::{ HvRegisterVsmVpSecureVtlConfig, VsmFunction, X86Cr0Flags, X86Cr4Flags, error::VsmError, heki::{ - HekiKdataType, HekiKernelInfo, HekiKernelSymbol, HekiKexecType, HekiPage, HekiPatch, - HekiPatchInfo, HekiRange, MemAttr, ModMemType, mem_attr_to_hv_page_prot_flags, - mod_mem_type_to_mem_attr, + HekiKdataType, HekiKernelSymbol, HekiKexecType, HekiPatch, HekiPatchInfo, MemAttr, + ModMemType, mem_attr_to_hv_page_prot_flags, mod_mem_type_to_mem_attr, }, hvcall::HypervCallError, hvcall_mm::hv_modify_vtl_protection_mask, @@ -40,7 +41,7 @@ use crate::{ vtl1_mem_layout::{PAGE_SHIFT, PAGE_SIZE}, }, }; -use alloc::{boxed::Box, ffi::CString, string::String, vec::Vec}; +use alloc::{boxed::Box, ffi::CString, string::String, vec, vec::Vec}; use core::{ mem, ops::Range, @@ -253,14 +254,23 @@ pub fn mshv_vsm_end_of_boot() -> i64 { 0 } -/// VSM function for protecting certain memory ranges (e.g., kernel text, data, heap). -/// `pa` and `nranges` specify a memory area containing the information about the memory ranges to protect. -pub fn mshv_vsm_protect_memory(pa: u64, nranges: u64) -> Result { +/// Protects ranges of VTL0 memory such as kernel text, data and heap with +/// appropriate permissions e.g. Read-Execute for text, Read-Write for heap +/// and Read-Only for unchanging data. +/// +/// Must be called before end of boot. +/// +/// `pa` (`va`) is the physical (virtual) address of a buffer of VTL0 memory +/// ranges to be protected. +/// `attr` encodes the size and format of ranges in the buffer. +pub fn mshv_vsm_protect_memory(pa: u64, va: u64, attr: u64) -> Result { + if attr == 0 { + return Err(VsmError::ApiAttrInvalid); + } if PhysAddr::try_new(pa) .ok() .filter(|p| p.is_aligned(Size4KiB::SIZE)) .is_none() - || nranges == 0 { return Err(VsmError::InvalidInputAddress); } @@ -271,40 +281,36 @@ pub fn mshv_vsm_protect_memory(pa: u64, nranges: u64) -> Result { )); } - let heki_pages = copy_heki_pages_from_vtl0(pa, nranges).ok_or(VsmError::HekiPagesCopyFailed)?; + let attr = HekiDataApiAttr::from_bytes(attr.to_le_bytes()); + let size = attr.size_or_err().map_err(|_| VsmError::ApiAttrInvalid)? as usize; - for heki_page in heki_pages { - for heki_range in &heki_page { - let pa = heki_range.pa; - let epa = heki_range.epa; - let mem_attr = heki_range - .mem_attr() - .ok_or(VsmError::MemoryAttributeInvalid)?; + let mem = MemoryContainer::from_vtl0_addr(va, pa, size)?; + let data_page = HekiDataPage::try_from_bytes(&mem)?; - if !heki_range.is_aligned(Size4KiB::SIZE) { - return Err(VsmError::AddressNotPageAligned); - } + vsm_for_each_data_page(data_page, |range| { + if range.heki_kdata_type() != HekiKdataType::PermInfo { + return Err(VsmError::KernelDataTypeInvalid); + } + let Some(mem_attr) = MemAttr::from_bits(u64::from(range.mem_attr)) else { + return Err(VsmError::PermInfoTypeInvalid); + }; + debug_serial_println!("VSM: Protect memory: {range:?} {:#x}", range.size); - #[cfg(debug_assertions)] - let va = heki_range.va; - debug_serial_println!( - "VSM: Protect memory: va {:#x} pa {:#x} epa {:#x} {:?} (size: {})", - va, - pa, - epa, - mem_attr, - epa - pa - ); + protect_physical_memory_range( + PhysFrame::range( + PhysFrame::containing_address(PhysAddr::new(range.pa)), + PhysFrame::containing_address(PhysAddr::new( + range + .pa + .checked_add(u64::from(range.size)) + .ok_or(VsmError::InvalidPhysicalAddress)?, + )), + ), + mem_attr, + )?; + Ok(0) + })?; - protect_physical_memory_range( - PhysFrame::range( - PhysFrame::containing_address(PhysAddr::new(pa)), - PhysFrame::containing_address(PhysAddr::new(epa)), - ), - mem_attr, - )?; - } - } Ok(0) } @@ -331,153 +337,73 @@ fn parse_certs(mut buf: &[u8]) -> Result, VsmError> { Ok(certs) } -/// VSM function for loading kernel data (e.g., certificates, blocklist, kernel symbols) into VTL1. -/// `pa` and `nranges` specify memory areas containing the information about the memory ranges to load. -pub fn mshv_vsm_load_kdata(pa: u64, nranges: u64) -> Result { +/// Copy VTL0 kernel data e.g. certificates, blocklists, symbols and process +/// them. +/// +/// Some data can only be be copied once e.g. system certificates +/// +/// `pa` (`va`) is the physical (virtual) address of a buffer of VTL0 memory +/// with data to be copied. +/// `attr` encodes the size and format of the data in the buffer. +pub fn mshv_vsm_load_kdata(pa: u64, va: u64, attr: u64) -> Result { + if attr == 0 { + return Err(VsmError::ApiAttrInvalid); + } if PhysAddr::try_new(pa) .ok() .filter(|p| p.is_aligned(Size4KiB::SIZE)) .is_none() - || nranges == 0 { return Err(VsmError::InvalidInputAddress); } - if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { - return Err(VsmError::OperationAfterEndOfBoot("loading kernel data")); - } + let attr = HekiDataApiAttr::from_bytes(attr.to_le_bytes()); + let data_type = attr + .data_type_or_err() + .map_err(|_| VsmError::KernelDataTypeInvalid)?; + let size = attr.size_or_err().map_err(|_| VsmError::ApiAttrInvalid)? as usize; - let vtl0_info = &crate::platform_low().vtl0_kernel_info; + let mem = MemoryContainer::from_vtl0_addr(va, pa, size)?; - let mut system_certs_mem = MemoryContainer::new(); - let mut kexec_trampoline_metadata = KexecMemoryMetadata::new(); - let mut patch_info_mem = MemoryContainer::new(); - let mut kinfo_mem = MemoryContainer::new(); - let mut kdata_mem = MemoryContainer::new(); - - let heki_pages = copy_heki_pages_from_vtl0(pa, nranges).ok_or(VsmError::HekiPagesCopyFailed)?; - - for heki_page in &heki_pages { - for heki_range in heki_page { - debug_serial_println!("VSM: Load kernel data {heki_range:?}"); - match heki_range.heki_kdata_type() { - HekiKdataType::SystemCerts => system_certs_mem - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - HekiKdataType::KexecTrampoline => { - kexec_trampoline_metadata.insert_heki_range(heki_range); - } - HekiKdataType::PatchInfo => patch_info_mem - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - HekiKdataType::KernelInfo => kinfo_mem - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - HekiKdataType::KernelData => kdata_mem - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - HekiKdataType::Unknown => { - return Err(VsmError::KernelDataTypeInvalid); - } - _ => { - debug_serial_println!("VSM: Unsupported kernel data not loaded {heki_range:?}"); - } - } - } - } - - system_certs_mem - .write_bytes_from_heki_range() - .map_err(|_| VsmError::Vtl0CopyFailed)?; - patch_info_mem - .write_bytes_from_heki_range() - .map_err(|_| VsmError::Vtl0CopyFailed)?; - kinfo_mem - .write_bytes_from_heki_range() - .map_err(|_| VsmError::Vtl0CopyFailed)?; - kdata_mem - .write_bytes_from_heki_range() - .map_err(|_| VsmError::Vtl0CopyFailed)?; - - if system_certs_mem.is_empty() { - return Err(VsmError::SystemCertificatesNotFound); + match data_type { + HekiKdataType::DataPage => vsm_load_data_page(&mem), + HekiKdataType::Unknown => Err(VsmError::KernelDataTypeInvalid), + _ => vsm_load_data_buf(data_type, &mem), } - - let cert_buf = &system_certs_mem[..]; - let certs = parse_certs(cert_buf)?; - - if certs.is_empty() { - return Err(VsmError::SystemCertificatesInvalid); - } - - // The system certificate is loaded into VTL1 and locked down before `end_of_boot` is signaled. - // Its integrity depends on UEFI Secure Boot which ensures only trusted software is loaded during - // the boot process. - vtl0_info.set_system_certificates(certs.clone()); - debug_serial_println!("VSM: Loaded {} system certificate(s)", certs.len()); - - for kexec_trampoline_range in &kexec_trampoline_metadata { - protect_physical_memory_range( - kexec_trampoline_range.phys_frame_range, - MemAttr::MEM_ATTR_READ, - )?; - } - - // pre-computed patch data for the kernel text - if !patch_info_mem.is_empty() { - let patch_info_buf = &patch_info_mem[..]; - vtl0_info - .precomputed_patches - .insert_patch_data_from_bytes(patch_info_buf, None) - .map_err(|_| VsmError::Vtl0CopyFailed)?; - } - - if kinfo_mem.is_empty() || kdata_mem.is_empty() { - return Err(VsmError::KernelSymbolTableNotFound); - } - - let kinfo_buf = &kinfo_mem[..]; - let kdata_buf = &kdata_mem[..]; - let kinfo = HekiKernelInfo::from_bytes(kinfo_buf)?; - - vtl0_info.gpl_symbols.build_from_container( - VirtAddr::from_ptr(kinfo.ksymtab_gpl_start), - VirtAddr::from_ptr(kinfo.ksymtab_gpl_end), - &kdata_mem, - kdata_buf, - )?; - - vtl0_info.symbols.build_from_container( - VirtAddr::from_ptr(kinfo.ksymtab_start), - VirtAddr::from_ptr(kinfo.ksymtab_end), - &kdata_mem, - kdata_buf, - )?; - - Ok(0) // TODO: create blocklist keys // TODO: save blocklist hashes } -/// VSM function for validating a guest kernel module and applying specified protection to its memory ranges after validation. -/// `pa` and `nranges` specify a memory area containing the information about the kernel module to validate or protect. -/// `flags` controls the validation process (unused for now). -/// This function returns a unique `token` to VTL0, which is used to identify the module in subsequent calls. -pub fn mshv_vsm_validate_guest_module(pa: u64, nranges: u64, _flags: u64) -> Result { +/// Validate guest kernel module protect its memory ranges. +/// +/// `pa` (`va`) is the physical (virtual) address of data pages containing +/// the module's aggregated data (ELF, text, data sections e.t.c) +/// `attr` encodes the size and format and optional flags (unused) of the +/// data in the buffer. +/// +/// # Returns +/// Unique token identifier for module. +pub fn mshv_vsm_validate_guest_module(pa: u64, va: u64, attr: u64) -> Result { + if attr == 0 { + return Err(VsmError::ApiAttrInvalid); + } if PhysAddr::try_new(pa) .ok() .filter(|p| p.is_aligned(Size4KiB::SIZE)) .is_none() - || nranges == 0 { return Err(VsmError::InvalidInputAddress); } + debug_serial_println!("VSM: Validate kernel module: pa {pa:#x} attr:{attr:#x}"); - debug_serial_println!( - "VSM: Validate kernel module: pa {:#x} nranges {}", - pa, - nranges, - ); + let attr = HekiDataApiAttr::from_bytes(attr.to_le_bytes()); + let data_type = attr + .data_type_or_err() + .map_err(|_| VsmError::ModuleMemoryTypeInvalid)?; + let size = attr.size_or_err().map_err(|_| VsmError::ApiAttrInvalid)? as usize; + if data_type != HekiKdataType::DataPage { + return Err(VsmError::DataPageInvalid); + } let certs = crate::platform_low() .vtl0_kernel_info @@ -494,33 +420,18 @@ pub fn mshv_vsm_validate_guest_module(pa: u64, nranges: u64, _flags: u64) -> Res // patch info for the kernel module let mut patch_info_for_module = MemoryContainer::new(); - let heki_pages = copy_heki_pages_from_vtl0(pa, nranges).ok_or(VsmError::HekiPagesCopyFailed)?; + let mem = MemoryContainer::from_vtl0_addr(va, pa, size)?; + let data_page = HekiDataPage::try_from_bytes(&mem)?; - for heki_page in &heki_pages { - for heki_range in heki_page { - match heki_range.mod_mem_type() { - ModMemType::Unknown => { - return Err(VsmError::ModuleMemoryTypeInvalid); - } - ModMemType::ElfBuffer => module_as_elf - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - ModMemType::Patch => patch_info_for_module - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?, - _ => { - // if input memory range's type is neither `Unknown` nor `ElfBuffer`, its addresses must be page-aligned - if !heki_range.is_aligned(Size4KiB::SIZE) { - return Err(VsmError::AddressNotPageAligned); - } - module_memory_metadata.insert_heki_range(heki_range); - module_in_memory - .extend_range(heki_range.mod_mem_type(), heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?; - } - } + vsm_for_each_data_page(data_page, |range| match range.heki_mod_mem_type() { + ModMemType::ElfBuffer => module_as_elf.extend_data_range(range), + ModMemType::Patch => patch_info_for_module.extend_data_range(range), + ModMemType::Unknown => Err(VsmError::ModuleMemoryTypeInvalid), + mod_mem_type => { + module_memory_metadata.insert_data_range(mod_mem_type, range)?; + module_in_memory.extend_data_range(mod_mem_type, range) } - } + })?; module_as_elf .write_bytes_from_heki_range() @@ -680,20 +591,21 @@ pub fn mshv_vsm_copy_secondary_key(_pa: u64, _nranges: u64) -> Result Result { - debug_serial_println!( - "VSM: Validate kexec pa {:#x} nranges {} crash {}", - pa, - nranges, - crash - ); +pub fn mshv_vsm_kexec_validate(pa: u64, va: u64, attr: u64) -> Result { + if attr == 0 { + return Err(VsmError::ApiAttrInvalid); + } + let attr = HekiDataApiAttr::from_bytes(attr.to_le_bytes()); + let size = attr.size_or_err().map_err(|_| VsmError::ApiAttrInvalid)? as usize; + + debug_serial_println!("VSM: Validate kexec pa {pa:#x} va {va:#x} attr {attr:?}"); let certs = crate::platform_low() .vtl0_kernel_info .get_system_certificates() .ok_or(VsmError::SystemCertificatesNotLoaded)?; - let is_crash = crash != 0; + let is_crash = attr.flags() != 0; let kexec_metadata_ref = if is_crash { &crate::platform_low().vtl0_kernel_info.crash_kexec_metadata } else { @@ -718,32 +630,21 @@ pub fn mshv_vsm_kexec_validate(pa: u64, nranges: u64, crash: u64) -> Result { - kexec_memory_metadata.insert_heki_range(heki_range); - kexec_image - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?; - } - HekiKexecType::KexecKernelBlob => - // we do not protect kexec kernel blob memory - { - kexec_kernel_blob - .extend_range(heki_range) - .map_err(|_| VsmError::InvalidInputAddress)?; - } - - HekiKexecType::KexecPages => kexec_memory_metadata.insert_heki_range(heki_range), - HekiKexecType::Unknown => { - return Err(VsmError::KexecTypeInvalid); - } + vsm_for_each_data_page(data_page, |range| { + Ok(match range.heki_kexec_type() { + HekiKexecType::KexecImage => { + kexec_memory_metadata.insert_data_range(range)?; + kexec_image.extend_data_range(range)? } - } - } + // we do not protect kexec kernel blob memory + HekiKexecType::KexecKernelBlob => kexec_kernel_blob.extend_data_range(range)?, + HekiKexecType::KexecPages => kexec_memory_metadata.insert_data_range(range)?, + HekiKexecType::Unknown => return Err(VsmError::KexecTypeInvalid), + }) + })?; kexec_image .write_bytes_from_heki_range() @@ -915,6 +816,180 @@ fn mshv_vsm_allocate_ringbuffer_memory(phys_addr: u64, size: usize) -> Result Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot( + "setting system certificate", + )); + } + + let certs = parse_certs(mem)?; + if certs.is_empty() { + return Err(VsmError::SystemCertificatesInvalid); + } + // The system certificate is loaded into VTL1 and locked down before `end_of_boot` is signaled. + // Its integrity depends on UEFI Secure Boot which ensures only trusted software is lo aded during + // the boot process. + crate::platform_low() + .vtl0_kernel_info + .set_system_certificates(certs.clone()); + Ok(0) +} + +fn vsm_protect_kexec_tramp(data_page: &HekiDataPage) -> Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot( + "protecting kexec trampoline", + )); + } + + let mut kexec_trampoline_metadata = KexecMemoryMetadata::new(); + + vsm_for_each_data_page(data_page, |range| { + kexec_trampoline_metadata.insert_data_range(range) + })?; + + for kexec_trampoline_range in &kexec_trampoline_metadata { + protect_physical_memory_range( + kexec_trampoline_range.phys_frame_range, + MemAttr::MEM_ATTR_READ, + )?; + } + + Ok(0) +} + +fn vsm_load_symbol_table(data_page: &HekiDataPage) -> Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot("loading kernel symbols")); + } + + let mut sym_mem = MemoryContainer::new(); + let mut sym_gpl_mem = MemoryContainer::new(); + let mut sym_string_mem = MemoryContainer::new(); + + vsm_for_each_data_page(data_page, |range| match range.heki_symbol_info_type() { + HekiSymbolInfoType::SymbolTable => sym_mem.extend_data_range(range), + HekiSymbolInfoType::GplSymbolTable => sym_gpl_mem.extend_data_range(range), + HekiSymbolInfoType::SymbolStringTable => sym_string_mem.extend_data_range(range), + HekiSymbolInfoType::Unknown => Err(VsmError::SymbolInfoTypeInvalid), + })?; + + sym_mem + .write_bytes_from_heki_range() + .map_err(|_| VsmError::Vtl0CopyFailed)?; + sym_gpl_mem + .write_bytes_from_heki_range() + .map_err(|_| VsmError::Vtl0CopyFailed)?; + sym_string_mem + .write_bytes_from_heki_range() + .map_err(|_| VsmError::Vtl0CopyFailed)?; + + let vtl0_info = &crate::platform_low().vtl0_kernel_info; + vtl0_info + .symbols + .build_from_container(&sym_mem, &sym_string_mem)?; + + vtl0_info + .gpl_symbols + .build_from_container(&sym_gpl_mem, &sym_string_mem)?; + + Ok(0) +} + +fn vsm_load_patches(data_page: &HekiDataPage) -> Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot("loading kernel patches")); + } + + let mut jump_label_mem = MemoryContainer::new(); + + vsm_for_each_data_page(data_page, |range| match range.heki_patch_type() { + HekiPatchType::JumpLabel => jump_label_mem.extend_data_range(range), + HekiPatchType::Unknown => Err(VsmError::PatchTypeInvalid), + })?; + + jump_label_mem + .write_bytes_from_heki_range() + .map_err(|_| VsmError::Vtl0CopyFailed)?; + + if jump_label_mem.is_empty() { + return Ok(0); + } + + let vtl0_info = &crate::platform_low().vtl0_kernel_info; + let patch_info_buf = &jump_label_mem[..]; + vtl0_info + .precomputed_patches + .insert_patch_data_from_bytes(patch_info_buf, None) + .map_err(|_| VsmError::Vtl0CopyFailed)?; + + Ok(0) +} + +/// Iterate over data pages, applying f() to each range +fn vsm_for_each_data_page(data_page: &HekiDataPage, mut f: F) -> Result +where + F: FnMut(&HekiDataRange) -> Result, +{ + let mut first_data_page = true; + let mut curr_data_page; + let mut curr_mem; + let mut next_page_params = None; + + loop { + if first_data_page { + curr_data_page = data_page; + next_page_params = data_page.next_page_params(); + first_data_page = false; + } else if let Some((va, pa, size)) = next_page_params { + curr_mem = MemoryContainer::from_vtl0_addr(va, pa, size)?; + curr_data_page = HekiDataPage::try_from_bytes(&curr_mem)?; + next_page_params = curr_data_page.next_page_params(); + } else { + break Ok(0); + } + + for (_, ranges) in curr_data_page { + for range in ranges { + f(range)?; + } + } + } +} + +/// Process VTL0 kernel data in data page format. Large (> PAGE_SIZE) and +/// and aggregate data are described by data pages which in turn point to +/// the actual data. +/// +/// `mem` is a copy of the first data page +fn vsm_load_data_page(mem: &MemoryContainer) -> Result { + let data_page = HekiDataPage::try_from_bytes(mem)?; + let data_type = data_page.kdata_type(); + + debug_serial_println!("VSM: Load {data_type:?} data page"); + match data_type { + HekiKdataType::KexecTrampoline => vsm_protect_kexec_tramp(data_page), + HekiKdataType::SymbolInfo => vsm_load_symbol_table(data_page), + HekiKdataType::PatchInfo => vsm_load_patches(data_page), + _ => Ok(0), // TODO: Some "small" data types may get big enough to use data page + } +} + +/// Process VTL0 kernel data in raw buffer format. +/// +/// `mem` is a copy of the buffer +fn vsm_load_data_buf(data_type: HekiKdataType, mem: &MemoryContainer) -> Result { + match data_type { + HekiKdataType::SystemCerts => vsm_set_system_certs(mem), + HekiKdataType::RevocationCerts | HekiKdataType::BlocklistHashes => { + debug_serial_println!("Handler for {data_type:?} unimplemented"); + Ok(0) + } + _ => Ok(0), + } +} + /// VSM function dispatcher pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { let result: Result = match func_id { @@ -922,8 +997,8 @@ pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { VsmFunction::BootAPs => mshv_vsm_boot_aps(params[0], params[1]), VsmFunction::LockRegs => mshv_vsm_lock_regs(), VsmFunction::SignalEndOfBoot => Ok(mshv_vsm_end_of_boot()), - VsmFunction::ProtectMemory => mshv_vsm_protect_memory(params[0], params[1]), - VsmFunction::LoadKData => mshv_vsm_load_kdata(params[0], params[1]), + VsmFunction::ProtectMemory => mshv_vsm_protect_memory(params[0], params[1], params[2]), + VsmFunction::LoadKData => mshv_vsm_load_kdata(params[0], params[1], params[2]), VsmFunction::ValidateModule => { mshv_vsm_validate_guest_module(params[0], params[1], params[2]) } @@ -1116,21 +1191,26 @@ impl ModuleMemoryMetadata { } #[inline] - pub(crate) fn insert_heki_range(&mut self, heki_range: &HekiRange) { - let va = heki_range.va; - let pa = heki_range.pa; - let epa = heki_range.epa; - self.insert_memory_range(ModuleMemoryRange::new( - va, - pa, - epa, - heki_range.mod_mem_type(), - )); + pub(crate) fn insert_memory_range(&mut self, mem_range: ModuleMemoryRange) { + self.ranges.push(mem_range); } #[inline] - pub(crate) fn insert_memory_range(&mut self, mem_range: ModuleMemoryRange) { - self.ranges.push(mem_range); + pub(crate) fn insert_data_range( + &mut self, + mem_type: ModMemType, + range: &HekiDataRange, + ) -> Result<(), VsmError> { + self.insert_memory_range(ModuleMemoryRange::new( + range.va, + range.pa, + range + .pa + .checked_add(u64::from(range.size)) + .ok_or(VsmError::InvalidPhysicalAddress)?, + mem_type, + )); + Ok(()) } #[inline] @@ -1279,28 +1359,6 @@ impl<'a> ModuleMemoryMetadataIters<'a> { } } -/// This function copies `HekiPage` structures from VTL0 and returns a vector of them. -/// `pa` and `nranges` specify the physical address range containing one or more than one `HekiPage` structures. -fn copy_heki_pages_from_vtl0(pa: u64, nranges: u64) -> Option> { - let mut next_pa = PhysAddr::new(pa); - let mut heki_pages = Vec::with_capacity(nranges.truncate()); - let mut range: u64 = 0; - - while range < nranges { - let heki_page = - (unsafe { crate::platform_low().copy_from_vtl0_phys::(next_pa) })?; - if !heki_page.is_valid() { - return None; - } - - range += heki_page.nranges; - next_pa = PhysAddr::new(heki_page.next_pa); - heki_pages.push(*heki_page); - } - - Some(heki_pages) -} - /// This function protects a physical memory range. It is a safe wrapper for `hv_modify_vtl_protection_mask`. /// `phys_frame_range` specifies the physical frame range to protect /// `mem_attr` specifies the memory attributes to be applied to the range @@ -1361,18 +1419,17 @@ impl ModuleMemory { Ok(()) } - pub(crate) fn extend_range( + pub(crate) fn extend_data_range( &mut self, mod_mem_type: ModMemType, - heki_range: &HekiRange, - ) -> Result<(), VsmError> { + range: &HekiDataRange, + ) -> Result { match mod_mem_type { - ModMemType::Text => self.text.extend_range(heki_range)?, - ModMemType::InitText => self.init_text.extend_range(heki_range)?, - ModMemType::InitRoData => self.init_rodata.extend_range(heki_range)?, - _ => {} + ModMemType::Text => self.text.extend_data_range(range), + ModMemType::InitText => self.init_text.extend_data_range(range), + ModMemType::InitRoData => self.init_rodata.extend_data_range(range), + _ => Ok(0), } - Ok(()) } } @@ -1426,26 +1483,6 @@ impl MemoryContainer { }) } - pub(crate) fn extend_range(&mut self, heki_range: &HekiRange) -> Result<(), VsmError> { - let addr = VirtAddr::try_new(heki_range.va).map_err(|_| VsmError::InvalidVirtualAddress)?; - let phys_addr = - PhysAddr::try_new(heki_range.pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; - if let Some(last_range) = self.range.last() - && last_range.addr + last_range.len != addr - { - debug_serial_println!("Discontiguous address found {heki_range:?}"); - // NOTE: Intentionally not returning an error here. - // TODO: This should be an error once patch_info is fixed from VTL0 - // It will simplify patch_info and heki_range parsing as well - } - self.range.push(MemoryRange { - addr, - phys_addr, - len: heki_range.epa - heki_range.pa, - }); - Ok(()) - } - /// Write physical memory bytes from VTL0 specified in `HekiRange` at the specified virtual address #[inline] pub(crate) fn write_bytes_from_heki_range(&mut self) -> Result<(), MemoryContainerError> { @@ -1492,6 +1529,46 @@ impl MemoryContainer { } Ok(()) } + + pub(crate) fn from_vtl0_addr(va: u64, pa: u64, size: usize) -> Result { + let phys_start = PhysAddr::try_new(pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; + if !phys_start.is_aligned(Size4KiB::SIZE) { + return Err(VsmError::InvalidPhysicalAddress); + } + let phys_end = + PhysAddr::try_new(pa + size as u64).map_err(|_| VsmError::InvalidPhysicalAddress)?; + let addr = VirtAddr::try_new(va).map_err(|_| VsmError::InvalidVirtualAddress)?; + let mut mem = Self { + range: vec![MemoryRange { + addr, + phys_addr: phys_start, + len: phys_end - phys_start, + }], + buf: Vec::new(), + }; + mem.write_vtl0_phys_bytes(phys_start, phys_end) + .map_err(|_| VsmError::Vtl0CopyFailed)?; + Ok(mem) + } + + pub(crate) fn extend_data_range(&mut self, range: &HekiDataRange) -> Result { + let addr = VirtAddr::try_new(range.va).map_err(|_| VsmError::InvalidVirtualAddress)?; + let phys_start = + PhysAddr::try_new(range.pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; + + if let Some(last_range) = self.range.last() + && last_range.addr + last_range.len != addr + { + debug_serial_println!("Discontiguous address found {addr:#x}"); + // TODO: This should be an error once patch_info is fixed from VTL0 + } + self.range.push(MemoryRange { + addr, + phys_addr: phys_start, + len: u64::from(range.size), + }); + Ok(0) + } } impl core::ops::Deref for MemoryContainer { @@ -1556,11 +1633,16 @@ impl KexecMemoryMetadata { } #[inline] - pub(crate) fn insert_heki_range(&mut self, heki_range: &HekiRange) { - let va = heki_range.va; - let pa = heki_range.pa; - let epa = heki_range.epa; - self.insert_memory_range(KexecMemoryRange::new(va, pa, epa)); + pub(crate) fn insert_data_range(&mut self, range: &HekiDataRange) -> Result { + self.insert_memory_range(KexecMemoryRange::new( + range.va, + range.pa, + range + .pa + .checked_add(u64::from(range.size)) + .ok_or(VsmError::InvalidPhysicalAddress)?, + )); + Ok(0) } #[inline] @@ -1761,57 +1843,6 @@ pub struct Symbol { _value: u64, } -impl Symbol { - /// Parse a symbol from a byte buffer. - pub fn from_bytes( - kinfo_start: usize, - start: VirtAddr, - bytes: &[u8], - ) -> Result<(String, Self), VsmError> { - let kinfo_bytes = &bytes[kinfo_start..]; - let ksym = HekiKernelSymbol::from_bytes(kinfo_bytes)?; - - let value_addr = start + mem::offset_of!(HekiKernelSymbol, value_offset) as u64; - let value = value_addr - .as_u64() - .wrapping_add_signed(i64::from(ksym.value_offset)); - - let name_offset = kinfo_start - + mem::offset_of!(HekiKernelSymbol, name_offset) - + usize::try_from(ksym.name_offset).map_err(|_| VsmError::SymbolNameOffsetInvalid)?; - - if name_offset >= bytes.len() { - return Err(VsmError::SymbolNameOffsetInvalid); - } - let name_len = bytes[name_offset..] - .iter() - .position(|&b| b == 0) - .ok_or(VsmError::SymbolNameNoTerminator)?; - if name_len >= HekiKernelSymbol::KSY_NAME_LEN { - return Err(VsmError::SymbolNameTooLong); - } - - // SAFETY: - // - offset is within bytes (checked above) - // - there is a NUL terminator within bytes[offset..] (checked above) - // - Length of name string is within spec range (checked above) - // - bytes is still valid for the duration of this function - let name_str = unsafe { - let name_ptr = bytes.as_ptr().add(name_offset).cast::(); - CStr::from_ptr(name_ptr) - }; - let name = CString::new( - name_str - .to_str() - .map_err(|_| VsmError::SymbolNameInvalidUtf8)?, - ) - .map_err(|_| VsmError::SymbolNameInvalidUtf8)?; - let name = name - .into_string() - .map_err(|_| VsmError::SymbolNameInvalidUtf8)?; - Ok((name, Symbol { _value: value })) - } -} pub struct SymbolTable { inner: spin::rwlock::RwLock>, } @@ -1830,41 +1861,92 @@ impl SymbolTable { } } - /// Build a symbol table from a memory container. pub fn build_from_container( &self, - start: VirtAddr, - end: VirtAddr, - mem: &MemoryContainer, - buf: &[u8], + sym_table: &MemoryContainer, + sym_str_table: &MemoryContainer, ) -> Result { - if mem.is_empty() { - return Err(VsmError::SymbolTableEmpty); + #[derive(FromBytes, KnownLayout, Immutable)] + #[repr(C)] + struct SymbolTable { + sym: [HekiKernelSymbol], } - let Some(range) = mem.get_range() else { - return Err(VsmError::SymbolTableEmpty); + let Some(sym_table_range) = sym_table.get_range() else { + return Err(VsmError::KernelSymbolTableNotFound); }; - if start < range.start || end > range.end { - return Err(VsmError::SymbolTableOutOfRange); - } - let kinfo_len: usize = (end - start).truncate(); - if !kinfo_len.is_multiple_of(HekiKernelSymbol::KSYM_LEN) { + if !sym_table.len().is_multiple_of(HekiKernelSymbol::KSYM_LEN) { return Err(VsmError::SymbolTableLengthInvalid); } - let mut kinfo_offset: usize = (start - range.start).truncate(); - let mut kinfo_addr = start; - let ksym_count = kinfo_len / HekiKernelSymbol::KSYM_LEN; + let ksym_count = sym_table.len() / HekiKernelSymbol::KSYM_LEN; let mut inner = self.inner.write(); inner.reserve(ksym_count); - for _ in 0..ksym_count { - let (name, sym) = Symbol::from_bytes(kinfo_offset, kinfo_addr, buf)?; - inner.insert(name, sym); - kinfo_offset += HekiKernelSymbol::KSYM_LEN; - kinfo_addr += HekiKernelSymbol::KSYM_LEN as u64; + let sym_table = SymbolTable::ref_from_bytes_with_elems(&sym_table[..], ksym_count) + .map_err(|_| VsmError::SymbolTableLengthInvalid)?; + let mut sym_table_addr = sym_table_range.start; + + for (i, ksym) in (sym_table.sym).iter().enumerate() { + let (name, sym) = Self::sym_from_bytes(ksym, sym_table_addr, sym_str_table)?; + if i < 2 { + debug_serial_println!("symbol:{name}"); + } + inner.insert(name, Symbol { _value: sym }); + sym_table_addr += HekiKernelSymbol::KSYM_LEN as u64; } Ok(0) } + + pub fn sym_from_bytes( + ksym: &HekiKernelSymbol, + addr: VirtAddr, + sym_str_table: &MemoryContainer, + ) -> Result<(String, u64), VsmError> { + let Some(sym_str_table_range) = sym_str_table.get_range() else { + return Err(VsmError::SymbolTableOutOfRange); + }; + + let bytes = &sym_str_table[..]; + let value_addr = addr + mem::offset_of!(HekiKernelSymbol, value_offset) as u64; + let value = value_addr + .as_u64() + .wrapping_add_signed(i64::from(ksym.value_offset)); + + let name_addr = addr + + mem::offset_of!(HekiKernelSymbol, name_offset) as u64 + + u64::try_from(ksym.name_offset).map_err(|_| VsmError::SymbolNameOffsetInvalid)?; + if name_addr < sym_str_table_range.start || name_addr >= sym_str_table_range.end { + return Err(VsmError::SymbolTableOutOfRange); + } + let name_offset = usize::try_from(name_addr - sym_str_table_range.start) + .map_err(|_| VsmError::SymbolNameOffsetInvalid)?; + let name_len = bytes[name_offset..] + .iter() + .position(|&b| b == 0) + .ok_or(VsmError::SymbolNameNoTerminator)?; + if name_len >= HekiKernelSymbol::KSY_NAME_LEN { + return Err(VsmError::SymbolNameTooLong); + } + + // SAFETY: + // - offset is within bytes (checked above) + // - there is a NUL terminator within bytes[offset..] (checked above) + // - Length of name string is within spec range (checked above) + // - bytes is still valid for the duration of this function + let name_str = unsafe { + let name_ptr = bytes.as_ptr().add(name_offset).cast::(); + CStr::from_ptr(name_ptr) + }; + let name = CString::new( + name_str + .to_str() + .map_err(|_| VsmError::SymbolNameInvalidUtf8)?, + ) + .map_err(|_| VsmError::SymbolNameInvalidUtf8)?; + let name = name + .into_string() + .map_err(|_| VsmError::SymbolNameInvalidUtf8)?; + Ok((name, value)) + } }