#![expect(non_camel_case_types)]
use super::MeasurementError;
use super::SHA_256_OUTPUT_SIZE;
use crate::IgvmDirectiveHeader;
use bitfield_struct::bitfield;
use igvm_defs::IgvmPageDataType;
use igvm_defs::VbsVpContextRegister;
use igvm_defs::PAGE_SIZE_4K;
use open_enum::open_enum;
use sha2::Digest;
use sha2::Sha256;
use static_assertions::const_assert;
use std::collections::HashMap;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;
const PAGE_SIZE_4K_USIZE: usize = PAGE_SIZE_4K as usize;
const VBS_VP_CHUNK_SIZE_BYTES: usize = PAGE_SIZE_4K_USIZE + size_of::<VpGpaPageChunk>();
const VM_GPA_PAGE_READABLE: u64 = 0x1;
const VM_GPA_PAGE_WRITABLE: u64 = 0x2;
#[repr(C)]
#[derive(IntoBytes, Immutable, KnownLayout)]
struct VbsChunkHeader {
byte_count: u32,
chunk_type: BootMeasurementType,
reserved: u64,
}
#[repr(C)]
#[derive(IntoBytes, Immutable, KnownLayout)]
struct VbsRegisterChunk {
header: VbsChunkHeader,
reserved: u32,
vtl: u8,
reserved2: u8,
reserved3: u16,
reserved4: u32,
name: u32,
value: [u8; 16],
}
const_assert!(size_of::<VbsRegisterChunk>() <= VBS_VP_CHUNK_SIZE_BYTES);
#[repr(C)]
#[derive(IntoBytes, Immutable, KnownLayout)]
struct VpGpaPageChunk {
header: VbsChunkHeader,
metadata: u64,
page_number: u64,
}
#[open_enum]
#[derive(IntoBytes, Immutable, KnownLayout, Clone, Copy)]
#[repr(u32)]
enum BootMeasurementType {
VP_REGISTER = 0,
VP_VTL_ENABLED = 1,
VP_GPA_PAGE = 2,
}
#[bitfield(u64)]
struct VBS_VM_GPA_PAGE_BOOT_METADATA {
#[bits(2)]
acceptance: u64,
#[bits(1)]
data_unmeasured: bool,
#[bits(61)]
reserved: u64,
}
pub fn generate_vbs_measurement(
directive_headers: &[IgvmDirectiveHeader],
compatibility_mask: u32,
) -> Result<[u8; SHA_256_OUTPUT_SIZE], MeasurementError> {
let mut digest = VbsDigestor::new();
let mut parameter_area_table = HashMap::new();
let mut bsp_regs: Vec<Vec<VbsVpContextRegister>> = Vec::new();
for header in directive_headers {
if header
.compatibility_mask()
.map(|mask| mask & compatibility_mask != compatibility_mask)
.unwrap_or(false)
{
continue;
}
match header {
IgvmDirectiveHeader::PageData {
gpa,
compatibility_mask: _,
flags,
data_type,
data,
} => {
assert_eq!(*data_type, IgvmPageDataType::NORMAL);
if flags.shared() {
continue;
}
let boot_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
.with_acceptance(0)
.with_data_unmeasured(flags.unmeasured());
digest.record_gpa_page(gpa / PAGE_SIZE_4K, 1, boot_metadata, data);
}
IgvmDirectiveHeader::ParameterInsert(param) => {
let page_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
.with_acceptance(0)
.with_data_unmeasured(true);
let parameter_area_size = parameter_area_table
.get(¶m.parameter_area_index)
.ok_or(MeasurementError::InvalidParameterAreaIndex(
param.parameter_area_index,
))?;
digest.record_gpa_page(
param.gpa / PAGE_SIZE_4K,
parameter_area_size / PAGE_SIZE_4K,
page_metadata,
&[],
);
}
IgvmDirectiveHeader::X64VbsVpContext {
vtl,
registers,
compatibility_mask: _,
} => {
let vtl_registers: Vec<VbsVpContextRegister> = registers
.iter()
.map(|r| r.into_vbs_vp_context_reg(*vtl))
.collect();
bsp_regs.push(vtl_registers);
}
IgvmDirectiveHeader::AArch64VbsVpContext {
vtl,
registers,
compatibility_mask: _,
} => {
let vtl_registers: Vec<VbsVpContextRegister> = registers
.iter()
.map(|r| r.into_vbs_vp_context_reg(*vtl))
.collect();
bsp_regs.push(vtl_registers);
}
IgvmDirectiveHeader::ErrorRange {
gpa,
compatibility_mask: _,
size_bytes,
} => {
let page_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
.with_acceptance(VM_GPA_PAGE_READABLE | VM_GPA_PAGE_WRITABLE)
.with_data_unmeasured(true);
digest.record_gpa_page(
*gpa / PAGE_SIZE_4K,
(*size_bytes as u64).div_ceil(PAGE_SIZE_4K),
page_metadata,
&[],
);
}
IgvmDirectiveHeader::ParameterArea {
number_of_bytes,
parameter_area_index,
initial_data: _,
} => {
if parameter_area_table.contains_key(parameter_area_index) {
return Err(MeasurementError::InvalidParameterAreaIndex(
*parameter_area_index,
));
}
parameter_area_table.insert(*parameter_area_index, *number_of_bytes);
}
_ => {}
}
}
for set in bsp_regs {
for reg in set {
digest.record_vp_register(reg);
}
}
Ok(digest.finish_digest())
}
struct VbsDigestor {
digest: [u8; SHA_256_OUTPUT_SIZE],
}
impl VbsDigestor {
fn new() -> Self {
Self {
digest: [0; SHA_256_OUTPUT_SIZE],
}
}
fn record_gpa_page(
&mut self,
gpa_page_base: u64,
page_count: u64,
page_metadata: VBS_VM_GPA_PAGE_BOOT_METADATA,
mut data: &[u8],
) {
for page in 0..page_count {
let import_data_len: usize = match page_metadata.data_unmeasured() {
true => 0,
false => std::cmp::min(PAGE_SIZE_4K_USIZE, data.len()),
};
let (import_data, data_remaining) = data.split_at(import_data_len);
data = data_remaining;
let padding = vec![0u8; PAGE_SIZE_4K_USIZE - import_data.len()];
let page_number = gpa_page_base + page;
let chunk = VpGpaPageChunk {
header: VbsChunkHeader {
byte_count: VBS_VP_CHUNK_SIZE_BYTES as u32,
chunk_type: BootMeasurementType::VP_GPA_PAGE,
reserved: 0,
},
metadata: page_metadata.into(),
page_number,
};
self.create_record_entry(&[chunk.as_bytes(), import_data, &padding]);
}
}
fn record_vp_register(&mut self, reg: VbsVpContextRegister) {
let chunk = VbsRegisterChunk {
header: VbsChunkHeader {
byte_count: size_of::<VbsRegisterChunk>() as u32,
chunk_type: BootMeasurementType::VP_REGISTER,
reserved: 0,
},
reserved: 0,
vtl: reg.vtl,
reserved2: 0,
reserved3: 0,
reserved4: 0,
name: reg.register_name.into(),
value: reg.register_value,
};
self.create_record_entry(&[chunk.as_bytes()]);
}
fn create_record_entry(&mut self, chunks: &[&[u8]]) {
let mut hasher = Sha256::new();
hasher.update(self.digest.as_slice());
for chunk in chunks {
hasher.update(chunk);
}
self.digest = hasher.finalize().into();
}
fn finish_digest(&self) -> [u8; SHA_256_OUTPUT_SIZE] {
self.digest
}
}