Skip to content

Commit

Permalink
Adding Measurement functionality to library
Browse files Browse the repository at this point in the history
Signed-off-by: Diego Gonzalez Villalobos <[email protected]>
  • Loading branch information
DGonzalezVillal committed Oct 19, 2023
1 parent a6d6568 commit 55e6cc9
Show file tree
Hide file tree
Showing 13 changed files with 2,011 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ bincode = "^1.3"
hex = "0.4.3"
libc = "0.2.147"
lazy_static = "1.4.0"
byteorder = "1.4.3"

[dev-dependencies]
kvm-ioctls = ">=0.12"
Expand Down
231 changes: 231 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0

use bincode;
use std::{
array::TryFromSliceError,
convert::From,
error,
fmt::{Debug, Display},
Expand Down Expand Up @@ -597,3 +599,232 @@ impl From<Indeterminate<Error>> for c_int {
}
}
}

#[derive(Debug)]
/// Errors which may be encountered when building custom guest context.
pub enum GCTXError {
/// Malformed guest context page.
InvalidPageSize(usize, usize),

/// Block size data was the incorrect size
InvalidBlockSize,

/// Unknown Error.
UnknownError,
}

impl std::fmt::Display for GCTXError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
GCTXError::InvalidPageSize(actual, expected) => write!(
f,
"Page information was not the correct length ({actual} vs {expected})"
),
GCTXError::InvalidBlockSize => {
write!(f, "Provided data does not conform to a 4096 block size")
}
GCTXError::UnknownError => write!(f, "An unknown Guest Context error encountered"),
}
}
}

impl std::error::Error for GCTXError {}

#[derive(Debug)]
/// Errors which may be encountered when handling OVMF data
pub enum OVMFError {
/// An invalid section type was provided for OVMF METADATA
InvalidSectionType,

/// Part of the SEV METADATA failed verification
SEVMetadataVerification(String),

/// Desired entry is missing from table
EntryMissingInTable(String),

/// Invalid Entry Size was provided
InvalidSize(String, usize, usize),

/// GUID doesn't match expected GUID
MismatchingGUID(),

/// Unknown Error.
UnknownError,
}

impl std::fmt::Display for OVMFError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
OVMFError::InvalidSectionType => write!(f, "An invalid section type was found"),
OVMFError::SEVMetadataVerification(section) => {
write!(f, "Wrong SEV metadata {section}")
}
OVMFError::EntryMissingInTable(entry) => {
write!(f, "Can't find {entry} entry in OVMF table")
}
OVMFError::InvalidSize(entry, actual, expected) => {
write!(f, "Invalid size of {entry}: {actual} < {expected}")
}
OVMFError::MismatchingGUID() => {
write!(f, "OVMF table footer GUID does not match expected GUID")
}
OVMFError::UnknownError => write!(f, "An unknown Guest Context error encountered"),
}
}
}

impl std::error::Error for OVMFError {}

#[derive(Debug)]
/// Errors which may be encountered when building SEV hashes.
pub enum SevHashError {
/// Provided page has invalid size
InvalidSize(usize, usize),

/// Provided page has invalid offset
InvalidOffset(usize, usize),

/// Unknown Error.
UnknownError,
}

impl std::fmt::Display for SevHashError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
SevHashError::InvalidOffset(actual, expected) => {
write!(f, "Invalid page Offset: {actual} vs {expected}")
}
SevHashError::InvalidSize(actual, expected) => {
write!(f, "Invalid page Size: {actual} vs {expected}")
}
SevHashError::UnknownError => write!(f, "An unknown Guest Context error encountered"),
}
}
}

impl std::error::Error for SevHashError {}

#[derive(Debug)]
/// Errors which may be encountered when calculating the guest measurement.
pub enum MeasurementError {
/// TryFrom Slice Error handling
FromSliceError(TryFromSliceError),

/// UUID Error handling
UUIDError(uuid::Error),

/// Bincode Error Handling
BincodeError(bincode::ErrorKind),

/// File Error Handling
FileError(std::io::Error),

/// Vec from hex Error Handling
FromHexError(hex::FromHexError),

/// Guest Context Error Handling
GCTXError(GCTXError),

/// OVMF Error Handling
OVMFError(OVMFError),

/// SEV Hash Error Handling
SevHashError(SevHashError),

/// Invalid VCPU provided
InvalidVcpuTypeError(String),

/// Invalid VMM Provided
InvalidVmmError(String),

/// Invalid SEV Mode provided
InvalidSevModeError(String),

/// Kernel specified for wrong OVMF
KernelSpecifiedError,

/// OVMF is missing required section with kernel specified
MissingSection(String),
}

impl std::fmt::Display for MeasurementError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MeasurementError::FromSliceError(e) => write!(f, "Error converting slice: {e}"),
MeasurementError::UUIDError(e) => write!(f, "UUID Error encountered: {e}"),
MeasurementError::BincodeError(e) => write!(f, "Bincode error encountered: {e}"),
MeasurementError::FileError(e) => write!(f, "Failed handling file: {e}"),
MeasurementError::FromHexError(e) => write!(f, "Converting hex to vector error: {e}"),
MeasurementError::GCTXError(e) => write!(f, "GCTX Error Encountered: {e}"),
MeasurementError::OVMFError(e) => write!(f, "OVMF Error Encountered: {e}"),
MeasurementError::SevHashError(e) => write!(f, "Sev hash Error Encountered: {e}"),
MeasurementError::InvalidVcpuTypeError(value) => {
write!(f, "Invalid VCPU type value provided: {value}")
}
MeasurementError::InvalidVmmError(value) => {
write!(f, "Invalid VMM type provided: {value}")
}
MeasurementError::InvalidSevModeError(value) => {
write!(f, "Invalid SEV mode provided: {value}")
}
MeasurementError::KernelSpecifiedError => write!(
f,
"Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement"
),
MeasurementError::MissingSection(section) => write!(
f,
"Kernel specified but OVMF metadata doesn't include {section} section"
),
}
}
}

impl std::error::Error for MeasurementError {}

impl std::convert::From<TryFromSliceError> for MeasurementError {
fn from(value: TryFromSliceError) -> Self {
Self::FromSliceError(value)
}
}

impl std::convert::From<uuid::Error> for MeasurementError {
fn from(value: uuid::Error) -> Self {
Self::UUIDError(value)
}
}

impl std::convert::From<bincode::ErrorKind> for MeasurementError {
fn from(value: bincode::ErrorKind) -> Self {
Self::BincodeError(value)
}
}

impl std::convert::From<std::io::Error> for MeasurementError {
fn from(value: std::io::Error) -> Self {
Self::FileError(value)
}
}

impl std::convert::From<hex::FromHexError> for MeasurementError {
fn from(value: hex::FromHexError) -> Self {
Self::FromHexError(value)
}
}

impl std::convert::From<GCTXError> for MeasurementError {
fn from(value: GCTXError) -> Self {
Self::GCTXError(value)
}
}

impl std::convert::From<OVMFError> for MeasurementError {
fn from(value: OVMFError) -> Self {
Self::OVMFError(value)
}
}

impl std::convert::From<SevHashError> for MeasurementError {
fn from(value: SevHashError) -> Self {
Self::SevHashError(value)
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub mod certs;
pub mod firmware;
#[cfg(target_os = "linux")]
pub mod launch;
#[cfg(all(any(feature = "sev", feature = "snp"), feature = "openssl"))]
pub mod measurement;
#[cfg(all(target_os = "linux", feature = "openssl", feature = "sev"))]
pub mod session;
mod util;
Expand Down
135 changes: 135 additions & 0 deletions src/measurement/gctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use openssl::sha::sha384;

use crate::error::*;
use std::convert::TryInto;

// Launch digest size in bytes
const LD_SIZE: usize = 384 / 8;

// VMSA page is recorded in the RMP table with GPA (u64)(-1).
// However, the address is page-aligned, and also all the bits above
// 51 are cleared.
const VMSA_GPA: u64 = 0xFFFFFFFFF000;

// Launch digest intialized in all zeros
const ZEROS: [u8; LD_SIZE] = [0; LD_SIZE];

/// Guest context field structure
pub struct Gctx {
/// Launch Digest, 48 bytes long
ld: [u8; LD_SIZE],
}

/// Default init of GCTX, launch digest of all 0s
impl Default for Gctx {
fn default() -> Gctx {
Gctx { ld: ZEROS }
}
}

impl Gctx {
/// Initialize a new guest context using existing data
pub fn new(seed: &[u8]) -> Result<Self, MeasurementError> {
Ok(Self {
ld: seed.try_into()?,
})
}

/// Get the launch digest bytes
pub fn get_ld(self) -> [u8; LD_SIZE] {
self.ld
}

/// Will update guest context launch digest with provided data from page
fn update(
&mut self,
page_type: u8,
gpa: u64,
contents: [u8; LD_SIZE],
) -> Result<(), GCTXError> {
let page_info_len: u16 = 0x70;
let is_imi: u8 = 0;
let vmpl3_perms: u8 = 0;
let vmpl2_perms: u8 = 0;
let vmpl1_perms: u8 = 0;

let mut page_info: Vec<u8> = self.ld.to_vec();
page_info.extend_from_slice(&contents);

page_info.extend_from_slice(&page_info_len.to_le_bytes());
page_info.extend_from_slice(&page_type.to_le_bytes());
page_info.extend_from_slice(&is_imi.to_le_bytes());

page_info.extend_from_slice(&vmpl3_perms.to_le_bytes());
page_info.extend_from_slice(&vmpl2_perms.to_le_bytes());
page_info.extend_from_slice(&vmpl1_perms.to_le_bytes());
page_info.extend_from_slice(&(0_u8).to_le_bytes());

page_info.extend_from_slice(&gpa.to_le_bytes());

if page_info.len() != (page_info_len as usize) {
return Err(GCTXError::InvalidPageSize(
page_info.len(),
page_info_len as usize,
));
}

self.ld = sha384(&page_info);

Ok(())
}

/// Update launch digest using normal memory pages
pub fn update_normal_pages(&mut self, start_gpa: u64, data: &[u8]) -> Result<(), GCTXError> {
if (data.len() % 4096) != 0 {
return Err(GCTXError::InvalidBlockSize);
}
let mut offset = 0;
while offset < data.len() {
let page_data = &data[offset..offset + 4096];
self.update(0x01, start_gpa + offset as u64, sha384(page_data))?;
offset += 4096;
}
Ok(())
}

/// Update launch digest using VMSA memory pages
pub fn update_vmsa_page(&mut self, data: &[u8]) -> Result<(), GCTXError> {
if data.len() != 4096 {
return Err(GCTXError::InvalidBlockSize);
}
self.update(0x02, VMSA_GPA, sha384(data))?;
Ok(())
}

/// Update launch digest using ZERO pages
pub fn update_zero_pages(&mut self, gpa: u64, length_bytes: usize) -> Result<(), GCTXError> {
if (length_bytes % 4096) != 0 {
return Err(GCTXError::InvalidBlockSize);
};
let mut offset = 0;
while offset < length_bytes {
self.update(0x03, gpa + offset as u64, ZEROS)?;
offset += 4096;
}
Ok(())
}

/// Update launch digest using an unmeasured page
fn _update_unmeasured_page(&mut self, gpa: u64) -> Result<(), GCTXError> {
self.update(0x04, gpa, ZEROS)?;
Ok(())
}

/// Update launch digest using a secret page
pub fn update_secrets_page(&mut self, gpa: u64) -> Result<(), GCTXError> {
self.update(0x05, gpa, ZEROS)?;
Ok(())
}

/// Update launch digest using a CPUID page
pub fn update_cpuid_page(&mut self, gpa: u64) -> Result<(), GCTXError> {
self.update(0x06, gpa, ZEROS)?;
Ok(())
}
}
Loading

0 comments on commit 55e6cc9

Please sign in to comment.