From 18f9e29734215bd043cee444ffa680becf7326c9 Mon Sep 17 00:00:00 2001 From: Mikko Ylinen Date: Thu, 23 Jan 2025 10:28:53 +0200 Subject: [PATCH 1/3] tdx-verifier: eventlog: parse OVMF measurements properly The original "TDVF" kernel parser is not able to find the kernel measurement from the raw eventlog. Futhermore, it never implemented cmdline/initrd parsers similar to be aligned with the TdShim functionality. CoCo TDX uses Qemu direct boot. OVMF loads the kernel using GenericQemuLoadImageLib and it is located under a vendor media device path. This path also is found in the measured event desciption so look for that as the anchor to the kernel measurement digest. The kernel first runs the Linux efistub. Starting Linux 6.9, it adds the support for CC_MEASUREMENT_PROTOCOL which is used to extend RTMRs for cmdline (load image parameters) and initrd measurements. These are logged using EV_EVENT_TAG with the event description (and ID) identifying the measurement. Signed-off-by: Mikko Ylinen --- deps/verifier/src/tdx/eventlog.rs | 100 ++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/deps/verifier/src/tdx/eventlog.rs b/deps/verifier/src/tdx/eventlog.rs index 3278c9e71..ecae01c9e 100644 --- a/deps/verifier/src/tdx/eventlog.rs +++ b/deps/verifier/src/tdx/eventlog.rs @@ -2,9 +2,13 @@ use anyhow::*; use byteorder::{LittleEndian, ReadBytesExt}; use core::mem::size_of; use eventlog_rs::Eventlog; -use strum::{Display, EnumString}; +use log::{trace, warn}; +use std::result::Result::Ok; +use strum::{AsRefStr, Display, EnumString}; -#[derive(Debug, Clone, EnumString, Display)] +const KERNEL_VENMEDIA_DEVPATH_OFFSET: usize = 55; + +#[derive(AsRefStr, Copy, Debug, Clone, EnumString, Display)] pub enum MeasuredEntity { #[strum(serialize = "td_hob\0")] TdShim, @@ -12,8 +16,12 @@ pub enum MeasuredEntity { TdShimKernel, #[strum(serialize = "td_payload_info\0")] TdShimKernelParams, - #[strum(serialize = "k\0e\0r\0n\0e\0l\0")] + #[strum(serialize = "kernel")] TdvfKernel, + #[strum(serialize = "LOADED_IMAGE::LoadOptions")] + TdvfKernelParams, + #[strum(serialize = "Linux initrd")] + TdvfInitrd, } #[derive(Debug, Clone, Copy)] @@ -38,6 +46,15 @@ impl TryFrom> for CcEventLog { } } +fn read_string(raw_bytes: &[u8]) -> Result { + let utf16_string: Vec = raw_bytes + .chunks_exact(2) + .map(|c| u16::from_be_bytes(c.try_into().unwrap_or([0u8; 2]))) + .collect(); + + String::from_utf16(utf16_string.as_ref()) +} + impl CcEventLog { pub fn integrity_check(&self, rtmr_from_quote: Rtmr) -> Result<()> { let rtmr_eventlog = self.rebuild_rtmr()?; @@ -67,21 +84,68 @@ impl CcEventLog { } pub fn query_digest(&self, entity: MeasuredEntity) -> Option { - let event_desc_prefix = Self::generate_query_key_prefix(entity)?; - for event_entry in self.cc_events.log.clone() { - if event_entry.event_desc.len() < event_desc_prefix.len() { - continue; - } - if &event_entry.event_desc[..event_desc_prefix.len()] == event_desc_prefix.as_slice() { - let digest = &event_entry.digests[0].digest; - return Some(hex::encode(digest)); + match (entity, event_entry.event_type.as_str()) { + (MeasuredEntity::TdvfKernel, "EV_EFI_BOOT_SERVICES_APPLICATION") => { + let raw_bytes = &event_entry.event_desc[KERNEL_VENMEDIA_DEVPATH_OFFSET + ..KERNEL_VENMEDIA_DEVPATH_OFFSET + 2 * entity.as_ref().len()]; + + match read_string(raw_bytes) { + Ok(kernel) => { + if kernel == entity.as_ref() { + return event_entry.digests.first().map(|d| hex::encode(&d.digest)); + } + warn!("Unknown Vendor Media Device Path: {kernel}"); + } + Err(e) => warn!("Failed to read UEFI_IMAGE_LOAD_EVENT: {e}"), + } + } + (MeasuredEntity::TdvfKernelParams | MeasuredEntity::TdvfInitrd, "EV_EVENT_TAG") => { + let offset = size_of::(); + + // Read the tagged event size after the first u32 (=Event ID) + let event_size = (&event_entry.event_desc[offset..2 * offset]) + .read_u32::() + .unwrap_or_default() as usize; + + // Read the tagged event after the event size + match String::from_utf8( + event_entry.event_desc[offset * 2..offset * 2 + event_size - 1].to_vec(), + ) { + Ok(event) => { + if event == entity.as_ref() { + return event_entry.digests.first().map(|d| hex::encode(&d.digest)); + } + warn!("Event {event:?} did not match with MeasuredEntity {entity:?}"); + } + + Err(e) => warn!("Failed to parse tagged event: {e}"), + } + } + ( + MeasuredEntity::TdShim + | MeasuredEntity::TdShimKernel + | MeasuredEntity::TdShimKernelParams, + _, + ) => { + let event_desc_prefix = + Self::generate_query_key_prefix(entity).unwrap_or_default(); + + if event_entry.event_desc.len() < event_desc_prefix.len() { + continue; + } + if &event_entry.event_desc[..event_desc_prefix.len()] + == event_desc_prefix.as_slice() + { + return event_entry.digests.first().map(|d| hex::encode(&d.digest)); + } + } + (me, ev) => trace!("Event {ev:?} did not match with MeasuredEntity {me:?}"), } } None } - #[allow(dead_code)] pub fn query_event_data(&self, entity: MeasuredEntity) -> Option> { let event_desc_prefix = Self::generate_query_key_prefix(entity)?; @@ -96,29 +160,25 @@ impl CcEventLog { None } - #[allow(unused_assignments)] fn generate_query_key_prefix(entity: MeasuredEntity) -> Option> { - let mut event_desc_prefix = Vec::new(); match entity { MeasuredEntity::TdShimKernel => { // Event data is in UEFI_PLATFORM_FIRMWARE_BLOB2 format // Defined in TCG PC Client Platform Firmware Profile Specification section // 'UEFI_PLATFORM_FIRMWARE_BLOB Structure Definition' let entity_name = entity.to_string(); - event_desc_prefix = vec![entity_name.as_bytes().len() as u8]; + let mut event_desc_prefix = vec![entity_name.len() as u8]; event_desc_prefix.extend_from_slice(entity_name.as_bytes()); - } - MeasuredEntity::TdvfKernel => { - event_desc_prefix = entity.to_string().as_bytes().to_vec(); + Some(event_desc_prefix) } MeasuredEntity::TdShim | MeasuredEntity::TdShimKernelParams => { // Event data is in TD_SHIM_PLATFORM_CONFIG_INFO format // Defined in td-shim spec 'Table 3.5-4 TD_SHIM_PLATFORM_CONFIG_INFO' // link: https://github.com/confidential-containers/td-shim/blob/main/doc/tdshim_spec.md - event_desc_prefix = entity.to_string().as_bytes().to_vec(); + Some(entity.to_string().as_bytes().to_vec()) } + _ => None, } - Some(event_desc_prefix) } } From c5a0f316305a62e0c020bc5a59361e74334d147c Mon Sep 17 00:00:00 2001 From: Mikko Ylinen Date: Thu, 23 Jan 2025 10:30:01 +0200 Subject: [PATCH 2/3] tdx-verifier: eventlog: add unit tests to parse OVMF originated CCEL Add tests to cover the Tdvf* MeasuredEntity enums. It also requires an updated CCEL raw blob which is originated from Kata 3.13.0 based boot with the initrd set as the rootfs. Signed-off-by: Mikko Ylinen --- deps/verifier/src/tdx/eventlog.rs | 29 ++++++++++++++++++++++++- deps/verifier/test_data/CCEL_data_ovmf | Bin 0 -> 65536 bytes 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 deps/verifier/test_data/CCEL_data_ovmf diff --git a/deps/verifier/src/tdx/eventlog.rs b/deps/verifier/src/tdx/eventlog.rs index ecae01c9e..55ac73e9a 100644 --- a/deps/verifier/src/tdx/eventlog.rs +++ b/deps/verifier/src/tdx/eventlog.rs @@ -249,7 +249,7 @@ mod tests { } #[test] - fn test_query_digest() { + fn test_query_digest_tdshim() { let ccel_bin = fs::read("./test_data/CCEL_data").unwrap(); let ccel = CcEventLog::try_from(ccel_bin).unwrap(); @@ -268,4 +268,31 @@ mod tests { "64ed1e5a47e8632f80faf428465bd987af3e8e4ceb10a5a9f387b6302e30f4993bded2331f0691c4a38ad34e4cbbc627".to_string() ); } + + #[test] + fn test_query_digest_ovmf() { + let ccel_bin = fs::read("./test_data/CCEL_data_ovmf").expect("open test data"); + let ccel = CcEventLog::try_from(ccel_bin).expect("parse CCEL eventlog"); + + let kernel_hash = ccel.query_digest(MeasuredEntity::TdvfKernel); + let kernel_params_hash = ccel.query_digest(MeasuredEntity::TdvfKernelParams); + let initrd_hash = ccel.query_digest(MeasuredEntity::TdvfInitrd); + + assert!(kernel_hash.is_some()); + assert!(kernel_params_hash.is_some()); + assert!(initrd_hash.is_some()); + + assert_eq!( + kernel_hash.unwrap(), + "a2ccae1e7d6c668ca325bb09c882d8ce44d26d714ba6f58d2e8083fe291a704646afe24a2368bca3341728d78ec80a80".to_string() + ); + assert_eq!( + initrd_hash.unwrap(), + "b15af9286108d3d8c9f794a51409e55bad6334f5d96a1e4469f8df2d75fd69aac648d939e13daf6800e82e6c1f6628c4".to_string() + ); + assert_eq!( + kernel_params_hash.unwrap(), + "4230f84885a6f3f305e91a1955045398bd9edd8ffd2aaf2aab8ad3ac53476c4ac82a3675ef559c4ae949a06e84119fc2".to_string() + ); + } } diff --git a/deps/verifier/test_data/CCEL_data_ovmf b/deps/verifier/test_data/CCEL_data_ovmf new file mode 100644 index 0000000000000000000000000000000000000000..3639a25ac1e719006badca09cc47074ae56b8775 GIT binary patch literal 65536 zcmeIzdsGuw9tZFXWDx?fYYmE`Y!Isukj0ilp(`aA&_ETp2(q?P0tqlSBnSymUybP6 zRe87zR@&tyh!09cF{_q`WmQsfmD7cmS6g2PQBW5ZF=4yl&Lln#SDox;tguIXpsa)y(hUqp-VrfC2*%NO^X8+#HUBw zu9jr27RSq^##l^-+Rqva9({FYWnujFdS!I5FWIYRE*pPmzsThi-ZH1i?vP7$Ij8cz z(pTSkOEhcCIJM8=T7@oV$-aI8|Ir-VEz1^1efr^8^xUMq0dxfS&nr5}f4&cF7G z)BH)YT29DG$SM+UmZft_^N@B9N414&EEj(e@I~{9AFNx}^!#bST6g+t-_)sAGlIVP zx~s7xMmSV@RihhSFi95kDHTpa{v(gg;fP%cC;e(pwfbg{`iK(JlS<4n8<< z|0wQ&*S6)+Z>>GEBQW}C&ETvX3z=&ERyV6FV9948m5sgMPEC?3%&U=*c(ZC8FUu_+ z=mtNpb~RL}g98gS+j>&icYmv17169eXn#^%((&icqp^QYX79Ye{`hEiE>n$p7V~PD zELp}pEc$NK%=cd^IPK`!zOY4ie5--jU+Wh8UU2uwTO&=^Z0$KFuqcah)PA+0b?9Gl zH#B9h?s~wCWEwM?nF*#SxmwGa4`YPURj*%hsS(=e-Q=whW$r(0w||i@JGO4@#@U#& ziXPoyZtuA_AOGNf(9@P4DOkEn#1jfu3nqs0D=z8ha5{Io7A|}zQR96lb<-iIIA_1o z;$@EI3$Du&^}E*V68nd#P*cag0QqenZElbh*|kcui}_x!+rcEYma zk~F;=H|&(N@XYzrrY=LlH!Rkx0FDO44$~)zkU(Q@^5S#hO@EP@&6dXYBvFJS0+LE^gs`S?No&)Y&36qx z5!Hi%eYMgV2Uylz;!^Dy%FVC}*)HFG>T#m$Vn&InWy%Ys%<|s4#U3l2*Bn`~bFIs* zx_{&*ZFI5V_iig;7Zs_qI=X_M_ov_h~f zBp~2jg;-*G&UE8{ zlGEtPa}j6t2;CkGikx=xrL3)@5W~pG$`e$)c1d(nx7q29+R9=ZWMQ3KRIm zH_7(0xVUccwqupICMSh=B*?b@tZdh=1*Q8qIgf*M1N*61$#+o~W`;>Uzg0hRwNo;0 za+2`xNtLQJxm3c-q?F0LNL2!zGyN$V1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009X6UkUsh D?68Y6 literal 0 HcmV?d00001 From 0f5d08ff77b946ed3a362a1901d6ea6a060a327b Mon Sep 17 00:00:00 2001 From: Mikko Ylinen Date: Thu, 23 Jan 2025 10:31:03 +0200 Subject: [PATCH 3/3] tdx-verifier: add OVMF kernel params and initrd to tdx.ccel claims Add all of the Tdvf* MeasuredEntity results to the TDX claims under ccel. Signed-off-by: Mikko Ylinen --- deps/verifier/src/tdx/claims.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/deps/verifier/src/tdx/claims.rs b/deps/verifier/src/tdx/claims.rs index 4c5d65683..c310b3166 100644 --- a/deps/verifier/src/tdx/claims.rs +++ b/deps/verifier/src/tdx/claims.rs @@ -232,6 +232,32 @@ fn parse_ccel(ccel: CcEventLog, ccel_map: &mut Map) -> Result<()> } } + // Digest of kernel cmdline using TDVF + match ccel.query_digest(MeasuredEntity::TdvfKernelParams) { + Some(cmdline_digest) => { + ccel_map.insert( + "cmdline".to_string(), + serde_json::Value::String(cmdline_digest), + ); + } + _ => { + warn!("No tdvf kernel cmdline hash in CCEL"); + } + } + + // Digest of initrd using TDVF + match ccel.query_digest(MeasuredEntity::TdvfInitrd) { + Some(initrd_digest) => { + ccel_map.insert( + "initrd".to_string(), + serde_json::Value::String(initrd_digest), + ); + } + _ => { + warn!("No tdvf initrd hash in CCEL"); + } + } + // Map of Kernel Parameters match ccel.query_event_data(MeasuredEntity::TdShimKernelParams) { Some(config_info) => { @@ -245,7 +271,7 @@ fn parse_ccel(ccel: CcEventLog, ccel_map: &mut Map) -> Result<()> ); } _ => { - warn!("No kernel parameters in CCEL"); + warn!("No td-shim kernel parameters in CCEL"); } }