Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion litebox_platform_lvbs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
22 changes: 21 additions & 1 deletion litebox_platform_lvbs/src/mshv/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<VerificationError> for VsmError {
Expand Down Expand Up @@ -217,7 +232,12 @@ impl From<VsmError> 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),
Expand Down
218 changes: 29 additions & 189 deletions litebox_platform_lvbs/src/mshv/heki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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<U>(&self, align: U) -> bool
where
U: Into<u64> + 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<MemAttr> {
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::<HekiRange>() 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 {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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::<HekiKernelInfo>();

pub fn from_bytes(bytes: &[u8]) -> Result<Self, VsmError> {
if bytes.len() < Self::KINFO_LEN {
return Err(VsmError::BufferTooSmall("HekiKernelInfo"));
}

#[allow(clippy::cast_ptr_alignment)]
let kinfo_ptr = bytes.as_ptr().cast::<HekiKernelInfo>();
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,
})
}
}
}
Loading
Loading