diff --git a/error/src/lib.rs b/error/src/lib.rs index e81e0db55e..62393a1f92 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -369,6 +369,8 @@ impl CaliptraError { CaliptraError::new_const(0x000E0027); pub const RUNTIME_LDEVID_CERT_HANDOFF_FAILED: CaliptraError = CaliptraError::new_const(0x000E0028); + pub const RUNTIME_CONTEXT_TAG_VALIDATION_FAILED: CaliptraError = + CaliptraError::new_const(0x000E0029); /// FMC Errors pub const FMC_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000F0001); diff --git a/runtime/README.md b/runtime/README.md index 832764155b..5cd674a5be 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -557,6 +557,7 @@ this case, the new Runtime Firmware must: Runtime Journey PCR (TYPE = RTJM, “Internal TCI” flag is set) matches the “Latest” Runtime PCR value from PCRX 1. Ensure `SHA384_HASH(0x00..00, TCI from SRAM) == RT_FW_JOURNEY_PCR` +1. Check that retired and inactive contexts do not have tags 1. If any validations fail, runtime firmware will execute the `DISABLE_ATTESTATION` command. diff --git a/runtime/doc/test-coverage.md b/runtime/doc/test-coverage.md index 37801eda01..432a7b1410 100644 --- a/runtime/doc/test-coverage.md +++ b/runtime/doc/test-coverage.md @@ -100,6 +100,7 @@ Attempts to add a duplicate tag and verifies that it fails | **test_duplicate_ta Calls the dpe_get_tagged_tci mailbox command with a tag that does not exist and checks that it fails | **test_get_tagged_tci_on_non_existent_tag** | RUNTIME_TAGGING_FAILURE Attempts to tag an inactive context and verifies that it fails | **test_tagging_inactive_context** | RUNTIME_TAGGING_FAILURE Tags the default context, destroys the default context, and checks that the dpe_get_tagged_tci mailbox command fails on the default context | **test_tagging_destroyed_context** | RUNTIME_TAGGING_FAILURE +Tags the default context, retires the default context, and checks that the dpe_get_tagged_tci mailbox command fails on the default context | **test_tagging_retired_context** | RUNTIME_TAGGING_FAILURE

# **DPE Verification Tests** @@ -147,4 +148,5 @@ Test PL1 pauser active context limits | N/A | N/A Check that measurements are stored in DPE when StashMeasurement is called | N/A | N/A Verify that DPE attestation flow fails after DisableAttestation is called | N/A | N/A Check that mailbox valid pausers are measured into DPE upon RT startup | N/A | N/A -Check that the RT alias key is different from the key signing DPE certs | N/A | N/A \ No newline at end of file +Check that the RT alias key is different from the key signing DPE certs | N/A | N/A +Test context tag validity upon warm/update reset | N/A | N/A \ No newline at end of file diff --git a/runtime/src/drivers.rs b/runtime/src/drivers.rs index 94967e641c..eaebb2479e 100644 --- a/runtime/src/drivers.rs +++ b/runtime/src/drivers.rs @@ -24,6 +24,7 @@ use caliptra_registers::{ }; use dpe::context::{Context, ContextState}; use dpe::tci::TciMeasurement; +use dpe::MAX_HANDLES; use dpe::{ commands::{CommandExecution, DeriveChildCmd, DeriveChildFlags}, context::ContextHandle, @@ -94,10 +95,12 @@ impl Drivers { } ResetReason::UpdateReset => { Self::validate_dpe_structure(&mut drivers)?; + Self::validate_context_tags(&mut drivers)?; Self::update_dpe_rt_journey(&mut drivers)?; } ResetReason::WarmReset => { Self::validate_dpe_structure(&mut drivers)?; + Self::validate_context_tags(&mut drivers)?; Self::check_dpe_rt_journey_unchanged(&mut drivers)?; } ResetReason::Unknown => { @@ -229,6 +232,22 @@ impl Drivers { Ok(()) } + fn validate_context_tags(mut drivers: &mut Drivers) -> CaliptraResult<()> { + let pdata = drivers.persistent_data.get(); + let context_has_tag = pdata.context_has_tag; + let context_tags = pdata.context_tags; + let dpe = &pdata.dpe; + + for i in (0..MAX_HANDLES) { + if dpe.contexts[i].state != ContextState::Active + && (context_has_tag[i].get() || context_tags[i] != 0) + { + return Err(CaliptraError::RUNTIME_CONTEXT_TAG_VALIDATION_FAILED); + } + } + Ok(()) + } + // Caliptra Name serialNumber fields are sha256 digests pub fn compute_rt_alias_sn(&mut self) -> CaliptraResult { let key = self.persistent_data.get().fht.rt_dice_pub_key.to_der(); diff --git a/runtime/src/invoke_dpe.rs b/runtime/src/invoke_dpe.rs index d917d3b758..98aea562d6 100644 --- a/runtime/src/invoke_dpe.rs +++ b/runtime/src/invoke_dpe.rs @@ -79,7 +79,10 @@ impl InvokeDpeCmd { { return Err(CaliptraError::RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL); } - cmd.execute(dpe, &mut env, locality) + let derive_child_resp = cmd.execute(dpe, &mut env, locality); + // clear tags for retired contexts + Self::clear_tags_for_non_active_contexts(dpe, context_has_tag, context_tags); + derive_child_resp } Command::CertifyKey(cmd) => { // PL1 cannot request X509 @@ -93,16 +96,7 @@ impl InvokeDpeCmd { Command::DestroyCtx(cmd) => { let destroy_ctx_resp = cmd.execute(dpe, &mut env, locality); // clear tags for destroyed contexts - (0..MAX_HANDLES).for_each(|i| { - if i < dpe.contexts.len() - && i < context_has_tag.len() - && i < context_tags.len() - && dpe.contexts[i].state != ContextState::Active - { - context_has_tag[i] = U8Bool::new(false); - context_tags[i] = 0; - } - }); + Self::clear_tags_for_non_active_contexts(dpe, context_has_tag, context_tags); destroy_ctx_resp } Command::Sign(cmd) => cmd.execute(dpe, &mut env, locality), @@ -172,4 +166,21 @@ impl InvokeDpeCmd { fn is_caller_pl1(pl0_pauser: u32, flags: u32, locality: u32) -> bool { flags & PL0_PAUSER_FLAG == 0 && locality != pl0_pauser } + + fn clear_tags_for_non_active_contexts( + dpe: &mut DpeInstance, + context_has_tag: &mut [U8Bool; MAX_HANDLES], + context_tags: &mut [u32; MAX_HANDLES], + ) { + (0..MAX_HANDLES).for_each(|i| { + if i < dpe.contexts.len() + && i < context_has_tag.len() + && i < context_tags.len() + && dpe.contexts[i].state != ContextState::Active + { + context_has_tag[i] = U8Bool::new(false); + context_tags[i] = 0; + } + }); + } } diff --git a/runtime/tests/runtime_integration_tests/test_tagging.rs b/runtime/tests/runtime_integration_tests/test_tagging.rs index bd5e4dc0f5..6653ad974c 100644 --- a/runtime/tests/runtime_integration_tests/test_tagging.rs +++ b/runtime/tests/runtime_integration_tests/test_tagging.rs @@ -6,9 +6,10 @@ use caliptra_common::mailbox_api::{ }; use caliptra_hw_model::HwModel; use dpe::{ - commands::{Command, DestroyCtxCmd}, + commands::{Command, DeriveChildCmd, DeriveChildFlags, DestroyCtxCmd}, context::ContextHandle, response::Response, + DPE_PROFILE, }; use zerocopy::FromBytes; @@ -206,3 +207,55 @@ fn test_tagging_destroyed_context() { resp, ); } + +#[test] +fn test_tagging_retired_context() { + let mut model = run_rt_test(None, None, None); + + // Tag default context + let mut cmd = MailboxReq::TagTci(TagTciReq { + hdr: MailboxReqHeader { chksum: 0 }, + handle: DEFAULT_HANDLE, + tag: TAG, + }); + cmd.populate_chksum().unwrap(); + let _ = model + .mailbox_execute(u32::from(CommandId::DPE_TAG_TCI), cmd.as_bytes().unwrap()) + .unwrap() + .expect("We expected a response"); + + // retire tagged context via DeriveChild + let derive_child_cmd = DeriveChildCmd { + handle: ContextHandle::default(), + data: [0u8; DPE_PROFILE.get_hash_size()], + flags: DeriveChildFlags::MAKE_DEFAULT, + tci_type: 0, + target_locality: 0, + }; + let resp = execute_dpe_cmd( + &mut model, + &mut Command::DeriveChild(derive_child_cmd), + DpeResult::Success, + ); + let Some(Response::DeriveChild(_)) = resp else { + panic!("Wrong response type!"); + }; + + // check that we cannot get tagged tci for a retired context + let mut cmd = MailboxReq::GetTaggedTci(GetTaggedTciReq { + hdr: MailboxReqHeader { chksum: 0 }, + tag: TAG, + }); + cmd.populate_chksum().unwrap(); + let resp = model + .mailbox_execute( + u32::from(CommandId::DPE_GET_TAGGED_TCI), + cmd.as_bytes().unwrap(), + ) + .unwrap_err(); + assert_error( + &mut model, + caliptra_drivers::CaliptraError::RUNTIME_TAGGING_FAILURE, + resp, + ); +}