Skip to content

Commit

Permalink
Implement KVM_TDX_INIT_VM and KVM_TDX_INIT_VCPU ioctl
Browse files Browse the repository at this point in the history
Implement the KVM_TDX_INIT_VM and KVM_TDX_INIT_VCPU ioctls.

Additionally move some device agnostic types into a shared `src/linux/`
folder.

Exposes the vm filedescriptor in the `TdxVm` type.

Implement a conversion from `i32` to `TdxError`

Signed-off-by: Jake Correnti <[email protected]>
  • Loading branch information
jakecorrenti committed Mar 21, 2024
1 parent 313f162 commit 330315f
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 65 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ rust-version = "1.71"

[dependencies]
bitflags = "2.4.2"
kvm-bindings = "0.7.0"
kvm-ioctls = "0.16.0"
vmm-sys-util = "0.12.1"
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

pub mod vcpu;
pub mod vm;

#[cfg(target_os = "linux")]
pub mod linux;
63 changes: 63 additions & 0 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0

/// Trust Domain eXtensions sub-ioctl() commands
#[repr(u32)]
pub enum CmdId {
GetCapabilities = 0,
InitVm = 1,
InitVcpu = 2,
}

/// Contains information for the sub-ioctl() command to be run. This is
/// equivalent to `struct kvm_tdx_cmd` in the kernel.
#[derive(Default)]
#[repr(C)]
pub struct Cmd {
/// TDX command identifier
pub id: u32,

/// Flags for sub-command. If sub-command doesn't use it, set to zero.
pub flags: u32,

/// A u64 representing a generic pointer to the respective ioctl input.
/// This data is read differently according to the TDX ioctl identifier.
pub data: u64,

/// Auxiliary error code. The sub-command may return TDX SEAMCALL status
/// code in addition to -Exxx.
pub error: u64,

/// Reserved.
pub _unused: u64,
}

#[derive(Debug)]
pub struct TdxError {
pub code: i32,
pub message: String,
}

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),
},
}
}
}
2 changes: 2 additions & 0 deletions src/vcpu/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
// SPDX-License-Identifier: Apache-2.0

pub mod ioctl;
35 changes: 35 additions & 0 deletions src/vcpu/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
// SPDX-License-Identifier: Apache-2.0

mod linux;

use crate::linux::{Cmd, CmdId, TdxError};
use kvm_bindings::*;
use vmm_sys_util::*;

vmm_sys_util::ioctl_iowr_nr!(KVM_MEMORY_ENCRYPT_OP, KVMIO, 0xba, std::os::raw::c_ulong);

pub struct TdxVcpu {
pub fd: kvm_ioctls::VcpuFd,
}

impl TdxVcpu {
pub fn new(vm: &crate::vm::TdxVm, id: u64) -> Result<TdxVcpu, TdxError> {
let vcpufd = vm.fd.create_vcpu(id)?;
Ok(Self { fd: vcpufd })
}

/// TDX specific VCPU initialization using a TDVF HOB address
pub fn init_vcpu(&self, hob_addr: u64) -> Result<(), TdxError> {
let mut cmd = Cmd {
id: CmdId::InitVcpu as u32,
flags: 0,
data: hob_addr as *const u64 as _,
error: 0,
_unused: 0,
};
let ret = unsafe { ioctl::ioctl_with_mut_ptr(&self.fd, KVM_MEMORY_ENCRYPT_OP(), &mut cmd) };
if ret < 0 {
return Err(TdxError::from(ret));
}
Ok(())
}
}
44 changes: 14 additions & 30 deletions src/vm/linux/ioctl.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::vm::linux::types::Capabilities;

/// Trust Domain eXtensions sub-ioctl() commands
#[repr(u32)]
pub enum CmdId {
GetCapabilities = 0,
}

/// Contains information for the sub-ioctl() command to be run. This is
/// equivalent to `struct kvm_tdx_cmd` in the kernel.
#[derive(Default)]
#[repr(C)]
pub struct Cmd {
/// TDX command identifier
pub id: u32,

/// Flags for sub-command. If sub-command doesn't use it, set to zero.
pub flags: u32,

/// A u64 representing a generic pointer to the respective ioctl input.
/// This data is read differently according to the TDX ioctl identifier.
pub data: u64,

/// Auxiliary error code. The sub-command may return TDX SEAMCALL status
/// code in addition to -Exxx.
pub error: u64,

/// Reserved.
pub _unused: u64,
}
use crate::linux::{Cmd, CmdId};
use crate::vm::linux::types::{Capabilities, InitVm};

impl From<&Capabilities> for Cmd {
fn from(caps: &Capabilities) -> Self {
Expand All @@ -42,3 +14,15 @@ impl From<&Capabilities> for Cmd {
}
}
}

impl From<&InitVm> for Cmd {
fn from(init_vm: &InitVm) -> Self {
Self {
id: CmdId::InitVm as u32,
flags: 0,
data: init_vm as *const InitVm as _,
error: 0,
_unused: 0,
}
}
}
54 changes: 54 additions & 0 deletions src/vm/linux/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,57 @@ impl Default for Capabilities {
}
}
}

/// TDX specific VM initialization information
#[derive(Debug)]
#[repr(C)]
pub struct InitVm {
/// attributes specifies various guest TD attributes
pub attributes: u64,

/// mrconfigid is a software-defined ID for non-owner-defined configuration of the guest TD
/// (runtime or OS configuration)
pub mrconfigid: [u64; 6],

/// mrowner is a software-defined ID for the guest TD’s owner
pub mrowner: [u64; 6],

/// mrownerconfig is a software-defined ID for owner-defined configuration of the guest TD
/// (specific to the workload)
pub mrownerconfig: [u64; 6],

/// reserved for future extensibility
reserved: [u64; 1004],

/// direct configuration of CPUID leaves/subleaves virtualization
pub cpuid_nent: u32,
cpuid_padding: u32,
pub cpuid_entries: [kvm_bindings::kvm_cpuid_entry2; 256],
}

impl InitVm {
pub fn new(cpuid_entries: &Vec<kvm_bindings::kvm_cpuid_entry2>) -> Self {
Self {
cpuid_nent: cpuid_entries.len() as u32,
cpuid_entries: cpuid_entries.as_slice().try_into().unwrap(),
..Default::default()
}
}
}

impl Default for InitVm {
fn default() -> Self {
Self {
// set the SEPT_VE_DISABLE bit by default to prevent an Extended Page Table
// (EPT) violation to #VE caused by guest TD access of PENDING pages
attributes: crate::vm::AttributesFlags::SEPT_VE_DISABLE.bits(),
mrconfigid: [0; 6],
mrowner: [0; 6],
mrownerconfig: [0; 6],
reserved: [0; 1004],
cpuid_nent: 0,
cpuid_padding: 0,
cpuid_entries: [Default::default(); 256],
}
}
}
116 changes: 83 additions & 33 deletions src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@

mod linux;

use crate::vm::linux::{
ioctl::Cmd,
types::{Capabilities, CpuidConfig},
};
use crate::linux::{Cmd, TdxError};
use crate::vm::linux::types::{Capabilities, CpuidConfig, InitVm};
use bitflags::bitflags;
use kvm_ioctls::{Kvm, VmFd};
use std::arch::x86_64;

// Defined in linux/arch/x86/include/uapi/asm/kvm.h
const KVM_X86_TDX_VM: u64 = 2;

/// Handle to the TDX VM file descriptor
pub struct TdxVm(VmFd);
pub struct TdxVm {
pub fd: VmFd,
}

impl TdxVm {
/// Create a new TDX VM with KVM
pub fn new(kvm_fd: Kvm) -> Result<Self, TdxError> {
pub fn new(kvm_fd: &Kvm) -> Result<Self, TdxError> {
let vm_fd = kvm_fd.create_vm_with_type(KVM_X86_TDX_VM)?;
Ok(Self(vm_fd))
Ok(Self { fd: vm_fd })
}

/// Retrieve information about the Intel TDX module
Expand All @@ -28,7 +29,7 @@ impl TdxVm {
let mut cmd: Cmd = Cmd::from(&caps);

unsafe {
if let Err(e) = self.0.encrypt_op(&mut cmd) {
if let Err(e) = self.fd.encrypt_op(&mut cmd) {
return Err(TdxError::from(e));
}
}
Expand All @@ -46,6 +47,80 @@ impl TdxVm {
cpuid_configs: Vec::from(caps.cpuid_configs),
})
}

/// Do additional VM initialization that is specific to Intel TDX
pub fn init_vm(&self, kvm_fd: &Kvm, caps: &TdxCapabilities) -> Result<(), TdxError> {
let cpuid = kvm_fd
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap();
let mut cpuid_entries: Vec<kvm_bindings::kvm_cpuid_entry2> =
cpuid.as_slice().iter().map(|e| (*e).into()).collect();

// resize to 256 entries to make sure that InitVm is 8KB
cpuid_entries.resize(256, kvm_bindings::kvm_cpuid_entry2::default());

// hex for Ob1100000001011111111 based on the XSAVE state-components architecture
let xcr0_mask = 0x602ff;
// hex for 0b11111110100000000 based on the XSAVE state-components architecture
let xss_mask = 0x1FD00;

let xfam_fixed0 = caps.xfam.fixed0.bits();
let xfam_fixed1 = caps.xfam.fixed1.bits();

// patch cpuid
for entry in cpuid_entries.as_mut_slice() {
// get the configurable cpuid bits (can be set to 0 or 1) reported by TDX Module from
// TdxCapabilities
for cpuid_config in &caps.cpuid_configs {
// 0xffffffff means the cpuid leaf has no subleaf
if cpuid_config.leaf == entry.function
&& (cpuid_config.sub_leaf == 0xffffffff || cpuid_config.sub_leaf == entry.index)
{
entry.eax |= cpuid_config.eax;
entry.ebx |= cpuid_config.ebx;
entry.ecx |= cpuid_config.ecx;
entry.edx |= cpuid_config.edx;
}
}

// mandatory patches for TDX based on XFAM values reported by TdxCapabilities
match entry.index {
// XSAVE features and state-components
0xD => {
if entry.index == 0 {
// XSAVE XCR0 LO
entry.eax &= (xfam_fixed0 as u32) & (xcr0_mask as u32);
entry.eax |= (xfam_fixed1 as u32) & (xcr0_mask as u32);
// XSAVE XCR0 HI
entry.edx &= ((xfam_fixed0 & xcr0_mask) >> 32) as u32;
entry.edx |= ((xfam_fixed1 & xcr0_mask) >> 32) as u32;
} else if entry.index == 1 {
// XSAVE XCR0 LO
entry.ecx &= (xfam_fixed0 as u32) & (xss_mask as u32);
entry.ecx |= (xfam_fixed1 as u32) & (xss_mask as u32);
// XSAVE XCR0 HI
entry.edx &= ((xfam_fixed0 & xss_mask) >> 32) as u32;
entry.edx |= ((xfam_fixed1 & xss_mask) >> 32) as u32;
}
}
0x8000_0008 => {
// host physical address bits supported
let phys_bits = unsafe { x86_64::__cpuid(0x8000_0008).eax } & 0xff;
entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff);
}
_ => (),
}
}

let mut cmd = Cmd::from(&InitVm::new(&cpuid_entries));
unsafe {
if let Err(e) = self.fd.encrypt_op(&mut cmd) {
return Err(TdxError::from(e));
}
}

Ok(())
}
}

bitflags! {
Expand Down Expand Up @@ -193,28 +268,3 @@ pub struct TdxCapabilities {

pub cpuid_configs: Vec<CpuidConfig>,
}

#[derive(Debug)]
pub struct TdxError {
pub code: i32,
pub message: String,
}

impl From<kvm_ioctls::Error> for TdxError {
fn from(kvm_err: kvm_ioctls::Error) -> Self {
match kvm_err.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: kvm_err.errno(),
message: format!("errno: {}", kvm_err.errno()),
},
}
}
}
Loading

0 comments on commit 330315f

Please sign in to comment.