Skip to content
Merged
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
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ rust-version = "1.86"

[dependencies]
bitflags = "2.4.2"
kvm-bindings = { git = "https://github.com/virtee/kvm-bindings.git", branch = "main", features = ["fam-wrappers"] }
kvm-ioctls = { git = "https://github.com/virtee/kvm-ioctls.git", branch = "main" }
iocuddle = "0.1.1"
kvm-bindings = { version = "0.12.0", features = ["fam-wrappers"] }
kvm-ioctls = "0.22.0"
libc = "0.2.155"
uuid = "1.8.0"
vmm-sys-util = "0.12.1"
43 changes: 19 additions & 24 deletions src/launch/linux.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0

use std::io::Error as IoError;
use std::marker::PhantomData;

pub const NR_CPUID_CONFIGS: usize = 24;
Expand Down Expand Up @@ -49,32 +50,26 @@ impl<'a, T: 'a> Cmd<'a, T> {
}

#[derive(Debug)]
pub struct TdxError {
pub code: i32,
pub message: String,
pub enum Error {
GetCapabilities(IoError),
InitVm(IoError),
InitVcpu(IoError),
InitMemRegion(IoError),
Finalize(IoError),
MissingVcpuFds,
}

impl From<kvm_ioctls::Error> for TdxError {
fn from(kvm_err: kvm_ioctls::Error) -> Self {
TdxError::from(kvm_err.errno())
}
}

impl From<i32> for TdxError {
fn from(errno: i32) -> Self {
match errno {
7 => TdxError {
code: 7,
message: String::from("Invalid value for NR_CPUID_CONFIGS"),
},
25 => TdxError {
code: 25,
message: String::from("Inappropriate ioctl for device. Ensure the proper VM type is being used for the ioctl"),
},
_ => TdxError {
code: errno,
message: format!("errno: {}", errno),
},
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::GetCapabilities(io_err) => {
write!(f, "KVM_TDX_CAPABILITIES failed: {io_err}")
}
Error::InitVm(io_err) => write!(f, "KVM_TDX_INIT_VM failed: {io_err}"),
Error::InitVcpu(io_err) => write!(f, "KVM_TDX_INIT_VCPU failed: {io_err}"),
Error::InitMemRegion(io_err) => write!(f, "KVM_TDX_INIT_MEM_REGION failed: {io_err}"),
Error::Finalize(io_err) => write!(f, "KVM_TDX_FINALIZE failed: {io_err}"),
Error::MissingVcpuFds => write!(f, "Launcher contains zero vCPU file descriptors"),
}
}
}
Expand Down
205 changes: 113 additions & 92 deletions src/launch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ mod bindings;
mod linux;

use bindings::{kvm_tdx_capabilities, kvm_tdx_init_mem_region, kvm_tdx_init_vm};
use kvm_bindings::kvm_enable_cap;
use linux::{Cmd, CmdId, TdxError, NR_CPUID_CONFIGS};
use linux::{Cmd, CmdId, Error, NR_CPUID_CONFIGS};

use bitflags::bitflags;
use kvm_ioctls::VmFd;
use iocuddle::*;

use std::os::unix::io::RawFd;

const KVM: Group = Group::new(0xAE);
const ENC_OP: Ioctl<WriteRead, &libc::c_ulong> = unsafe { KVM.write_read(0xBA) };
const GET_CAPABILITIES: Ioctl<WriteRead, &Cmd<kvm_tdx_capabilities>> = unsafe { ENC_OP.lie() };
const INIT_VM: Ioctl<WriteRead, &Cmd<kvm_tdx_init_vm>> = unsafe { ENC_OP.lie() };
const INIT_VCPU: Ioctl<WriteRead, &Cmd<u64>> = unsafe { ENC_OP.lie() };
const INIT_MEM_REGION: Ioctl<WriteRead, &Cmd<kvm_tdx_init_mem_region>> = unsafe { ENC_OP.lie() };
const FINALIZE: Ioctl<WriteRead, &Cmd<u64>> = unsafe { ENC_OP.lie() };

// Defined in linux/arch/x86/include/uapi/asm/kvm.h
pub const KVM_X86_TDX_VM: u64 = 5;
Expand Down Expand Up @@ -42,24 +51,56 @@ pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
vec_with_size_in_bytes(vec_size_bytes)
}

/// Handle to the TDX VM file descriptor
pub struct TdxVm {}
/// Represents the memory region to be initialized by KVM_TDX_INIT_MEM_REGION
pub struct MemRegion {
/// starting guest address of private memory
pub gpa: u64,

impl TdxVm {
/// Create a new TDX VM with KVM
pub fn new(vm_fd: &VmFd) -> Result<Self, TdxError> {
let mut cap: kvm_enable_cap = kvm_enable_cap {
cap: kvm_bindings::KVM_CAP_X2APIC_API,
..Default::default()
};
cap.args[0] = (1 << 0) | (1 << 1);
vm_fd.enable_cap(&cap).unwrap();
/// number of pages to initialize
pub nr_pages: u64,

/// memory attributes of region
pub attributes: u32,

/// address of userspace provided data
pub source_addr: u64,
}

impl MemRegion {
pub fn new(gpa: u64, nr_pages: u64, attributes: u32, source_addr: u64) -> Self {
Self {
gpa,
nr_pages,
attributes,
source_addr,
}
}
}

pub type Result<T> = std::result::Result<T, Error>;

/// Launcher facilitates the correct execution of the TDX command
#[derive(Clone, Default)]
pub struct Launcher {
/// Raw FD associated with the KVM VM fd
vm_fd: RawFd,

/// Collection of Raw FDs associated with the vCPUs created with KVM
vcpu_fds: Vec<RawFd>,
}

Ok(Self {})
impl Launcher {
/// Initialize a new Launcher
pub fn new(vm_fd: RawFd) -> Self {
Self {
vm_fd,
vcpu_fds: Vec::new(),
}
}

/// Retrieve information about the Intel TDX module
pub fn get_capabilities(&self, fd: &VmFd) -> Result<TdxCapabilities, TdxError> {
/// Retrieve the TDX capabilities that KVM supports with the TDX module loaded
/// in the system.
pub fn get_capabilities(&mut self) -> Result<TdxCapabilities> {
let mut caps = kvm_tdx_capabilities::default();

let mut defaults = Vec::with_capacity(NR_CPUID_CONFIGS);
Expand All @@ -83,9 +124,9 @@ impl TdxVm {
let mut cmd: Cmd<kvm_tdx_capabilities> =
Cmd::from(CmdId::GetCapabilities, &cpuid_entries[0]);

unsafe {
fd.encrypt_op(&mut cmd)?;
}
GET_CAPABILITIES
.ioctl(&mut self.vm_fd, &mut cmd)
.map_err(Error::GetCapabilities)?;

Ok(TdxCapabilities {
attributes: AttributesFlags::from_bits_truncate(cpuid_entries[0].supported_attrs),
Expand All @@ -100,13 +141,11 @@ impl TdxVm {
})
}

/// Do additional VM initialization that is specific to Intel TDX
pub fn init_vm(
&self,
fd: &VmFd,
caps: &TdxCapabilities,
cpuid: kvm_bindings::CpuId,
) -> Result<(), TdxError> {
/// Perform TDX specific VM initialization.
///
/// Note: this must be called after after calling `KVM_CREATE_VM` and before
/// creating any vCPUs.
pub fn init_vm(&mut self, caps: &TdxCapabilities, cpuid: kvm_bindings::CpuId) -> Result<()> {
let mut defaults: Vec<kvm_bindings::kvm_cpuid_entry2> = cpuid.as_slice().to_vec();
defaults.resize(
kvm_bindings::KVM_MAX_CPUID_ENTRIES,
Expand All @@ -131,9 +170,9 @@ impl TdxVm {
Self::tdx_filter_cpuid(&mut entries[0].cpuid, &caps.cpuid_configs);

let mut cmd: Cmd<kvm_tdx_init_vm> = Cmd::from(CmdId::InitVm, &entries[0]);
unsafe {
fd.encrypt_op(&mut cmd)?;
}
INIT_VM
.ioctl(&mut self.vm_fd, &mut cmd)
.map_err(Error::InitVm)?;

Ok(())
}
Expand Down Expand Up @@ -183,12 +222,53 @@ impl TdxVm {
}

/// Complete measurement of the initial TD contents and mark it ready to run
pub fn finalize(&self, fd: &VmFd) -> Result<(), TdxError> {
pub fn finalize(&mut self) -> Result<()> {
let mut cmd: Cmd<u64> = Cmd::from(CmdId::FinalizeVm, &0);
unsafe {
fd.encrypt_op(&mut cmd)?;
FINALIZE
.ioctl(&mut self.vm_fd, &mut cmd)
.map_err(Error::Finalize)?;

Ok(())
}

/// Add RawFd associated with a vCPU to the Launcher
pub fn add_vcpu_fd(&mut self, fd: RawFd) {
self.vcpu_fds.push(fd);
}

/// Perform TDX specific vCPU initialization for each vCPU fd provided to
/// the Launcher
pub fn init_vcpus(&mut self, hob_address: u64) -> Result<()> {
let mut cmd: Cmd<u64> = Cmd::from(CmdId::InitVcpu, &hob_address);
for fd in self.vcpu_fds.iter_mut() {
INIT_VCPU.ioctl(fd, &mut cmd).map_err(Error::InitVcpu)?;
}
Ok(())
}

/// Initialize @nr_pages TDX guest private memory starting from @gpa with
/// userspace provided data from @source_addr
pub fn init_mem_region(&mut self, region: MemRegion) -> Result<()> {
if self.vcpu_fds.is_empty() {
return Err(Error::MissingVcpuFds);
}

const TDVF_SECTION_ATTRIBUTES_MR_EXTEND: u32 = 1u32 << 0;
let mem_region = kvm_tdx_init_mem_region {
source_addr: region.source_addr,
gpa: region.gpa,
nr_pages: region.nr_pages,
};

let mut cmd: Cmd<kvm_tdx_init_mem_region> = Cmd::from(CmdId::InitMemRegion, &mem_region);

// determines if we also extend the measurement
cmd.flags = (region.attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND > 0) as u32;

INIT_MEM_REGION
.ioctl(&mut self.vcpu_fds[0], &mut cmd)
.map_err(Error::InitMemRegion)?;

Ok(())
}
}
Expand Down Expand Up @@ -319,62 +399,3 @@ pub struct TdxCapabilities {
pub xfam: XFAMFlags,
pub cpuid_configs: Vec<kvm_bindings::kvm_cpuid_entry2>,
}

/// Manually create the wrapper for KVM_MEMORY_ENCRYPT_OP since `kvm_ioctls` doesn't
/// support `.encrypt_op` for vcpu fds
use vmm_sys_util::*;
ioctl_iowr_nr!(
KVM_MEMORY_ENCRYPT_OP,
kvm_bindings::KVMIO,
0xba,
std::os::raw::c_ulong
);

pub struct TdxVcpu {}

impl TdxVcpu {
pub fn init(fd: &kvm_ioctls::VcpuFd, hob_address: u64) -> Result<(), TdxError> {
let mut cmd: Cmd<u64> = Cmd::from(CmdId::InitVcpu, &hob_address);
let ret = unsafe { ioctl::ioctl_with_mut_ptr(fd, KVM_MEMORY_ENCRYPT_OP(), &mut cmd) };
if ret < 0 {
// can't return `ret` because it will just return -1 and not give the error
// code. `cmd.error` will also just be 0.
return Err(TdxError::from(errno::Error::last()));
}
Ok(())
}

/// Encrypt a memory continuous region
pub fn init_mem_region(
fd: &kvm_ioctls::VcpuFd,
gpa: u64,
nr_pages: u64,
attributes: u32,
source_addr: u64,
) -> Result<(), TdxError> {
const TDVF_SECTION_ATTRIBUTES_MR_EXTEND: u32 = 1u32 << 0;
let mem_region = kvm_tdx_init_mem_region {
source_addr,
gpa,
nr_pages,
};

let mut cmd: Cmd<kvm_tdx_init_mem_region> = Cmd::from(CmdId::InitMemRegion, &mem_region);

// determines if we also extend the measurement
cmd.flags = if attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND > 0 {
1
} else {
0
};

let ret = unsafe { ioctl::ioctl_with_mut_ptr(fd, KVM_MEMORY_ENCRYPT_OP(), &mut cmd) };
if ret < 0 {
// can't return `ret` because it will just return -1 and not give the error
// code. `cmd.error` will also just be 0.
return Err(TdxError::from(errno::Error::last()));
}

Ok(())
}
}
Loading