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
24 changes: 24 additions & 0 deletions docs/intel_tdx.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,27 @@ devices responsible for handling PCI hotplug (PCI hotplug controller, PCI
Express Bus and Generic Event Device) will not be allowed, therefore the
corresponding drivers will not be loaded and the PCI hotplug feature will not
be supported.

## Measurement configuration registers

A TD carries three owner/configuration measurement registers that are baked
into its attestation report: `MRCONFIGID`, `MROWNER` and `MROWNERCONFIG`. Each
is a 384-bit (48-byte) SHA384 digest chosen by the tenant or platform owner.

They can be supplied through the matching `--platform` options as hex strings
of exactly 96 characters (48 bytes):

```bash
./cloud-hypervisor \
--platform tdx=on,mrconfigid=<96-hex-chars>,mrowner=<96-hex-chars>,mrownerconfig=<96-hex-chars> \
--firmware td-shim/target/release/final.bin \
--kernel bzImage \
--cmdline "root=/dev/vda3 console=hvc0 rw" \
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img
```

Any register left unset defaults to all zeros, preserving the previous
behavior.

15 changes: 11 additions & 4 deletions hypervisor/src/kvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,14 @@ impl vm::Vm for KvmVm {
/// Initialize TDX for this VM
///
#[cfg(feature = "tdx")]
fn tdx_init(&self, cpuid: &[CpuIdEntry], max_vcpus: u32) -> vm::Result<()> {
fn tdx_init(
&self,
cpuid: &[CpuIdEntry],
max_vcpus: u32,
mrconfigid: &[u8; 48],
mrowner: &[u8; 48],
mrownerconfig: &[u8; 48],
) -> vm::Result<()> {
let tdx_capabilities = self.tdx_capabilities()?;

// `KVM_TDX_INIT_VM` only accepts the configurable leaves reported by
Expand Down Expand Up @@ -1063,9 +1070,9 @@ impl vm::Vm for KvmVm {
let data = KvmTdxInitVm {
attributes,
xfam,
mrconfigid: [0; 6],
mrowner: [0; 6],
mrownerconfig: [0; 6],
mrconfigid: *mrconfigid,
mrowner: *mrowner,
mrownerconfig: *mrownerconfig,
reserved: [0; 12],
cpuid: kvm_cpuid2 {
nent: filtered_cpuid.len() as u32,
Expand Down
10 changes: 7 additions & 3 deletions hypervisor/src/kvm/tdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,13 @@ pub(crate) struct KvmTdxCmd {
pub(crate) struct KvmTdxInitVm {
pub attributes: u64,
pub xfam: u64,
pub mrconfigid: [u64; 6],
pub mrowner: [u64; 6],
pub mrownerconfig: [u64; 6],
// The kernel treats mrconfigid/mrowner/mrownerconfig as raw 48-byte SHA384
// digests (declared `__u64[6]`, populated by a byte copy), so store them
// as byte arrays to forward the caller's values verbatim without any
// endianness conversion.
pub mrconfigid: [u8; 48],
pub mrowner: [u8; 48],
pub mrownerconfig: [u8; 48],
pub reserved: [u64; 12],
pub cpuid: kvm_cpuid2,
}
Expand Down
9 changes: 8 additions & 1 deletion hypervisor/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,14 @@ pub trait Vm: Send + Sync + Any {
}
#[cfg(feature = "tdx")]
/// Initialize TDX on this VM
fn tdx_init(&self, _cpuid: &[CpuIdEntry], _max_vcpus: u32) -> Result<()> {
fn tdx_init(
&self,
_cpuid: &[CpuIdEntry],
_max_vcpus: u32,
_mrconfigid: &[u8; 48],
_mrowner: &[u8; 48],
_mrownerconfig: &[u8; 48],
) -> Result<()> {
unimplemented!()
}
#[cfg(feature = "tdx")]
Expand Down
2 changes: 1 addition & 1 deletion vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ mshv = [
]
pvmemcontrol = ["devices/pvmemcontrol"]
sev_snp = ["arch/sev_snp", "hypervisor/sev_snp", "virtio-devices/sev_snp"]
tdx = ["arch/tdx", "hypervisor/tdx"]
tdx = ["arch/tdx", "hex", "hypervisor/tdx"]
tracing = ["tracer/tracing"]

[dependencies]
Expand Down
111 changes: 111 additions & 0 deletions vmm/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ pub enum ValidationError {
#[cfg(feature = "tdx")]
#[error("No TDX firmware specified")]
TdxFirmwareMissing,
/// Invalid TDX measurement-configuration digest
#[cfg(feature = "tdx")]
#[error("TDX '{0}' must be a 96-character (48-byte) hex SHA384 digest: {1}")]
TdxInvalidMeasurement(&'static str, String),
/// TDX measurement-configuration digest specified without enabling TDX
#[cfg(feature = "tdx")]
#[error("mrconfigid/mrowner/mrownerconfig require 'tdx=on'")]
TdxMeasurementWithoutTdx,
/// Insufficient vCPUs for queues
#[error("Queue count ({0}) must not exceed boot vCPUs ({1})")]
TooManyQueues(usize /* queues */, usize /* vCPUs */),
Expand Down Expand Up @@ -809,6 +817,12 @@ impl PlatformConfig {
.add("oem_strings");
#[cfg(feature = "tdx")]
parser.add("tdx");
#[cfg(feature = "tdx")]
parser.add("mrconfigid");
#[cfg(feature = "tdx")]
parser.add("mrowner");
#[cfg(feature = "tdx")]
parser.add("mrownerconfig");
#[cfg(feature = "sev_snp")]
parser.add("sev_snp");
parser.parse(platform).map_err(Error::ParsePlatform)?;
Expand Down Expand Up @@ -839,6 +853,18 @@ impl PlatformConfig {
.map_err(Error::ParsePlatform)?
.unwrap_or(Toggle(false))
.0;
#[cfg(feature = "tdx")]
let tdx_mrconfigid = parser
.convert::<String>("mrconfigid")
.map_err(Error::ParsePlatform)?;
#[cfg(feature = "tdx")]
let tdx_mrowner = parser
.convert::<String>("mrowner")
.map_err(Error::ParsePlatform)?;
#[cfg(feature = "tdx")]
let tdx_mrownerconfig = parser
.convert::<String>("mrownerconfig")
.map_err(Error::ParsePlatform)?;
#[cfg(feature = "sev_snp")]
let sev_snp = parser
.convert::<Toggle>("sev_snp")
Expand All @@ -854,11 +880,38 @@ impl PlatformConfig {
oem_strings,
#[cfg(feature = "tdx")]
tdx,
#[cfg(feature = "tdx")]
tdx_mrconfigid,
#[cfg(feature = "tdx")]
tdx_mrowner,
#[cfg(feature = "tdx")]
tdx_mrownerconfig,
#[cfg(feature = "sev_snp")]
sev_snp,
})
}

/// Decode the optional TDX SHA384 measurement-configuration digests
/// (`mrconfigid`, `mrowner`, `mrownerconfig`) from their hex string form
/// into 48-byte arrays. Any register left unset decodes to all zeros.
#[cfg(feature = "tdx")]
pub fn tdx_measurements(&self) -> ValidationResult<([u8; 48], [u8; 48], [u8; 48])> {
fn decode(name: &'static str, value: &Option<String>) -> ValidationResult<[u8; 48]> {
let mut out = [0u8; 48];
if let Some(s) = value {
hex::decode_to_slice(s, &mut out)
.map_err(|e| ValidationError::TdxInvalidMeasurement(name, e.to_string()))?;
}
Ok(out)
}

Ok((
decode("mrconfigid", &self.tdx_mrconfigid)?,
decode("mrowner", &self.tdx_mrowner)?,
decode("mrownerconfig", &self.tdx_mrownerconfig)?,
))
}

pub fn validate(&self) -> ValidationResult<()> {
if self.num_pci_segments == 0 || self.num_pci_segments > MAX_NUM_PCI_SEGMENTS {
return Err(ValidationError::InvalidNumPciSegments(
Expand Down Expand Up @@ -2873,6 +2926,15 @@ impl VmConfig {
if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) {
return Err(ValidationError::TdxNoCpuHotplug);
}
let has_tdx_measurements = self.platform.as_ref().is_some_and(|p| {
p.tdx_mrconfigid.is_some() || p.tdx_mrowner.is_some() || p.tdx_mrownerconfig.is_some()
});
if has_tdx_measurements && !tdx_enabled {
return Err(ValidationError::TdxMeasurementWithoutTdx);
}
if tdx_enabled {
self.platform.as_ref().unwrap().tdx_measurements()?;
}
if tdx_enabled {
// For TDX the guest physical address width (GPAW) is fixed at:
// 48 - (GPAW-48, 4-level EPT)
Expand Down Expand Up @@ -4415,6 +4477,38 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
Ok(())
}

#[cfg(feature = "tdx")]
#[test]
fn test_platform_tdx_measurements() -> Result<()> {
// Unset registers decode to all zeros.
let p = PlatformConfig::parse("tdx=on")?;
assert_eq!(p.tdx_measurements(), Ok(([0u8; 48], [0u8; 48], [0u8; 48])));

// A valid 96-character hex digest decodes into the matching register.
let hexid = "01".repeat(48);
let p = PlatformConfig::parse(&format!("tdx=on,mrconfigid={hexid}"))?;
let (mrconfigid, mrowner, mrownerconfig) = p.tdx_measurements().unwrap();
assert_eq!(mrconfigid, [1u8; 48]);
assert_eq!(mrowner, [0u8; 48]);
assert_eq!(mrownerconfig, [0u8; 48]);

// Wrong length is rejected by validation.
let p = PlatformConfig::parse("tdx=on,mrowner=00")?;
assert!(matches!(
p.tdx_measurements(),
Err(ValidationError::TdxInvalidMeasurement("mrowner", _))
));

// Non-hex characters are rejected too.
let p = PlatformConfig::parse(&format!("tdx=on,mrownerconfig={}", "zz".repeat(48)))?;
assert!(matches!(
p.tdx_measurements(),
Err(ValidationError::TdxInvalidMeasurement("mrownerconfig", _))
));

Ok(())
}

#[test]
fn test_vsock_parsing() -> Result<()> {
// socket and cid is required
Expand Down Expand Up @@ -4808,6 +4902,12 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
oem_strings: None,
#[cfg(feature = "tdx")]
tdx: false,
#[cfg(feature = "tdx")]
tdx_mrconfigid: None,
#[cfg(feature = "tdx")]
tdx_mrowner: None,
#[cfg(feature = "tdx")]
tdx_mrownerconfig: None,
#[cfg(feature = "sev_snp")]
sev_snp: false,
}
Expand Down Expand Up @@ -4926,6 +5026,17 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
tdx_config.cpus.max_phys_bits = 52;
tdx_config.validate().unwrap();
assert_eq!(tdx_config.cpus.max_phys_bits, 52);

// mrconfigid/mrowner/mrownerconfig are rejected without tdx=on.
let mut no_tdx_config = valid_config.clone();
no_tdx_config.platform = Some(PlatformConfig {
tdx_mrconfigid: Some("01".repeat(48)),
..platform_fixture()
});
assert_eq!(
no_tdx_config.validate(),
Err(ValidationError::TdxMeasurementWithoutTdx)
);
}

let mut invalid_config = valid_config.clone();
Expand Down
10 changes: 9 additions & 1 deletion vmm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,15 @@ impl Vm {
if config.lock().unwrap().is_tdx_enabled() {
let cpuid = cpu_manager.lock().unwrap().common_cpuid();
let max_vcpus = cpu_manager.lock().unwrap().max_vcpus();
vm.tdx_init(&cpuid, max_vcpus)
let (mrconfigid, mrowner, mrownerconfig) = config
.lock()
.unwrap()
.platform
.as_ref()
.expect("TDX requires a platform configuration")
.tdx_measurements()
.map_err(Error::ConfigValidation)?;
vm.tdx_init(&cpuid, max_vcpus, &mrconfigid, &mrowner, &mrownerconfig)
.map_err(Error::InitializeTdxVm)?;
}
Ok(())
Expand Down
13 changes: 13 additions & 0 deletions vmm/src/vm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ pub struct PlatformConfig {
#[cfg(feature = "tdx")]
#[serde(default)]
pub tdx: bool,
// Optional SHA384 measurement-configuration digests, hex encoded (96 hex
// characters = 48 bytes each), forwarded verbatim to `KVM_TDX_INIT_VM` as
// the TD's `mrconfigid`, `mrowner` and `mrownerconfig` registers. When
// unset each register is left as all zeros.
#[cfg(feature = "tdx")]
#[serde(default)]
pub tdx_mrconfigid: Option<String>,
#[cfg(feature = "tdx")]
#[serde(default)]
pub tdx_mrowner: Option<String>,
#[cfg(feature = "tdx")]
#[serde(default)]
pub tdx_mrownerconfig: Option<String>,
#[cfg(feature = "sev_snp")]
#[serde(default)]
pub sev_snp: bool,
Expand Down
Loading