diff --git a/Cargo.lock b/Cargo.lock index ce1fa111b..2fc1f5f07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,6 +649,7 @@ dependencies = [ "jinux-framebuffer", "jinux-std", "jinux-time", + "tdx-guest", "x86_64", ] @@ -690,6 +691,7 @@ dependencies = [ "multiboot2", "pod", "spin 0.9.8", + "tdx-guest", "trapframe", "volatile", "x86", @@ -792,6 +794,7 @@ dependencies = [ "ringbuf", "smoltcp", "spin 0.9.8", + "tdx-guest", "time", "typeflags", "typeflags-util", @@ -1295,6 +1298,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tdx-guest" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "raw-cpuid", + "x86_64", +] + [[package]] name = "thiserror" version = "1.0.44" diff --git a/Cargo.toml b/Cargo.toml index d78f71130..5afcdc0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ path = "kernel/main.rs" jinux-frame = { path = "framework/jinux-frame" } jinux-std = { path = "services/libs/jinux-std" } component = { path = "services/libs/comp-sys/component" } +tdx-guest = { path = "framework/libs/tdx-guest", optional = true } [dev-dependencies] x86_64 = "0.14.2" @@ -38,3 +39,6 @@ members = [ ] exclude = ["services/libs/comp-sys/controlled", "services/libs/comp-sys/cargo-component"] + +[features] +intel_tdx = ["dep:tdx-guest", "jinux-frame/intel_tdx", "jinux-std/intel_tdx"] diff --git a/Makefile b/Makefile index 83c70b9e4..cf2ae2e70 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,10 @@ build: @make --no-print-directory -C regression @cargo kbuild +build_td: + @make --no-print-directory -C regression + @cargo kbuild --features intel_tdx + tools: @cd services/libs/comp-sys && cargo install --path cargo-component diff --git a/framework/jinux-frame/Cargo.toml b/framework/jinux-frame/Cargo.toml index 699c0fd2f..3ebd7069d 100644 --- a/framework/jinux-frame/Cargo.toml +++ b/framework/jinux-frame/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4" lazy_static = { version = "1.0", features = ["spin_no_std"] } trapframe = { git = "https://github.com/sdww0/trapframe-rs", rev = "e886763" } inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods-macro", rev = "98f7e3e" } +tdx-guest = { path = "../libs/tdx-guest", optional = true } [target.x86_64-custom.dependencies] x86_64 = "0.14.2" @@ -27,3 +28,4 @@ aml = "0.16.3" multiboot2 = "0.16.0" [features] +intel_tdx = ["dep:tdx-guest"] diff --git a/framework/libs/tdx-guest/.gitignore b/framework/libs/tdx-guest/.gitignore new file mode 100644 index 000000000..4fffb2f89 --- /dev/null +++ b/framework/libs/tdx-guest/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/framework/libs/tdx-guest/Cargo.toml b/framework/libs/tdx-guest/Cargo.toml new file mode 100644 index 000000000..75edd653f --- /dev/null +++ b/framework/libs/tdx-guest/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tdx-guest" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +x86_64 = "0.14.10" +bitflags = "1.3" +raw-cpuid = "10" +lazy_static = "1.4.0" + diff --git a/framework/libs/tdx-guest/src/asm/mod.rs b/framework/libs/tdx-guest/src/asm/mod.rs new file mode 100644 index 000000000..1eaf81a83 --- /dev/null +++ b/framework/libs/tdx-guest/src/asm/mod.rs @@ -0,0 +1,11 @@ +use crate::{tdcall::TdcallArgs, tdvmcall::TdVmcallArgs}; +use core::arch::global_asm; + +global_asm!(include_str!("tdcall.asm")); +global_asm!(include_str!("tdvmcall.asm")); + +// TODO: Use sysv64 +extern "win64" { + pub(crate) fn asm_td_call(args: *mut TdcallArgs) -> u64; + pub(crate) fn asm_td_vmcall(args: *mut TdVmcallArgs) -> u64; +} diff --git a/framework/libs/tdx-guest/src/asm/tdcall.asm b/framework/libs/tdx-guest/src/asm/tdcall.asm new file mode 100644 index 000000000..2d0d54e25 --- /dev/null +++ b/framework/libs/tdx-guest/src/asm/tdcall.asm @@ -0,0 +1,78 @@ +.section .text + +# Arguments offsets in TdVmcallArgs struct +.equ TDCALL_ARG_RAX, 0x0 +.equ TDCALL_ARG_RCX, 0x8 +.equ TDCALL_ARG_RDX, 0x10 +.equ TDCALL_ARG_R8, 0x18 +.equ TDCALL_ARG_R9, 0x20 +.equ TDCALL_ARG_R10, 0x28 +.equ TDCALL_ARG_R11, 0x30 +.equ TDCALL_ARG_R12, 0x38 +.equ TDCALL_ARG_R13, 0x40 + +# asm_td_call -> u64 ( +# args: *mut TdcallArgs, //rcx +# ) +.global asm_td_call +asm_td_call: + endbr64 + # Save the registers accroding to MS x64 calling convention + push rbp + mov rbp, rsp + push r15 + push r14 + push r13 + push r12 + push rbx + push rsi + push rdi + + # Use RDI to save RCX value + mov rdi, rcx + + # Test if input pointer is valid + test rdi, rdi + jz td_call_exit + + # Copy the input operands from memory to registers + mov rax, [rdi + TDCALL_ARG_RAX] + mov rcx, [rdi + TDCALL_ARG_RCX] + mov rdx, [rdi + TDCALL_ARG_RDX] + mov r8, [rdi + TDCALL_ARG_R8] + mov r9, [rdi + TDCALL_ARG_R9] + mov r10, [rdi + TDCALL_ARG_R10] + mov r11, [rdi + TDCALL_ARG_R11] + mov r12, [rdi + TDCALL_ARG_R12] + mov r13, [rdi + TDCALL_ARG_R13] + + # tdcall + .byte 0x66,0x0f,0x01,0xcc + + # Exit if tdcall reports failure. + test rax, rax + jnz td_call_exit + + # Copy the output operands from registers to the struct + mov [rdi + TDCALL_ARG_RAX], rax + mov [rdi + TDCALL_ARG_RCX], rcx + mov [rdi + TDCALL_ARG_RDX], rdx + mov [rdi + TDCALL_ARG_R8], r8 + mov [rdi + TDCALL_ARG_R9], r9 + mov [rdi + TDCALL_ARG_R10], r10 + mov [rdi + TDCALL_ARG_R11], r11 + mov [rdi + TDCALL_ARG_R12], r12 + mov [rdi + TDCALL_ARG_R13], r13 + +td_call_exit: + # Pop out saved registers from stack + pop rdi + pop rsi + pop rbx + pop r12 + pop r13 + pop r14 + pop r15 + pop rbp + + ret diff --git a/framework/libs/tdx-guest/src/asm/tdvmcall.asm b/framework/libs/tdx-guest/src/asm/tdvmcall.asm new file mode 100644 index 000000000..dba18d072 --- /dev/null +++ b/framework/libs/tdx-guest/src/asm/tdvmcall.asm @@ -0,0 +1,96 @@ +.section .text + +# Mask used to control which part of the guest TD GPR and XMM +# state is exposed to the VMM. A bit value of 1 indicates the +# corresponding register is passed to VMM. Refer to TDX Module +# ABI specification section TDG.VP.VMCALL for detail. +# Here we expose R10 - R15 to VMM in td_vm_call() +.equ TDVMCALL_EXPOSE_REGS_MASK, 0xfc00 + +# TDG.VP.VMCALL leaf number +.equ TDVMCALL, 0 + +# Arguments offsets in TdVmcallArgs struct +.equ VMCALL_ARG_R10, 0x0 +.equ VMCALL_ARG_R11, 0x8 +.equ VMCALL_ARG_R12, 0x10 +.equ VMCALL_ARG_R13, 0x18 +.equ VMCALL_ARG_R14, 0x20 +.equ VMCALL_ARG_R15, 0x28 + +# asm_td_vmcall -> u64 ( +# args: *mut TdVmcallArgs, +# ) +.global asm_td_vmcall +asm_td_vmcall: + endbr64 + # Save the registers accroding to MS x64 calling convention + push rbp + mov rbp, rsp + push r15 + push r14 + push r13 + push r12 + push rbx + push rsi + push rdi + + # Use RDI to save RCX value + mov rdi, rcx + + # Test if input pointer is valid + test rdi, rdi + jz vmcall_exit + + # Copy the input operands from memory to registers + mov r10, [rdi + VMCALL_ARG_R10] + mov r11, [rdi + VMCALL_ARG_R11] + mov r12, [rdi + VMCALL_ARG_R12] + mov r13, [rdi + VMCALL_ARG_R13] + mov r14, [rdi + VMCALL_ARG_R14] + mov r15, [rdi + VMCALL_ARG_R15] + + # Set TDCALL leaf number + mov rax, TDVMCALL + + # Set exposed register mask + mov ecx, TDVMCALL_EXPOSE_REGS_MASK + + # TDCALL + .byte 0x66,0x0f,0x01,0xcc + + # RAX should always be zero for TDVMCALL, panic if it is not. + test rax, rax + jnz vmcall_panic + + # Copy the output operands from registers to the struct + mov [rdi + VMCALL_ARG_R10], r10 + mov [rdi + VMCALL_ARG_R11], r11 + mov [rdi + VMCALL_ARG_R12], r12 + mov [rdi + VMCALL_ARG_R13], r13 + mov [rdi + VMCALL_ARG_R14], r14 + mov [rdi + VMCALL_ARG_R15], r15 + + mov rax, r10 + +vmcall_exit: + # Clean the registers that are exposed to VMM to + # protect against speculative attack, others will + # be restored to the values saved in stack + xor r10, r10 + xor r11, r11 + + # Pop out saved registers from stack + pop rdi + pop rsi + pop rbx + pop r12 + pop r13 + pop r14 + pop r15 + pop rbp + + ret + +vmcall_panic: + ud2 diff --git a/framework/libs/tdx-guest/src/lib.rs b/framework/libs/tdx-guest/src/lib.rs new file mode 100644 index 000000000..a38128d3a --- /dev/null +++ b/framework/libs/tdx-guest/src/lib.rs @@ -0,0 +1,81 @@ +#![no_std] +#![allow(dead_code)] +#![allow(unused_variables)] + +extern crate alloc; + +pub mod asm; +pub mod tdcall; +pub mod tdvmcall; + +pub use self::tdcall::{deadloop, tdg_vp_veinfo_get, TdxVirtualExceptionType}; +pub use self::tdvmcall::print; + +use alloc::string::{String, ToString}; +use raw_cpuid::{native_cpuid::cpuid_count, CpuIdResult}; +use tdcall::{tdg_vp_vmcall, InitError, TdgVeInfo, TdgVpInfo}; + +const TDX_CPUID_LEAF_ID: u64 = 0x21; + +pub fn tdx_early_init() -> Result { + match is_tdx_guest() { + Ok(_) => Ok(tdcall::tdg_vp_info()?), + Err(err) => Err(err), + } +} + +fn is_tdx_guest() -> Result<(), InitError> { + let cpuid_leaf = cpuid_count(0, 0).eax as u64; + if cpuid_leaf < TDX_CPUID_LEAF_ID { + return Err(InitError::TdxCpuLeafIdError); + } + let cpuid_result: CpuIdResult = cpuid_count(TDX_CPUID_LEAF_ID as u32, 0); + if convert_ascii(cpuid_result.ebx) == "Inte" + && convert_ascii(cpuid_result.edx) == "lTDX" + && convert_ascii(cpuid_result.ecx) == " " + { + Ok(()) + } else { + Err(InitError::TdxVendorIdError) + } +} + +fn convert_ascii(reg: u32) -> String { + let bytes = [ + (reg & 0xFF) as u8, + ((reg >> 8) & 0xFF) as u8, + ((reg >> 16) & 0xFF) as u8, + ((reg >> 24) & 0xFF) as u8, + ]; + String::from_utf8_lossy(&bytes).to_string() +} + +pub trait TdxTrapFrame { + fn rax(&self) -> usize; + fn set_rax(&mut self, rax: usize); + fn rbx(&self) -> usize; + fn set_rbx(&mut self, rbx: usize); + fn rcx(&self) -> usize; + fn set_rcx(&mut self, rcx: usize); + fn rdx(&self) -> usize; + fn set_rdx(&mut self, rdx: usize); + fn rsi(&self) -> usize; + fn set_rsi(&mut self, rsi: usize); + fn rdi(&self) -> usize; + fn set_rdi(&mut self, rdi: usize); + fn rip(&self) -> usize; + fn set_rip(&mut self, rip: usize); +} + +pub fn virtual_exception_handler(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) { + match ve_info.exit_reason.into() { + TdxVirtualExceptionType::Hlt + | TdxVirtualExceptionType::Io + | TdxVirtualExceptionType::MsrRead + | TdxVirtualExceptionType::MsrWrite + | TdxVirtualExceptionType::CpuId => tdg_vp_vmcall(trapframe, ve_info), + TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"), + _ => return, + }; + trapframe.set_rip(trapframe.rip() + ve_info.exit_instruction_length as usize); +} diff --git a/framework/libs/tdx-guest/src/tdcall.rs b/framework/libs/tdx-guest/src/tdcall.rs new file mode 100644 index 000000000..4a293abef --- /dev/null +++ b/framework/libs/tdx-guest/src/tdcall.rs @@ -0,0 +1,596 @@ +use super::tdvmcall::*; +use crate::{asm::asm_td_call, serial_println, TdxTrapFrame}; +use bitflags::bitflags; +use core::fmt; + +const TDCALL_VP_INFO: u64 = 1; +const TDCALL_MR_RTMR_EXTEND: u64 = 2; +const TDCALL_VP_VEINFO_GET: u64 = 3; +const TDCALL_MR_REPORT: u64 = 4; +const TDCALL_VP_CPUIDVE_SET: u64 = 5; +const TDCALL_MEM_PAGE_ACCEPT: u64 = 6; +const TDCALL_VM_RD: u64 = 7; +const TDCALL_VM_WR: u64 = 8; +const TDCALL_MR_VERIFYREPORT: u64 = 22; +const TDCALL_MEM_PAGE_ATTR_RD: u64 = 23; +const TDCALL_MEM_PAGE_ATTR_WR: u64 = 24; + +bitflags! { + pub struct Attributes: u64 { + /// Guest TD runs in off-TD debug mode. + /// Its VCPU state and private memory are accessible by the host VMM. + const DEBUG = 1; + /// TD is migratable (using a Migration TD). + const MIGRATABLE = 1 << 29; + /// TD is allowed to use Supervisor Protection Keys. + const PKS = 1 << 30; + /// TD is allowed to use Key Locker. Must be 0. + const KL = 1 << 31; + /// TD is allowed to use Perfmon and PERF_METRICS capabilities. + const PERFMON = 1 << 63; + } +} + +bitflags! { + /// Controls whether CPUID executed by the guest TD will cause #VE unconditionally. + struct CpuidveFlag: u64 { + /// Flags that when CPL is 0, a CPUID executed + /// by the guest TD will cause a #VE unconditionally. + const SUPERVISOR = 1; + /// Flags that when CPL > 0, a CPUID executed + /// by the guest TD will cause a #VE unconditionally. + const USER = 1 << 1; + } +} + +bitflags! { + /// GPA Attributes (Single VM) Definition. + pub struct GpaAttr: u16 { + /// Read. + const R = 1; + /// Write. + const W = 1 << 1; + /// Execute (Supervisor). + const XS = 1 << 2; + /// Execute (User). + const XU = 1 << 3; + /// Verify Guest Paging. + const VGP = 1 << 4; + /// Paging-Write Access. + const PWA = 1 << 5; + /// Supervisor Shadow Stack. + const SSS = 1 << 6; + /// Suppress #VE. + const SVE = 1 << 7; + /// Indicates that the other bits are valid. + /// If its value is 0, other fields are reserved and must be 0. + const VALID = 1 << 15; + } +} +pub struct PageAttr { + /// Actual GPA mapping of the page. + gpa_mapping: u64, + /// Guest-visible page attributes. + gpa_attr: GpaAttrAll, +} + +/// GPA Attributes (all VMs) Definition. +pub struct GpaAttrAll { + /// L1 GPA attributes. + l1_attr: GpaAttr, + /// GPA attributes for L2 VM #1. + vm1_attr: GpaAttr, + /// GPA attributes for L2 VM #2. + vm2_attr: GpaAttr, + /// GPA attributes for L2 VM #3. + vm3_attr: GpaAttr, +} + +impl From for GpaAttrAll { + fn from(val: u64) -> Self { + GpaAttrAll { + l1_attr: GpaAttr::from_bits_truncate((val & 0xFFFF) as u16), + vm1_attr: GpaAttr::from_bits_truncate(((val >> 16) & 0xFFFF) as u16), + vm2_attr: GpaAttr::from_bits_truncate(((val >> 32) & 0xFFFF) as u16), + vm3_attr: GpaAttr::from_bits_truncate(((val >> 48) & 0xFFFF) as u16), + } + } +} + +impl From for u64 { + fn from(s: GpaAttrAll) -> Self { + let field1 = s.l1_attr.bits() as u64; + let field2 = (s.vm1_attr.bits() as u64) << 16; + let field3 = (s.vm2_attr.bits() as u64) << 32; + let field4 = (s.vm3_attr.bits() as u64) << 48; + field4 | field3 | field2 | field1 + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct TdReport { + /// REPORTMACSTRUCT for the TDG.MR.REPORT. + pub report_mac: ReportMac, + /// Additional attestable elements in the TD’s TCB are not reflected in the + /// REPORTMACSTRUCT.CPUSVN – includes the Intel TDX module measurements. + pub tee_tcb_info: [u8; 239], + pub reserved: [u8; 17], + /// TD’s attestable properties. + pub tdinfo: TdInfo, +} + +#[repr(C)] +#[derive(Debug)] +pub struct ReportMac { + /// Type Header Structure. + pub report_type: ReportType, + pub cpu_svn: [u8; 16], + /// SHA384 of TEE_TCB_INFO for TEEs implemented using Intel TDX. + pub tee_tcb_info_hash: [u8; 48], + /// SHA384 of TEE_INFO: a TEE-specific info structure (TDINFO_STRUCT or SGXINFO) + /// or 0 if no TEE is represented. + pub tee_info_hash: [u8; 48], + /// A set of data used for communication between the caller and the target. + pub report_data: [u8; 64], + pub reserved: [u8; 32], + /// The MAC over the REPORTMACSTRUCT with model-specific MAC. + pub mac: [u8; 32], +} + +#[derive(Debug)] +pub enum TeeType { + SGX, + TDX, +} + +/// REPORTTYPE indicates the reported Trusted Execution Environment (TEE) type, +/// sub-type and version. +#[repr(C)] +#[derive(Debug)] +pub struct ReportType { + /// Trusted Execution Environment (TEE) Type. 0x00: SGX, 0x81: TDX. + pub tee_type: TeeType, + /// TYPE-specific subtype. + pub sub_type: u8, + /// TYPE-specific version. + pub version: u8, + pub reserved: u8, +} + +/// TDINFO_STRUCT is defined as the TDX-specific TEE_INFO part of TDG.MR.REPORT. +/// It contains the measurements and initial configuration of the TD that was +/// locked at initialization and a set of measurement registers that are run-time +/// extendable. These values are copied from the TDCS by the TDG.MR.REPORT function. +/// Refer to the [TDX Module Base Spec] for additional details. +#[repr(C)] +#[derive(Debug)] +pub struct TdInfo { + /// TD’s ATTRIBUTES. + pub attributes: u64, + /// TD’s XFAM. + pub xfam: u64, + /// Measurement of the initial contents of the TD. + pub mrtd: [u8; 48], + /// Software-defined ID for non-owner-defined configuration of the + /// guest TD – e.g., run-time or OS configuration. + pub mr_config_id: [u8; 48], + /// Software-defined ID for the guest TD’s owner. + pub mr_owner: [u8; 48], + /// Software-defined ID for owner-defined configuration of the + /// guest TD – e.g., specific to the workload rather than the run-time or OS. + pub mr_owner_config: [u8; 48], + /// Array of NUM_RTMRS (4) run-time extendable measurement registers. + pub rtmr0: [u8; 48], + pub rtmr1: [u8; 48], + pub rtmr2: [u8; 48], + pub rtmr3: [u8; 48], + /// If is one or more bound or pre-bound service TDs, SERVTD_HASH is the SHA384 hash of the + /// TDINFO_STRUCTs of those service TDs bound. Else, SERVTD_HASH is 0. + pub servtd_hash: [u8; 48], + pub reserved: [u8; 64], +} + +#[repr(C)] +#[derive(Debug)] +pub struct TdgVeInfo { + pub exit_reason: u32, + /// the 64-bit value that would have been saved into the VMCS as an exit qualification + /// if a legacy VM exit had occurred instead of the virtualization exception. + pub exit_qualification: u64, + /// the 64-bit value that would have been saved into the VMCS as a guestlinear address + /// if a legacy VM exit had occurred instead of the virtualization exception. + pub guest_linear_address: u64, + /// the 64-bit value that would have been saved into the VMCS as a guestphysical address + /// if a legacy VM exit had occurred instead of the virtualization exception. + pub guest_physical_address: u64, + /// The 32-bit value that would have been saved into the VMCS as VM-exit instruction + /// length if a legacy VM exit had occurred instead of the virtualization exception. + pub exit_instruction_length: u32, + /// The 32-bit value that would have been saved into the VMCS as VM-exit instruction + /// information if a legacy VM exit had occurred instead of the virtualization exception. + pub exit_instruction_info: u32, +} + +#[derive(Debug)] +pub enum Gpaw { + Bit48, + Bit52, +} + +impl From for Gpaw { + fn from(val: u64) -> Self { + match val { + 48 => Self::Bit48, + 52 => Self::Bit52, + _ => panic!("Invalid gpaw"), + } + } +} + +impl From for u64 { + fn from(s: Gpaw) -> Self { + match s { + Gpaw::Bit48 => 48, + Gpaw::Bit52 => 52, + } + } +} + +impl fmt::Display for Gpaw { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Gpaw::Bit48 => write!(f, "48-bit"), + Gpaw::Bit52 => write!(f, "52-bit"), + } + } +} + +#[derive(Debug)] +pub struct TdgVpInfo { + /// The effective GPA width (in bits) for this TD (do not confuse with MAXPA). + /// SHARED bit is at GPA bit GPAW-1. + /// + /// Only GPAW values 48 and 52 are possible. + pub gpaw: Gpaw, + /// The TD's ATTRIBUTES (provided as input to TDH.MNG.INIT) + pub attributes: Attributes, + pub num_vcpus: u32, + pub max_vcpus: u32, + pub vcpu_index: u32, + /// Indicates that the TDG.SYS.RD/RDM/RDCALL function are avaliable. + pub sys_rd: u32, +} + +#[derive(Debug, PartialEq)] +pub enum TdCallError { + /// There is no valid #VE information. + TdxNoValidVeInfo, + /// Operand is invalid. + TdxOperandInvalid, + /// The operand is busy (e.g., it is locked in Exclusive mode). + TdxOperandBusy, + /// Page has already been accepted. + TdxPageAlreadyAccepted, + /// Requested page size does not match the current GPA mapping size. + TdxPageSizeMismatch, + Other, +} + +impl From for TdCallError { + fn from(val: u64) -> Self { + match val { + 0xC000_0704 => Self::TdxNoValidVeInfo, + 0xC000_0100 => Self::TdxOperandInvalid, + 0x8000_0200 => Self::TdxOperandBusy, + 0x0000_0B0A => Self::TdxPageAlreadyAccepted, + 0xC000_0B0B => Self::TdxPageSizeMismatch, + _ => Self::Other, + } + } +} + +#[repr(C)] +#[derive(Default)] +pub(crate) struct TdcallArgs { + rax: u64, + rcx: u64, + rdx: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, +} + +pub enum TdxVirtualExceptionType { + Hlt, + Io, + MsrRead, + MsrWrite, + CpuId, + VmCall, + Mwait, + Monitor, + Wbinvd, + Rdpmc, + Other, +} + +impl From for TdxVirtualExceptionType { + fn from(val: u32) -> Self { + match val { + 10 => Self::CpuId, + 12 => Self::Hlt, + 15 => Self::Rdpmc, + 18 => Self::VmCall, + 30 => Self::Io, + 31 => Self::MsrRead, + 32 => Self::MsrWrite, + 36 => Self::Mwait, + 39 => Self::Monitor, + 54 => Self::Wbinvd, + _ => Self::Other, + } + } +} + +#[derive(Debug)] +pub enum InitError { + TdxVendorIdError, + TdxCpuLeafIdError, + TdxGetVpInfoError(TdCallError), +} + +impl From for InitError { + fn from(error: TdCallError) -> Self { + InitError::TdxGetVpInfoError(error) + } +} + +/// Get guest TD execution environment information. +pub(crate) fn tdg_vp_info() -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VP_INFO, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => { + let td_info = TdgVpInfo { + gpaw: Gpaw::from(args.rcx), + attributes: Attributes::from_bits_truncate(args.rdx), + num_vcpus: args.r8 as u32, + max_vcpus: (args.r8 >> 32) as u32, + vcpu_index: args.r9 as u32, + sys_rd: args.r10 as u32, + }; + Ok(td_info) + } + Err(res) => Err(res), + } +} + +/// Get Virtualization Exception Information for the recent #VE exception. +pub fn tdg_vp_veinfo_get() -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VP_VEINFO_GET, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => { + let ve_info = TdgVeInfo { + exit_reason: args.rcx as u32, + exit_qualification: args.rdx, + guest_linear_address: args.r8, + guest_physical_address: args.r9, + exit_instruction_length: args.r10 as u32, + exit_instruction_info: (args.r10 >> 32) as u32, + }; + Ok(ve_info) + } + Err(res) => Err(res), + } +} + +/// Perform a TD Exit to the host VMM. +pub(crate) fn tdg_vp_vmcall(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) { + match ve_info.exit_reason.into() { + TdxVirtualExceptionType::Hlt => { + serial_println!("Ready to halt"); + tdvmcall_hlt(); + } + TdxVirtualExceptionType::Io => { + if !tdvmcall_io(trapframe, ve_info) { + serial_println!("Handle tdx ioexit errors, ready to halt"); + tdvmcall_hlt(); + } + } + TdxVirtualExceptionType::MsrRead => { + let msr = tdvmcall_rdmsr(trapframe.rcx() as u32).unwrap(); + trapframe.set_rax((msr as u32 & u32::MAX) as usize); + trapframe.set_rdx(((msr >> 32) as u32 & u32::MAX) as usize); + } + TdxVirtualExceptionType::MsrWrite => { + let data = trapframe.rax() as u64 | ((trapframe.rdx() as u64) << 32); + tdvmcall_wrmsr(trapframe.rcx() as u32, data).unwrap(); + } + TdxVirtualExceptionType::CpuId => { + let cpuid = tdvmcall_cpuid(trapframe.rax() as u32, trapframe.rcx() as u32).unwrap(); + let mask = 0xFFFF_FFFF_0000_0000_usize; + trapframe.set_rax((trapframe.rax() & mask) | cpuid.eax); + trapframe.set_rbx((trapframe.rbx() & mask) | cpuid.ebx); + trapframe.set_rcx((trapframe.rcx() & mask) | cpuid.ecx); + trapframe.set_rdx((trapframe.rdx() & mask) | cpuid.edx); + } + _ => { + unreachable!() + } + }; +} + +#[allow(dead_code)] +pub fn deadloop() { + #[allow(clippy::empty_loop)] + loop { + x86_64::instructions::interrupts::enable(); + x86_64::instructions::hlt(); + } +} + +fn td_call(args: &mut TdcallArgs) -> Result<(), TdCallError> { + let td_call_result = unsafe { asm_td_call(args) }; + if td_call_result == 0 { + Ok(()) + } else { + Err(td_call_result.into()) + } +} + +/// Extend a TDCS.RTMR measurement register. +fn tdg_mr_rtmr_extend() -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_MR_RTMR_EXTEND, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +/// TDG.MR.REPORT creates a TDREPORT_STRUCT structure that contains the measurements/configuration +/// information of the guest TD that called the function, measurements/configuration information +/// of the Intel TDX module and a REPORTMACSTRUCT. +fn tdg_mr_report(report_addr: u64, data_addr: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_MR_REPORT, + rcx: report_addr, + rdx: data_addr, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +/// Verify a cryptographic REPORTMACSTRUCT that describes the contents of a TD, +/// to determine that it was created on the current TEE on the current platform. +fn tdg_mr_verifyreport(report_mac_addr: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_MR_VERIFYREPORT, + rcx: report_mac_addr, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +/// Accept a pending private page and initialize it to all-0 using the TD ephemeral private key. +fn tdg_mem_page_accept(sept_level: u64, addr: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_MEM_PAGE_ACCEPT, + rcx: sept_level | (addr << 12), + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +/// Read the GPA mapping and attributes of a TD private page. +fn tdg_mem_page_attr_rd(phy_addr: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_MEM_PAGE_ATTR_RD, + rcx: phy_addr, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => { + let page_attr = PageAttr { + gpa_mapping: args.rcx, + gpa_attr: GpaAttrAll::from(args.rdx), + }; + Ok(page_attr) + } + Err(res) => Err(res), + } +} + +/// Write the attributes of a private page. Create or remove L2 page aliases as required. +fn tdg_mem_page_attr_wr(page_attr: PageAttr, attr_flags: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_MEM_PAGE_ATTR_WR, + rcx: page_attr.gpa_mapping, + rdx: u64::from(page_attr.gpa_attr), + r8: attr_flags, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => { + let page_attr = PageAttr { + gpa_mapping: args.rcx, + gpa_attr: GpaAttrAll::from(args.rdx), + }; + Ok(page_attr) + } + Err(res) => Err(res), + } +} + +/// Read a TD-scope metadata field (control structure field) of a TD. +fn tdg_vm_rd(field_identifier: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VM_RD, + rdx: field_identifier, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(args.r8), + Err(res) => Err(res), + } +} + +/// Write a TD-scope metadata field (control structure field) of a TD. +/// +/// - data: data to write to the field +/// +/// - write_mask: a 64b write mask to indicate which bits of the value +/// in R8 are to be written to the field. +/// +/// It returns previous contents of the field. +fn tdg_vm_wr(field_identifier: u64, data: u64, write_mask: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VM_WR, + rdx: field_identifier, + r8: data, + r9: write_mask, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(args.r8), + Err(res) => Err(res), + } +} + +/// TDG.VP.CPUIDVE.SET controls unconditional #VE on CPUID execution by the guest TD. +/// +/// Note: TDG.VP.CPUIDVE.SET is provided for backward compatibility. +/// +/// The guest TD may control the same settings by writing to the +/// VCPU-scope metadata fields CPUID_SUPERVISOR_VE and CPUID_USER_VE using TDG.VP.WR. +fn tdg_vp_cpuidve_set(cpuidve_flag: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_VP_CPUIDVE_SET, + rcx: cpuidve_flag, + ..Default::default() + }; + match td_call(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} diff --git a/framework/libs/tdx-guest/src/tdvmcall.rs b/framework/libs/tdx-guest/src/tdvmcall.rs new file mode 100644 index 000000000..f78bb6593 --- /dev/null +++ b/framework/libs/tdx-guest/src/tdvmcall.rs @@ -0,0 +1,340 @@ +extern crate alloc; + +use crate::{asm::asm_td_vmcall, tdcall::TdgVeInfo, TdxTrapFrame}; +use alloc::fmt; +use bitflags::bitflags; +use core::fmt::Write; +use x86_64::{ + registers::rflags::{self, RFlags}, + structures::port::PortRead, +}; + +const TDVMCALL_CPUID: u64 = 0x0000a; +const TDVMCALL_HLT: u64 = 0x0000c; +const TDVMCALL_IO: u64 = 0x0001e; +const TDVMCALL_RDMSR: u64 = 0x0001f; +const TDVMCALL_WRMSR: u64 = 0x00020; +const TDVMCALL_REQUEST_MMIO: u64 = 0x00030; +const TDVMCALL_WBINVD: u64 = 0x00036; +const TDVMCALL_PCONFIG: u64 = 0x00041; +const TDVMCALL_MAPGPA: u64 = 0x10001; + +const SERIAL_IO_PORT: u16 = 0x3F8; +const SERIAL_LINE_STS: u16 = 0x3FD; +const IO_READ: u64 = 0; +const IO_WRITE: u64 = 1; + +#[derive(Debug, PartialEq)] +pub enum TdVmcallError { + /// TDCALL[TDG.VP.VMCALL] sub-function invocation must be retried. + TdxRetry, + /// Invalid operand to TDG.VP.VMCALL sub-function. + TdxInvalidOperand, + /// GPA already mapped. + TdxGpaInuse, + /// Operand (address) aligned error. + TdxAlignError, + Other, +} + +impl From for TdVmcallError { + fn from(val: u64) -> Self { + match val { + 0x1 => Self::TdxRetry, + 0x8000_0000_0000_0000 => Self::TdxInvalidOperand, + 0x8000_0000_0000_0001 => Self::TdxGpaInuse, + 0x8000_0000_0000_0002 => Self::TdxAlignError, + _ => Self::Other, + } + } +} + +#[repr(C)] +#[derive(Default)] +pub(crate) struct TdVmcallArgs { + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, +} + +#[repr(C)] +#[derive(Debug, Default)] +pub(crate) struct CpuIdInfo { + pub eax: usize, + pub ebx: usize, + pub ecx: usize, + pub edx: usize, +} + +enum Direction { + In, + Out, +} + +enum Operand { + Dx, + Immediate, +} + +pub(crate) fn tdvmcall_cpuid(eax: u32, ecx: u32) -> Result { + let mut args = TdVmcallArgs { + r11: TDVMCALL_CPUID, + r12: eax as u64, + r13: ecx as u64, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(CpuIdInfo { + eax: args.r12 as usize, + ebx: args.r13 as usize, + ecx: args.r14 as usize, + edx: args.r15 as usize, + }), + Err(res) => Err(res), + } +} + +pub(crate) fn tdvmcall_hlt() { + let interrupt_blocked = !rflags::read().contains(RFlags::INTERRUPT_FLAG); + let mut args = TdVmcallArgs { + r11: TDVMCALL_HLT, + r12: interrupt_blocked as u64, + ..Default::default() + }; + let _ = td_vmcall(&mut args); +} + +pub(crate) fn tdvmcall_rdmsr(index: u32) -> Result { + let mut args = TdVmcallArgs { + r11: TDVMCALL_RDMSR, + r12: index as u64, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(args.r11), + Err(res) => Err(res), + } +} + +pub(crate) fn tdvmcall_wrmsr(index: u32, value: u64) -> Result<(), TdVmcallError> { + let mut args = TdVmcallArgs { + r11: TDVMCALL_WRMSR, + r12: index as u64, + r13: value, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +/// Used to help perform WBINVD operation. +pub(crate) fn tdvmcall_wbinvd(wbinvd: u64) -> Result<(), TdVmcallError> { + let mut args = TdVmcallArgs { + r11: TDVMCALL_WBINVD, + r12: wbinvd, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +pub(crate) fn tdvmcall_read_mmio(size: u64, mmio_addr: u64) -> Result { + match size { + 1 | 2 | 4 | 8 => {} + _ => return Err(TdVmcallError::TdxInvalidOperand), + } + let mut args = TdVmcallArgs { + r11: TDVMCALL_REQUEST_MMIO, + r12: size, + r13: 0, + r14: mmio_addr, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(args.r11), + Err(res) => Err(res), + } +} + +pub(crate) fn tdvmcall_write_mmio( + size: u64, + mmio_addr: u64, + data: u64, +) -> Result<(), TdVmcallError> { + match size { + 1 | 2 | 4 | 8 => {} + _ => { + return Err(TdVmcallError::TdxInvalidOperand); + } + } + let mut args = TdVmcallArgs { + r11: TDVMCALL_REQUEST_MMIO, + r12: size, + r13: 1, + r14: mmio_addr, + r15: data, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } +} + +pub(crate) fn tdvmcall_io(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) -> bool { + let size = match ve_info.exit_qualification & 0x3 { + 0 => 1, + 1 => 2, + 3 => 4, + _ => panic!("Invalid size value"), + }; + let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 { + Direction::Out + } else { + Direction::In + }; + let string = (ve_info.exit_qualification >> 4) & 0x1 == 1; + let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1; + let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 { + Operand::Dx + } else { + Operand::Immediate + }; + let port = (ve_info.exit_qualification >> 16) as u16; + + match direction { + Direction::In => { + trapframe.set_rax(io_read(size, port).unwrap() as usize); + } + Direction::Out => { + io_write(size, port, trapframe.rax() as u32).unwrap(); + } + }; + true +} + +macro_rules! tdvmcall_io_read { + ($port:expr, $ty:ty) => {{ + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::<$ty>() as u64, + r13: IO_READ, + r14: $port as u64, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(args.r11 as u32), + Err(res) => Err(res), + } + }}; +} + +fn io_read(size: usize, port: u16) -> Result { + match size { + 1 => tdvmcall_io_read!(port, u8), + 2 => tdvmcall_io_read!(port, u16), + 4 => tdvmcall_io_read!(port, u32), + _ => unreachable!(), + } +} + +macro_rules! tdvmcall_io_write { + ($port:expr, $byte:expr, $size:expr) => {{ + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of_val(&$byte) as u64, + r13: IO_WRITE, + r14: $port as u64, + r15: $byte as u64, + ..Default::default() + }; + match td_vmcall(&mut args) { + Ok(()) => Ok(()), + Err(res) => Err(res), + } + }}; +} + +fn io_write(size: usize, port: u16, byte: u32) -> Result<(), TdVmcallError> { + match size { + 1 => tdvmcall_io_write!(port, byte, u8), + 2 => tdvmcall_io_write!(port, byte, u16), + 4 => tdvmcall_io_write!(port, byte, u32), + _ => unreachable!(), + } +} + +fn td_vmcall(args: &mut TdVmcallArgs) -> Result<(), TdVmcallError> { + let td_vmcall_result = unsafe { asm_td_vmcall(args) }; + if td_vmcall_result == 0 { + Ok(()) + } else { + Err(td_vmcall_result.into()) + } +} + +bitflags! { + struct LineSts: u8 { + const INPUT_FULL = 1; + const OUTPUT_EMPTY = 1 << 5; + } +} + +fn line_sts() -> LineSts { + LineSts::from_bits_truncate(unsafe { PortRead::read_from_port(SERIAL_LINE_STS) }) +} + +struct Serial; + +impl Write for Serial { + fn write_str(&mut self, s: &str) -> fmt::Result { + for &c in s.as_bytes() { + serial_write_byte(c); + } + Ok(()) + } +} + +pub fn print(args: fmt::Arguments) { + Serial + .write_fmt(args) + .expect("Failed to write to serial port"); +} + +fn serial_write_byte(byte: u8) { + match byte { + 8 | 0x7F => { + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + tdvmcall_io_write!(SERIAL_IO_PORT, 8, u8).unwrap(); + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + tdvmcall_io_write!(SERIAL_IO_PORT, b' ', u8).unwrap(); + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + tdvmcall_io_write!(SERIAL_IO_PORT, 8, u8).unwrap(); + } + _ => { + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + tdvmcall_io_write!(SERIAL_IO_PORT, byte, u8).unwrap(); + } + } +} + +#[macro_export] +macro_rules! serial_print { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::tdvmcall::print(format_args!($fmt $(, $($arg)+)?)); + } +} + +#[macro_export] +macro_rules! serial_println { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::tdvmcall::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) + } +} diff --git a/services/libs/jinux-std/Cargo.toml b/services/libs/jinux-std/Cargo.toml index 31fd7ba26..2c089d11b 100644 --- a/services/libs/jinux-std/Cargo.toml +++ b/services/libs/jinux-std/Cargo.toml @@ -40,6 +40,7 @@ smoltcp = { version = "0.9.1", default-features = false, features = [ "socket-raw", "socket-dhcpv4", ] } +tdx-guest = { path = "../../../framework/libs/tdx-guest", optional = true } # parse elf file xmas-elf = "0.8.0" @@ -65,3 +66,6 @@ getrandom = { version = "0.2.10", default-features = false, features = [ [dependencies.lazy_static] version = "1.0" features = ["spin_no_std"] + +[features] +intel_tdx = ["dep:tdx-guest"]