Skip to content

Commit

Permalink
vlek: Adding vlek ioctls
Browse files Browse the repository at this point in the history
Adding in ioctls for SNP_VLEK_LOAD to allow
platform owners to load VLEK hashstick where
appropriate.

Signed-off-by: Larry Dewey <larry.dewey@amd.com>
  • Loading branch information
larrydewey committed May 8, 2024
1 parent 897644c commit a18cd41
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 1 deletion.
40 changes: 40 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ pub enum UserApiError {
/// Uuid parsing errors.
UuidError(uuid::Error),

/// VLEK errors.
HashstickError(HashstickError),

/// Invalid VMPL.
VmplError,

Expand All @@ -188,6 +191,7 @@ impl error::Error for UserApiError {
Self::FirmwareError(firmware_error) => Some(firmware_error),
Self::UuidError(uuid_error) => Some(uuid_error),
Self::VmmError(vmm_error) => Some(vmm_error),
Self::HashstickError(vlek_error) => Some(vlek_error),
Self::VmplError => None,
Self::Unknown => None,
}
Expand All @@ -201,13 +205,20 @@ impl std::fmt::Display for UserApiError {
Self::ApiError(error) => format!("Certificate Error Encountered: {error}"),
Self::UuidError(error) => format!("UUID Error Encountered: {error}"),
Self::VmmError(error) => format!("VMM Error Encountered: {error}"),
Self::HashstickError(error) => format!("VLEK Error Encountered: {error}"),
Self::VmplError => "Invalid VM Permission Level (VMPL)".to_string(),
Self::Unknown => "Unknown Error Encountered!".to_string(),
};
write!(f, "{err_msg}")
}
}

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

impl std::convert::From<VmmError> for UserApiError {
fn from(value: VmmError) -> Self {
Self::VmmError(value)
Expand Down Expand Up @@ -238,6 +249,35 @@ impl std::convert::From<CertError> for UserApiError {
}
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Errors which may be encountered when handling Version Loaded Endorsement Keys
pub enum HashstickError {
/// Certificate length does not match what was specified in the buffer.
InvalidLength,

/// No certificates were provided
EmptyHashstickBuffer,

/// Unknown Error.
UnknownError,
}

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

impl std::fmt::Display for HashstickError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HashstickError::InvalidLength => {
write!(f, "VLEK is an invalid length. Should be 432 bytes in size.")
}
HashstickError::EmptyHashstickBuffer => write!(f, "Hashstick buffer is empty."),
HashstickError::UnknownError => {
write!(f, "Unknown error encountered when handling the VLEK.")
}
}
}
}

#[derive(Debug)]
/// Errors which may be encountered through misuse of the User API.
pub enum CertError {
Expand Down
24 changes: 24 additions & 0 deletions src/firmware/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,30 @@ impl Firmware {

Ok(())
}

#[cfg(feature = "snp")]
/// Insert a Version Loaded Endorsement Key Hash Stick into the AMD Secure Processor.
///
/// # Example:
/// ```ignore
/// # Read the VLEK Hashstick Bytes into your application.
/// # Our variable will be "hashstick_bytes"
///
/// let mut firmware: Firmware = Firmware::open().unwrap();
///
/// firmware.snp_vlek_load(hashstick_bytes.as_slice()).unwrap();
/// ```
pub fn snp_vlek_load(&mut self, hashstick_bytes: &[u8]) -> Result<(), UserApiError> {
use types::FFI::types::{SnpVlekLoad, WrappedVlekHashstick};

let parsed_bytes: WrappedVlekHashstick = hashstick_bytes.try_into()?;

let mut vlek_load: SnpVlekLoad = SnpVlekLoad::new(&parsed_bytes);

SNP_VLEK_LOAD.ioctl(&mut self.0, &mut Command::from_mut(&mut vlek_load))?;

Ok(())
}
}

#[cfg(target_os = "linux")]
Expand Down
6 changes: 6 additions & 0 deletions src/firmware/linux/host/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl_const_id! {
SnpPlatformStatus = 0x9,
SnpCommit = 0xA,
SnpSetConfig = 0xB,
SnpVlekLoad = 0xC,
}

#[cfg(all(feature = "sev", not(feature = "snp")))]
Expand All @@ -56,6 +57,7 @@ impl_const_id! {
SnpPlatformStatus = 0x9,
SnpCommit = 0xA,
SnpSetConfig = 0xB,
SnpVlekLoad = 0xC,
}

const SEV: Group = Group::new(b'S');
Expand Down Expand Up @@ -114,6 +116,10 @@ pub const SNP_COMMIT: Ioctl<WriteRead, &Command<SnpCommit>> = unsafe { SEV.write
#[cfg(feature = "snp")]
pub const SNP_SET_CONFIG: Ioctl<WriteRead, &Command<SnpSetConfig>> = unsafe { SEV.write_read(0) };

#[cfg(feature = "snp")]
/// Load a specified VLEK into the AMD Secure Processor to be used in place of VCEK.
pub const SNP_VLEK_LOAD: Ioctl<WriteRead, &Command<SnpVlekLoad>> = unsafe { SEV.write_read(0) };

/// The Rust-flavored, FFI-friendly version of `struct sev_issue_cmd` which is
/// used to pass arguments to the SEV ioctl implementation.
///
Expand Down
139 changes: 138 additions & 1 deletion src/firmware/linux/host/types/snp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#[cfg(target_os = "linux")]
use crate::error::CertError;

use crate::firmware::host as UAPI;
use crate::{error::HashstickError, firmware::host as UAPI};

#[cfg(target_os = "linux")]
use uuid::Uuid;
Expand Down Expand Up @@ -249,6 +249,71 @@ impl Default for SnpSetConfig {
}
}

// Length defined in the Linux Kernel for the IOCTL.
const HASHSTICK_BUFFER_LEN: usize = 432;

#[cfg(feature = "snp")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(C, packed)]
/// Wrapped VLEK data.
pub struct WrappedVlekHashstick<'a> {
/// Opaque data provided by AMD Key Distribution Server
/// (as described in SEV-SNP Firmware ABI 1.54, SNP_VLEK_LOAD)
pub data: &'a [u8], // 432 bytes of data
}

impl<'a, 'b: 'a> std::convert::TryFrom<&'b [u8]> for WrappedVlekHashstick<'a> {
type Error = HashstickError;

fn try_from(value: &'b [u8]) -> Result<Self, Self::Error> {
if value.len() != HASHSTICK_BUFFER_LEN {
return Err(HashstickError::InvalidLength);
}

if value == [0u8; HASHSTICK_BUFFER_LEN] {
return Err(HashstickError::EmptyHashstickBuffer);
}

Ok(Self { data: value })
}
}

#[cfg(feature = "snp")]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(C, packed)]
/// Structure used to load a VLEK hashstick into the AMD Secure Processor.
pub struct SnpVlekLoad {
/// Length of the command buffer read by the AMD Secure Processor.
pub len: u32,

/// Version of wrapped VLEK hashstick (Must be 0h).
pub vlek_wrapped_version: u8,

_reserved: [u8; 3],

/// Address of wrapped VLEK hashstick ([WrappedVlekHashstick])
pub vlek_wrapped_address: u64,
}

#[cfg(feature = "snp")]
impl SnpVlekLoad {
/// Creates a new VLEK load instruction from a hashstick.
pub fn new(hashstick: &WrappedVlekHashstick) -> Self {
hashstick.into()
}
}

impl<'a> std::convert::From<&WrappedVlekHashstick<'a>> for SnpVlekLoad {
fn from(value: &WrappedVlekHashstick<'a>) -> Self {
Self {
len: value.data.len() as u32,
vlek_wrapped_version: 0u8,
_reserved: Default::default(),
vlek_wrapped_address: value as *const WrappedVlekHashstick as u64,
}
}
}

#[cfg(test)]
mod test {
mod raw_data {
Expand Down Expand Up @@ -307,6 +372,78 @@ mod test {
}
}

#[cfg(target_os = "linux")]
mod hashstick {
use std::convert::TryFrom;

use crate::{error::HashstickError, firmware::linux::host::types::SnpVlekLoad};

use super::super::{WrappedVlekHashstick, HASHSTICK_BUFFER_LEN};

const VALID_HASHSTICK_BYTES: [u8; HASHSTICK_BUFFER_LEN] = [1u8; HASHSTICK_BUFFER_LEN];
const INVALID_HASHSTICK_BYTES: [u8; 25] = [2u8; 25];

#[test]
fn test_bytes_to_wrapped_hashstick() {
let bytes: [u8; HASHSTICK_BUFFER_LEN] = VALID_HASHSTICK_BYTES;
let expected: WrappedVlekHashstick = WrappedVlekHashstick { data: &bytes };
let actual: WrappedVlekHashstick =
WrappedVlekHashstick::try_from(VALID_HASHSTICK_BYTES.as_slice()).unwrap();

assert_eq!(actual, expected)
}

#[test]
fn test_invalid_bytes_to_wrapped_hashstick() {
assert_eq!(
WrappedVlekHashstick::try_from(INVALID_HASHSTICK_BYTES.as_slice()).unwrap_err(),
HashstickError::InvalidLength
);
}

#[test]
fn test_empty_buffer_to_wrapped_hashstick() {
assert_eq!(
WrappedVlekHashstick::try_from([0; HASHSTICK_BUFFER_LEN].as_slice()).unwrap_err(),
HashstickError::EmptyHashstickBuffer
)
}

#[test]
fn test_wrapped_hashtick_into_snp_vlek_load() {
let test_hashstick: WrappedVlekHashstick =
WrappedVlekHashstick::try_from(VALID_HASHSTICK_BYTES.as_slice()).unwrap();

let actual: SnpVlekLoad = (&test_hashstick).into();

let expected: SnpVlekLoad = SnpVlekLoad {
len: HASHSTICK_BUFFER_LEN as u32,
vlek_wrapped_version: 0u8,
_reserved: Default::default(),
vlek_wrapped_address: &test_hashstick as *const WrappedVlekHashstick as u64,
};

assert_eq!(actual, expected);
}

#[test]
fn test_snp_vlek_load_new() {
let test_hashstick: WrappedVlekHashstick =
WrappedVlekHashstick::try_from(VALID_HASHSTICK_BYTES.as_slice()).unwrap();

let actual: SnpVlekLoad = SnpVlekLoad::new(&test_hashstick);

let expected: SnpVlekLoad = SnpVlekLoad {
len: HASHSTICK_BUFFER_LEN as u32,
vlek_wrapped_version: 0u8,
_reserved: Default::default(),
vlek_wrapped_address: &test_hashstick as *const WrappedVlekHashstick as u64,
};

assert_eq!(actual, expected);
}
}

#[cfg(target_os = "linux")]
mod cert_table_entry {

Expand Down

0 comments on commit a18cd41

Please sign in to comment.