diff --git a/Cargo.lock b/Cargo.lock index dd6c59e7..f17ea88a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,9 +388,9 @@ checksum = "d8972d5be69940353d5347a1344cb375d9b457d6809b428b05bb1ca2fb9ce007" [[package]] name = "kvm-bindings" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c1030d8ce8a7ac5797738f98d6e16450d2ddd298b4555c492009108822d3bd" +checksum = "2efe3f1a4437bffe000e6297a593b98184213cd27486776c335f95ab53d48e3a" dependencies = [ "vmm-sys-util", ] diff --git a/Cargo.toml b/Cargo.toml index f2dcbbe4..75c61576 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,5 +75,5 @@ rdrand = { version = "^0.8", optional = true } kvm-ioctls = ">=0.16" [dev-dependencies] -kvm-bindings = ">=0.7" +kvm-bindings = ">=0.9.1" serial_test = "3.0" diff --git a/src/launch/linux/ioctl.rs b/src/launch/linux/ioctl.rs index 52b7c648..f891f425 100644 --- a/src/launch/linux/ioctl.rs +++ b/src/launch/linux/ioctl.rs @@ -14,6 +14,9 @@ use crate::launch::linux::sev; #[cfg(feature = "snp")] use crate::launch::linux::snp; +#[cfg(all(feature = "sev", feature = "snp"))] +use crate::launch::linux::shared; + use std::{ marker::PhantomData, os::{raw::c_ulong, unix::io::AsRawFd}, @@ -22,7 +25,7 @@ use std::{ use iocuddle::*; // These enum ordinal values are defined in the Linux kernel -// source code: include/uapi/linux/kvm.h +// source code: arch/x86/include/uapi/asm/kvm.h #[cfg(all(feature = "sev", feature = "snp"))] impl_const_id! { /// The ioctl sub number @@ -38,10 +41,11 @@ impl_const_id! { sev::LaunchFinish = 7, sev::LaunchAttestation<'_> = 20, - snp::Init = 22, - snp::LaunchStart<'_> = 23, - snp::LaunchUpdate<'_> = 24, - snp::LaunchFinish<'_> = 25, + shared::Init2 = 22, + + snp::LaunchStart = 100, + snp::LaunchUpdate<'_> = 101, + snp::LaunchFinish<'_> = 102, } #[cfg(all(feature = "sev", not(feature = "snp")))] @@ -58,6 +62,8 @@ impl_const_id! { sev::LaunchMeasure<'_> = 6, sev::LaunchFinish = 7, sev::LaunchAttestation<'_> = 20, + + shared::Init2 = 22 } #[cfg(all(not(feature = "sev"), feature = "snp"))] @@ -65,14 +71,16 @@ impl_const_id! { /// The ioctl sub number pub Id => u32; - snp::Init = 22, - snp::LaunchStart<'_> = 23, - snp::LaunchUpdate<'_> = 24, - snp::LaunchFinish<'_> = 25, + shared::Init2 = 22, + + snp::LaunchStart = 100, + snp::LaunchUpdate<'_> = 101, + snp::LaunchFinish<'_> = 102, } const KVM: Group = Group::new(0xAE); const ENC_OP: Ioctl = unsafe { KVM.write_read(0xBA) }; +pub const KVM_MEMORY_ATTRIBUTE_PRIVATE: u64 = 1 << 3; // Note: the iocuddle::Ioctl::lie() constructor has been used here because // KVM_MEMORY_ENCRYPT_OP ioctl was defined like this: @@ -88,11 +96,13 @@ const ENC_OP: Ioctl = unsafe { KVM.write_read(0xBA) }; /// Initialize the SEV platform context. #[cfg(feature = "sev")] -pub const INIT: Ioctl> = unsafe { ENC_OP.lie() }; +#[deprecated(note = "Init2 should be used instead")] +pub const _INIT: Ioctl> = unsafe { ENC_OP.lie() }; /// Initialize the SEV-ES platform context. #[cfg(feature = "sev")] -pub const ES_INIT: Ioctl> = unsafe { ENC_OP.lie() }; +#[deprecated(note = "Init2 should be used instead")] +pub const _ES_INIT: Ioctl> = unsafe { ENC_OP.lie() }; /// Create encrypted guest context. #[cfg(feature = "sev")] @@ -132,9 +142,14 @@ pub const LAUNCH_ATTESTATION: Ioctl> pub const ENC_REG_REGION: Ioctl = unsafe { KVM.read::(0xBB).lie() }; -/// Initialize the SEV-SNP platform in KVM. -#[cfg(feature = "snp")] -pub const SNP_INIT: Ioctl> = unsafe { ENC_OP.lie() }; +/// Corresponds to the `KVM_SET_MEMORY_ATTRIBUTES` ioctl +#[cfg(any(feature = "sev", feature = "snp"))] +pub const SET_MEMORY_ATTRIBUTES: Ioctl = + unsafe { KVM.write::(0xd2) }; + +/// Use the KVM_SEV_INIT2 ioctl to initialize the SEV platform context. +#[cfg(any(feature = "sev", feature = "snp"))] +pub const INIT2: Ioctl> = unsafe { ENC_OP.lie() }; /// Initialize the flow to launch a guest. #[cfg(feature = "snp")] @@ -175,6 +190,36 @@ impl<'a> KvmEncRegion<'a> { } } +/// Corresponds to the kernel struct `kvm_memory_attributes` +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct KvmSetMemoryAttributes { + addr: u64, + size: u64, + attributes: u64, + flags: u64, +} + +impl KvmSetMemoryAttributes { + /// Create a new `KvmEncRegion` referencing some memory assigned to the virtual machine. + pub fn new(data: u64, len: u64, attributes: u64) -> Self { + Self { + addr: data, + size: len, + attributes, + flags: 0, + } + } + + /// Register the encrypted memory region to a virtual machine + pub fn set_attributes( + &mut self, + vm_fd: &mut impl AsRawFd, + ) -> std::io::Result { + SET_MEMORY_ATTRIBUTES.ioctl(vm_fd, self) + } +} + /// A generic SEV command #[repr(C)] pub struct Command<'a, T: Id> { diff --git a/src/launch/linux/mod.rs b/src/launch/linux/mod.rs index d73938ac..b1dd2a57 100644 --- a/src/launch/linux/mod.rs +++ b/src/launch/linux/mod.rs @@ -8,3 +8,6 @@ pub(crate) mod sev; #[cfg(feature = "snp")] pub(crate) mod snp; + +#[cfg(all(feature = "sev", feature = "snp"))] +pub(crate) mod shared; diff --git a/src/launch/linux/shared.rs b/src/launch/linux/shared.rs new file mode 100644 index 00000000..2c60e208 --- /dev/null +++ b/src/launch/linux/shared.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! SEV and SEV-SNP shared types for interacting with the KVM SEV guest management API. + +/// Structure passed into KVM_SEV_INIT2 command. +#[derive(Default)] +#[repr(C, packed)] +pub struct Init2 { + /// Initial value of features field in VMSA. (Must be 0 for SEV) + vmsa_features: u64, + + /// Always set to 0 + flags: u32, + + /// Maximum guest GHCB version allowed. (Currently 0 for SEV and 1 for SEV-ES and SEV-SNP) + ghcb_version: u16, + + pad1: u16, + + pad2: [u32; 8], +} + +impl Init2 { + /// Default INIT2 values for SEV + pub fn init_default_sev() -> Self { + Self { + vmsa_features: 0, + flags: 0, + ghcb_version: 0, + pad1: Default::default(), + pad2: Default::default(), + } + } + + /// Default INIT2 values for SEV-ES + pub fn init_default_es() -> Self { + Self { + vmsa_features: 0x1, + flags: 0, + ghcb_version: 1, + pad1: Default::default(), + pad2: Default::default(), + } + } + + /// Default INIT2 values for SEV-SNP + pub fn init_default_snp() -> Self { + Self { + vmsa_features: 0, + flags: 0, + ghcb_version: 2, + pad1: Default::default(), + pad2: Default::default(), + } + } +} diff --git a/src/launch/linux/snp.rs b/src/launch/linux/snp.rs index 0b54fe82..492d7358 100644 --- a/src/launch/linux/snp.rs +++ b/src/launch/linux/snp.rs @@ -14,46 +14,31 @@ pub struct Init { flags: u64, } -/// Initialize the flow to launch a guest. #[repr(C)] -pub struct LaunchStart<'a> { +pub struct LaunchStart { /// Guest policy. See Table 7 of the AMD SEV-SNP Firmware /// specification for a description of the guest policy structure. policy: u64, - /// Userspace address of migration agent - ma_uaddr: u64, - - /// 1 if this guest is associated with a migration agent. Otherwise 0. - ma_en: u8, - - /// 1 if this launch flow is launching an IMI for the purpose of - /// guest-assisted migration. Otherwise 0. - imi_en: u8, - /// Hypervisor provided value to indicate guest OS visible workarounds. /// The format is hypervisor defined. gosvw: [u8; 16], - pad: [u8; 6], + flags: u16, - _phantom: PhantomData<&'a [u8]>, + pad0: [u8; 6], + + pad1: [u64; 4], } -impl From> for LaunchStart<'_> { +impl From for LaunchStart { fn from(start: Start) -> Self { Self { policy: start.policy.into(), - ma_uaddr: if let Some(addr) = start.ma_uaddr { - addr.as_ptr() as u64 - } else { - 0 - }, - ma_en: u8::from(start.ma_uaddr.is_some()), - imi_en: start.imi_en as _, gosvw: start.gosvw, - pad: [0u8; 6], - _phantom: PhantomData, + flags: 0, + pad0: [0u8; 6], + pad1: [0u64; 4], } } } @@ -62,32 +47,27 @@ impl From> for LaunchStart<'_> { #[repr(C)] pub struct LaunchUpdate<'a> { /// guest start frame number. - start_gfn: u64, + pub start_gfn: u64, /// Userspace address of the page needed to be encrypted. - uaddr: u64, + pub uaddr: u64, /// Length of the page needed to be encrypted: /// (end encryption uaddr = uaddr + len). - len: u32, - - /// Indicates that this page is part of the IMI of the guest. - imi_page: u8, + pub len: u64, /// Encoded page type. See Table 58 if the SNP Firmware specification. - page_type: u8, + pub page_type: u8, - /// VMPL permission mask for VMPL3. See Table 59 of the SNP Firmware - /// specification for the definition of the mask. - vmpl3_perms: u8, + pad0: u8, - /// VMPL permission mask for VMPL2. - vmpl2_perms: u8, + flags: u16, - /// VMPL permission mask for VMPL1. - vmpl1_perms: u8, + pad1: u32, - _phantom: PhantomData<&'a ()>, + pad2: [u64; 4], + + _phantom: PhantomData<&'a [u8]>, } impl From> for LaunchUpdate<'_> { @@ -96,11 +76,11 @@ impl From> for LaunchUpdate<'_> { start_gfn: update.start_gfn, uaddr: update.uaddr.as_ptr() as _, len: update.uaddr.len() as _, - imi_page: u8::from(update.imi_page), page_type: update.page_type as _, - vmpl3_perms: update.vmpl3_perms.bits(), - vmpl2_perms: update.vmpl2_perms.bits(), - vmpl1_perms: update.vmpl1_perms.bits(), + pad0: 0, + flags: 0, + pad1: 0, + pad2: [0u64; 4], _phantom: PhantomData, } } diff --git a/src/launch/sev.rs b/src/launch/sev.rs index d7800ada..3d911ec2 100644 --- a/src/launch/sev.rs +++ b/src/launch/sev.rs @@ -9,7 +9,7 @@ use crate::error::{Error::InvalidLen, Indeterminate}; #[cfg(target_os = "linux")] use crate::launch::linux::ioctl::*; #[cfg(target_os = "linux")] -use crate::launch::linux::sev::*; +use crate::launch::linux::{sev::*, shared::*}; use crate::*; use std::convert::TryFrom; @@ -56,8 +56,12 @@ impl Launcher { state: New, }; - let mut cmd = Command::from(&launcher.sev, &Init); - INIT.ioctl(&mut launcher.vm_fd, &mut cmd) + let init = Init2::init_default_sev(); + + let mut cmd = Command::from(&launcher.sev, &init); + + INIT2 + .ioctl(&mut launcher.vm_fd, &mut cmd) .map_err(|e| cmd.encapsulate(e))?; Ok(launcher) @@ -71,8 +75,10 @@ impl Launcher { state: New, }; - let mut cmd = Command::from(&launcher.sev, &EsInit); - ES_INIT + let init = Init2::init_default_es(); + + let mut cmd = Command::from(&launcher.sev, &init); + INIT2 .ioctl(&mut launcher.vm_fd, &mut cmd) .map_err(|e| cmd.encapsulate(e))?; diff --git a/src/launch/snp.rs b/src/launch/snp.rs index c89931de..64cde5fc 100644 --- a/src/launch/snp.rs +++ b/src/launch/snp.rs @@ -6,7 +6,7 @@ use crate::firmware::guest::GuestPolicy; #[cfg(target_os = "linux")] -use crate::launch::linux::{ioctl::*, snp::*}; +use crate::launch::linux::{ioctl::*, shared::*, snp::*}; use std::{io::Result, marker::PhantomData, os::unix::io::AsRawFd}; @@ -50,10 +50,11 @@ impl Launcher { state: PhantomData, }; - let init = Init::default(); + let init = Init2::init_default_snp(); let mut cmd = Command::from(&launcher.sev, &init); - SNP_INIT + + INIT2 .ioctl(&mut launcher.vm_fd, &mut cmd) .map_err(|e| cmd.encapsulate(e))?; @@ -62,8 +63,8 @@ impl Launcher { /// Initialize the flow to launch a guest. pub fn start(mut self, start: Start) -> Result> { - let mut launch_start = LaunchStart::from(start); - let mut cmd = Command::from_mut(&self.sev, &mut launch_start); + let launch_start = LaunchStart::from(start); + let mut cmd = Command::from(&self.sev, &launch_start); SNP_LAUNCH_START .ioctl(&mut self.vm_fd, &mut cmd) @@ -81,15 +82,45 @@ impl Launcher { impl Launcher { /// Encrypt guest SNP data. - pub fn update_data(&mut self, update: Update) -> Result<()> { - let launch_update_data = LaunchUpdate::from(update); - let mut cmd = Command::from(&self.sev, &launch_update_data); - - KvmEncRegion::new(update.uaddr).register(&mut self.vm_fd)?; - - SNP_LAUNCH_UPDATE - .ioctl(&mut self.vm_fd, &mut cmd) - .map_err(|e| cmd.encapsulate(e))?; + pub fn update_data(&mut self, mut update: Update, gpa: u64, gpa_len: u64) -> Result<()> { + loop { + let launch_update_data = LaunchUpdate::from(update); + let mut cmd = Command::from(&self.sev, &launch_update_data); + + // Register the encryption region + KvmEncRegion::new(update.uaddr).register(&mut self.vm_fd)?; + + // Set memory attributes to private + KvmSetMemoryAttributes::new(gpa, gpa_len, KVM_MEMORY_ATTRIBUTE_PRIVATE) + .set_attributes(&mut self.vm_fd)?; + + // Perform the SNP_LAUNCH_UPDATE ioctl call + match SNP_LAUNCH_UPDATE.ioctl(&mut self.vm_fd, &mut cmd) { + Ok(_) => { + // Check if the entire range has been processed + if launch_update_data.len == 0 { + break; + } + + // Update the `update` object with the remaining range + update.start_gfn = launch_update_data.start_gfn; + update.uaddr = unsafe { + std::slice::from_raw_parts( + launch_update_data.uaddr as *const u8, + launch_update_data.len as usize, + ) + }; + } + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + // Retry the operation if `-EAGAIN` is returned + continue; + } + Err(e) => { + // Handle other errors + return Err(cmd.encapsulate(e).into()); + } + } + } Ok(()) } @@ -109,37 +140,53 @@ impl Launcher { /// Encapsulates the various data needed to begin the launch process. #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] -pub struct Start<'a> { - /// The userspace address of the migration agent region to be encrypted. - pub(crate) ma_uaddr: Option<&'a [u8]>, - +pub struct Start { /// Describes a policy that the AMD Secure Processor will enforce. pub(crate) policy: GuestPolicy, - /// Indicates that this launch flow is launching an IMI for the purpose of guest-assisted migration. - pub(crate) imi_en: bool, - /// Hypervisor provided value to indicate guest OS visible workarounds.The format is hypervisor defined. pub(crate) gosvw: [u8; 16], + + /// Indicates that this launch flow is launching an IMI for the purpose of guest-assisted migration. + pub(crate) flags: u16, } -impl<'a> Start<'a> { +impl Start { /// Encapsulate all data needed for the SNP_LAUNCH_START ioctl. - pub fn new( - ma_uaddr: Option<&'a [u8]>, - policy: GuestPolicy, - imi_en: bool, - gosvw: [u8; 16], - ) -> Self { + pub fn new(policy: GuestPolicy, gosvw: [u8; 16]) -> Self { Self { - ma_uaddr, policy, - imi_en, gosvw, + flags: 0, } } } +/// Encoded page types for a launch update. See Table 58 of the SNP Firmware +/// specification for further details. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[repr(C)] +#[non_exhaustive] +pub enum PageType { + /// A normal data page. + Normal = 0x1, + + /// A VMSA page. + Vmsa = 0x2, + + /// A page full of zeroes. + Zero = 0x3, + + /// A page that is encrypted but not measured + Unmeasured = 0x4, + + /// A page for the firmware to store secrets for the guest. + Secrets = 0x5, + + /// A page for the hypervisor to provide CPUID function values. + Cpuid = 0x6, +} + /// Encapsulates the various data needed to begin the update process. #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct Update<'a> { @@ -149,39 +196,17 @@ pub struct Update<'a> { /// The userspace of address of the encrypted region. pub(crate) uaddr: &'a [u8], - /// Indicates that this page is part of the IMI of the guest. - pub(crate) imi_page: bool, - /// Encoded page type. pub(crate) page_type: PageType, - - /// VMPL3 permission mask. - pub(crate) vmpl3_perms: VmplPerms, - - /// VMPL2 permission mask. - pub(crate) vmpl2_perms: VmplPerms, - - /// VMPL1 permission mask. - pub(crate) vmpl1_perms: VmplPerms, } impl<'a> Update<'a> { /// Encapsulate all data needed for the SNP_LAUNCH_UPDATE ioctl. - pub fn new( - start_gfn: u64, - uaddr: &'a [u8], - imi_page: bool, - page_type: PageType, - perms: (VmplPerms, VmplPerms, VmplPerms), - ) -> Self { + pub fn new(start_gfn: u64, uaddr: &'a [u8], page_type: PageType) -> Self { Self { start_gfn, uaddr, - imi_page, page_type, - vmpl3_perms: perms.2, - vmpl2_perms: perms.1, - vmpl1_perms: perms.0, } } } @@ -204,31 +229,6 @@ bitflags! { } } -/// Encoded page types for a launch update. See Table 58 of the SNP Firmware -/// specification for further details. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[repr(C)] -#[non_exhaustive] -pub enum PageType { - /// A normal data page. - Normal = 0x1, - - /// A VMSA page. - Vmsa = 0x2, - - /// A page full of zeroes. - Zero = 0x3, - - /// A page that is encrypted but not measured - Unmeasured = 0x4, - - /// A page for the firmware to store secrets for the guest. - Secrets = 0x5, - - /// A page for the hypervisor to provide CPUID function values. - Cpuid = 0x6, -} - /// Encapsulates the data needed to complete a guest launch. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Finish<'a, 'b> { diff --git a/tests/launch.rs b/tests/launch.rs index 568f8089..2a43033b 100644 --- a/tests/launch.rs +++ b/tests/launch.rs @@ -31,6 +31,9 @@ const CODE: &[u8; 16] = &[ #[test] #[serial] fn sev() { + // KVM SEV type + const KVM_X86_SEV_VM: u64 = 2; + let mut sev = Firmware::open().unwrap(); let build = sev.platform_status().unwrap().build; let chain = cached_chain::get().expect( @@ -43,7 +46,9 @@ fn sev() { let start = session.start(chain).unwrap(); let kvm = Kvm::new().unwrap(); - let vm = kvm.create_vm().unwrap(); + + // Create VMft with SEV type + let vm = kvm.create_vm_with_type(KVM_X86_SEV_VM).unwrap(); // Allocate a 1kB page of memory for the address space of the VM. const MEM_SIZE: usize = 0x1000; diff --git a/tests/snp_launch.rs b/tests/snp_launch.rs index c39dc0cc..3c475df3 100644 --- a/tests/snp_launch.rs +++ b/tests/snp_launch.rs @@ -4,13 +4,16 @@ use std::slice::from_raw_parts_mut; #[cfg(all(feature = "snp", target_os = "linux"))] -use sev::firmware::host::Firmware; +use std::os::fd::RawFd; + +#[cfg(all(feature = "snp", target_os = "linux"))] +use sev::firmware::{guest::GuestPolicy, host::Firmware}; #[cfg(all(feature = "snp", target_os = "linux"))] use sev::launch::snp::*; #[cfg(all(feature = "snp", target_os = "linux"))] -use kvm_bindings::kvm_userspace_memory_region; +use kvm_bindings::{kvm_create_guest_memfd, kvm_userspace_memory_region2, KVM_MEM_GUEST_MEMFD}; #[cfg(all(feature = "snp", target_os = "linux"))] use kvm_ioctls::{Kvm, VcpuExit}; @@ -21,14 +24,16 @@ const CODE: &[u8; 4096] = &[ 0xf4; 4096 // hlt ]; +const KVM_X86_SNP_VM: u64 = 4; + #[cfg(all(feature = "snp", target_os = "linux"))] #[cfg_attr(not(has_sev), ignore)] #[test] fn snp() { - use sev::firmware::guest::GuestPolicy; - let kvm_fd = Kvm::new().unwrap(); - let vm_fd = kvm_fd.create_vm().unwrap(); + + // Create VM-fd with SEV-SNP type + let vm_fd = kvm_fd.create_vm_with_type(KVM_X86_SNP_VM).unwrap(); const MEM_ADDR: u64 = 0x1000; @@ -46,39 +51,51 @@ fn snp() { let userspace_addr = address_space as *const [u8] as *const u8 as u64; - let mem_region = kvm_userspace_memory_region { + // Create KVM guest_memfd struct + let gmem = kvm_create_guest_memfd { + size: 0x1000, + flags: 0, + reserved: [0; 6], + }; + + // Create KVM guest_memfd + let fd: RawFd = vm_fd.create_guest_memfd(gmem).unwrap(); + + // Create memory region + let mem_region = kvm_userspace_memory_region2 { slot: 0, - guest_phys_addr: MEM_ADDR, - memory_size: CODE.len() as _, + flags: KVM_MEM_GUEST_MEMFD, + guest_phys_addr: 0x1000_u64, + memory_size: 0x1000_u64, userspace_addr, - flags: 0, + guest_memfd_offset: 0, + guest_memfd: fd as u32, + pad1: 0, + pad2: [0; 14], }; unsafe { - vm_fd.set_user_memory_region(mem_region).unwrap(); - } + vm_fd.set_user_memory_region2(mem_region).unwrap(); + }; let sev = Firmware::open().unwrap(); let launcher = Launcher::new(vm_fd, sev).unwrap(); let mut policy = GuestPolicy(0); policy.set_smt_allowed(1); - let start = Start::new(None, policy, false, [0; 16]); + let start = Start::new(policy, [0; 16]); let mut launcher = launcher.start(start).unwrap(); - // If VMPL is not enabled, perms must be zero - let dp = VmplPerms::empty(); - let update = Update::new( mem_region.guest_phys_addr >> 12, address_space, - false, PageType::Normal, - (dp, dp, dp), ); - launcher.update_data(update).unwrap(); + launcher + .update_data(update, mem_region.guest_phys_addr, mem_region.memory_size) + .unwrap(); let finish = Finish::new(None, None, [0u8; 32]);