Skip to content

Commit

Permalink
Add PAUSER Privilege level tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sree-revoori1 authored and jhand2 committed Dec 8, 2023
1 parent 5ee64bf commit dd36931
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 96 deletions.
14 changes: 8 additions & 6 deletions runtime/doc/test-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ Tests that failed DPE command populates mbox header with correct error code | **
Test Scenario| Test Name | Runtime Error Code
---|---|---
Checks the limit on the number of active DPE contexts belonging to PL0 by calling derive_child via the invoke_dpe mailbox command with the RETAINS_PARENT flag set | **test_pl0_derive_child_dpe_context_thresholds** | RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED
Checks the limit on the number of active DPE contexts belonging to PL1 by calling derive_child via the invoke_dpe mailbox command with the RETAINS_PARENT flag set | **test_pl1_derive_child_dpe_context_thresholds** | RUNTIME_PL1_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED
Checks the limit on the number of active DPE contexts belonging to PL0 by calling initialize_context via the invoke_dpe mailbox command with the SIMULATION flag set | **test_pl0_init_ctx_dpe_context_thresholds** | RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED
Checks the limit on the number of active DPE contexts belonging to PL1 by calling initialize_context via the invoke_dpe mailbox command with the SIMULATION flag set | **test_pl1_init_ctx_dpe_context_thresholds** | RUNTIME_PL1_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED
Checks that PopulateIdevIdCert cannot be called from PL1 | **test_populate_idev_cannot_be_called_from_pl1** | RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL
Checks that InvokeDpe::DeriveChild cannot be called from PL1 if it attempts to change locality to P0 | **test_derive_child_cannot_be_called_from_pl1_if_changes_locality_to_pl0** | RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL
Checks that InvokeDpe::CertifyKey cannot be called from PL1 if it requests X509 | **test_certify_key_x509_cannot_be_called_from_pl1** | RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL
Checks the limit on the number of active DPE contexts belonging to PL0 by calling the stash_measurement mailbox command | **test_stash_measurement_pl_context_thresholds** | RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED
Checks the limit on the number of active DPE contexts belonging to PL0 by adding measurements to the measurement log | **test_measurement_log_pl_context_threshold** | RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED

<br><br>
# **Tagging Tests**
Expand Down Expand Up @@ -132,19 +140,13 @@ Test Scenario| Test Name | Runtime Error Code
---|---|---
Test DPE structure validation upon update reset | N/A | N/A
Trigger warm reset and check that DPE structure is valid upon RT initialization | N/A | N/A
Check that calling DeriveChild via InvokeDpe more than PL0_DPE_ACTIVE_CONTEXT_THRESHOLD times succeeds if the RETAINS_PARENT flag is not set | N/A | N/A
Test PL context limits with InitializeContext with the SIMULATION flag set, via the InvokeDpe mailbox command | N/A | N/A
Verify the RT Journey PCR on a warm reset | N/A | N/A
Check that the RT Journey PCR was updated correctly on update reset | N/A | N/A
Check that attestation is disabled if mbox_busy during a warm reset | N/A | N/A
Check that measurements in the measurement log are added to DPE upon initializing drivers | N/A | N/A
Check that PCR31 is updated in StashMeasurement | N/A | N/A
Test GetIdevCert cmd fails if provided bad signature or tbs | N/A | N/A
Add higher fidelity HMAC test that verifies correctness of HMAC tag based on UDS | N/A | N/A
Check that PopulateIdevIdCert cannot be called from PL1 | N/A | N/A
Check that InvokeDpe::DeriveChild cannot be called from PL1 if it attempts to change locality to P0 | N/A | N/A
Check that InvokeDpe::CertifyKey cannot be called from PL1 if it requests X509 | N/A | N/A
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
Expand Down
20 changes: 19 additions & 1 deletion runtime/src/dpe_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use dpe::{
x509::{CertWriter, DirectoryString, Name},
DPE_PROFILE,
};
use platform::{Platform, PlatformError, MAX_CHUNK_SIZE};
use platform::{Platform, PlatformError, MAX_CHUNK_SIZE, MAX_SN_SIZE};

use crate::MAX_CERT_CHAIN_SIZE;

Expand Down Expand Up @@ -96,6 +96,24 @@ impl Platform for DpePlatform<'_> {
Ok(issuer_len)
}

fn get_issuer_sn(&mut self, out: &mut [u8; MAX_SN_SIZE]) -> Result<usize, PlatformError> {
let sn = self.hashed_rt_pub_key.bytes();
let size = min(MAX_SN_SIZE, sn.len());
// Prevents potential panic
if size > out.len() || size > sn.len() {
return Err(PlatformError::IssuerNameError(0));
}
out[..size].copy_from_slice(&sn[..size]);

// Ensure the encoded integer is positive, and that the first octet
// is non-zero (otherwise it will be considered padding, and the integer
// will fail to parse if the MSB of the second octet is zero).
out[0] &= !0x80;
out[0] |= 0x04;

Ok(size)
}

fn write_str(&mut self, str: &str) -> Result<(), PlatformError> {
cprintln!("{}", str);
Ok(())
Expand Down
74 changes: 72 additions & 2 deletions runtime/src/drivers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ pub use crate::fips::{fips_self_test_cmd, fips_self_test_cmd::SelfTestStatus};

use crate::{
dice, CptraDpeTypes, DisableAttestationCmd, DpeCrypto, DpePlatform, Mailbox, DPE_SUPPORT,
MAX_CERT_CHAIN_SIZE,
MAX_CERT_CHAIN_SIZE, PL0_DPE_ACTIVE_CONTEXT_THRESHOLD, PL0_PAUSER_FLAG,
PL1_DPE_ACTIVE_CONTEXT_THRESHOLD,
};

use arrayvec::ArrayVec;
Expand All @@ -24,14 +25,14 @@ use caliptra_registers::{
};
use dpe::context::{Context, ContextState};
use dpe::tci::TciMeasurement;
use dpe::MAX_HANDLES;
use dpe::{
commands::{CommandExecution, DeriveChildCmd, DeriveChildFlags},
context::ContextHandle,
dpe_instance::{DpeEnv, DpeInstance, DpeTypes},
support::Support,
DPE_PROFILE,
};
use dpe::{U8Bool, MAX_HANDLES};

use crypto::{AlgLen, Crypto, CryptoBuf, Hasher};
use zerocopy::AsBytes;
Expand Down Expand Up @@ -335,6 +336,17 @@ impl Drivers {
let num_measurements = drivers.persistent_data.get().fht.meas_log_index as usize;
let measurement_log = drivers.persistent_data.get().measurement_log;
for measurement_log_entry in measurement_log.iter().take(num_measurements) {
// Check that adding this measurement to DPE doesn't cause
// the PL0 context threshold to be exceeded.
let pl0_pauser = drivers.persistent_data.get().manifest1.header.pl0_pauser;
let flags = drivers.persistent_data.get().manifest1.header.flags;
Self::is_dpe_context_threshold_exceeded(
pl0_pauser_locality,
flags,
pl0_pauser_locality,
&dpe,
)?;

let measurement_data = measurement_log_entry.pcr_entry.measured_data();
let tci_type = u32::from_be_bytes(measurement_log_entry.metadata);
DeriveChildCmd {
Expand Down Expand Up @@ -407,4 +419,62 @@ impl Drivers {
drivers.cert_chain = cert_chain;
Ok(())
}

/// Counts the number of non-inactive DPE contexts and returns an error
/// if this number is equal to the active context threshold corresponding
/// to the privilege level of the caller.
///
/// This function should only ever be called right before attempting to
/// create a new context in DPE in order to prevent DPE from breaching
/// the active context limit.
pub fn is_dpe_context_threshold_exceeded(
pl0_pauser: u32,
flags: u32,
locality: u32,
dpe: &DpeInstance,
) -> CaliptraResult<()> {
let used_pl0_dpe_context_count = dpe
.count_contexts(|c: &Context| {
c.state != ContextState::Inactive && c.locality == pl0_pauser
})
.map_err(|_| CaliptraError::RUNTIME_INTERNAL)?;
// the number of used pl1 dpe contexts is the total number of used contexts
// minus the number of used pl0 contexts, since a context can only be activated
// from pl0 or from pl1. Here, used means an active or retired context.
let used_pl1_dpe_context_count = dpe
.count_contexts(|c: &Context| c.state != ContextState::Inactive)
.map_err(|_| CaliptraError::RUNTIME_INTERNAL)?
- used_pl0_dpe_context_count;
if Self::is_caller_pl1(pl0_pauser, flags, locality)
&& used_pl1_dpe_context_count == PL1_DPE_ACTIVE_CONTEXT_THRESHOLD
{
return Err(CaliptraError::RUNTIME_PL1_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED);
} else if !Self::is_caller_pl1(pl0_pauser, flags, locality)
&& used_pl0_dpe_context_count == PL0_DPE_ACTIVE_CONTEXT_THRESHOLD
{
return Err(CaliptraError::RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED);
}
Ok(())
}

pub fn is_caller_pl1(pl0_pauser: u32, flags: u32, locality: u32) -> bool {
flags & PL0_PAUSER_FLAG == 0 && locality != pl0_pauser
}

pub 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;
}
});
}
}
70 changes: 9 additions & 61 deletions runtime/src/invoke_dpe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use dpe::{
},
context::{Context, ContextState},
response::{Response, ResponseHdr},
DpeInstance, U8Bool, MAX_HANDLES,
DpeInstance, MAX_HANDLES,
};
use zerocopy::{AsBytes, FromBytes};

Expand Down Expand Up @@ -64,30 +64,29 @@ impl InvokeDpeCmd {
Command::InitCtx(cmd) => {
// InitCtx can only create new contexts if they are simulation contexts.
if InitCtxCmd::flag_is_simulation(&cmd) {
Self::pl_context_threshold_exceeded(pl0_pauser, flags, locality, dpe)?;
Drivers::is_dpe_context_threshold_exceeded(
pl0_pauser, flags, locality, dpe,
)?;
}
cmd.execute(dpe, &mut env, locality)
}
Command::DeriveChild(cmd) => {
// If retain parent is not set for the DeriveChildCmd, the change in number of contexts is 0.
if DeriveChildCmd::retains_parent(&cmd) {
Self::pl_context_threshold_exceeded(pl0_pauser, flags, locality, dpe)?;
}
Drivers::is_dpe_context_threshold_exceeded(pl0_pauser, flags, locality, dpe)?;
if DeriveChildCmd::changes_locality(&cmd)
&& cmd.target_locality == pl0_pauser
&& Self::is_caller_pl1(pl0_pauser, flags, locality)
&& Drivers::is_caller_pl1(pl0_pauser, flags, locality)
{
return Err(CaliptraError::RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL);
}
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);
Drivers::clear_tags_for_non_active_contexts(dpe, context_has_tag, context_tags);
derive_child_resp
}
Command::CertifyKey(cmd) => {
// PL1 cannot request X509
if cmd.format == CertifyKeyCmd::FORMAT_X509
&& Self::is_caller_pl1(pl0_pauser, flags, locality)
&& Drivers::is_caller_pl1(pl0_pauser, flags, locality)
{
return Err(CaliptraError::RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL);
}
Expand All @@ -96,7 +95,7 @@ impl InvokeDpeCmd {
Command::DestroyCtx(cmd) => {
let destroy_ctx_resp = cmd.execute(dpe, &mut env, locality);
// clear tags for destroyed contexts
Self::clear_tags_for_non_active_contexts(dpe, context_has_tag, context_tags);
Drivers::clear_tags_for_non_active_contexts(dpe, context_has_tag, context_tags);
destroy_ctx_resp
}
Command::Sign(cmd) => cmd.execute(dpe, &mut env, locality),
Expand Down Expand Up @@ -132,55 +131,4 @@ impl InvokeDpeCmd {
Err(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY)
}
}

fn pl_context_threshold_exceeded(
pl0_pauser: u32,
flags: u32,
locality: u32,
dpe: &DpeInstance,
) -> CaliptraResult<()> {
let used_pl0_dpe_context_count = dpe
.count_contexts(|c: &Context| {
c.state != ContextState::Inactive && c.locality == pl0_pauser
})
.map_err(|_| CaliptraError::RUNTIME_INTERNAL)?;
// the number of used pl1 dpe contexts is the total number of used contexts
// minus the number of used pl0 contexts, since a context can only be activated
// from pl0 or from pl1. Here, used means an active or retired context.
let used_pl1_dpe_context_count = dpe
.count_contexts(|c: &Context| c.state != ContextState::Inactive)
.map_err(|_| CaliptraError::RUNTIME_INTERNAL)?
- used_pl0_dpe_context_count;
if Self::is_caller_pl1(pl0_pauser, flags, locality)
&& used_pl1_dpe_context_count == Self::PL1_DPE_ACTIVE_CONTEXT_THRESHOLD
{
return Err(CaliptraError::RUNTIME_PL1_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED);
} else if !Self::is_caller_pl1(pl0_pauser, flags, locality)
&& used_pl0_dpe_context_count == Self::PL0_DPE_ACTIVE_CONTEXT_THRESHOLD
{
return Err(CaliptraError::RUNTIME_PL0_USED_DPE_CONTEXT_THRESHOLD_EXCEEDED);
}
Ok(())
}

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;
}
});
}
}
2 changes: 2 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub const DPE_SUPPORT: Support = Support::all();
pub const MAX_CERT_CHAIN_SIZE: usize = 4096;

pub const PL0_PAUSER_FLAG: u32 = 1;
pub const PL0_DPE_ACTIVE_CONTEXT_THRESHOLD: usize = 8;
pub const PL1_DPE_ACTIVE_CONTEXT_THRESHOLD: usize = 16;

pub struct CptraDpeTypes;

Expand Down
19 changes: 14 additions & 5 deletions runtime/src/stash_measurement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ impl StashMeasurementCmd {
),
};

let pl0_pauser = pdata.manifest1.header.pl0_pauser;
let flags = pdata.manifest1.header.flags;
let locality = drivers.mbox.user();
// Call DeriveChild to add the measurement to DPE
// Check that adding this measurement to DPE doesn't cause
// the PL0 context threshold to be exceeded.
Drivers::is_dpe_context_threshold_exceeded(
pl0_pauser, flags, locality, &pdata.dpe,
)?;
let pdata_mut = drivers.persistent_data.get_mut();
let derive_child_resp = DeriveChildCmd {
handle: ContextHandle::default(),
data: cmd.measurement,
Expand All @@ -51,10 +58,12 @@ impl StashMeasurementCmd {
tci_type: u32::from_be_bytes(cmd.metadata),
target_locality: locality,
}
.execute(
&mut drivers.persistent_data.get_mut().dpe,
&mut env,
locality,
.execute(&mut pdata_mut.dpe, &mut env, locality);
// clear tags for retired contexts
Drivers::clear_tags_for_non_active_contexts(
&mut pdata_mut.dpe,
&mut pdata_mut.context_has_tag,
&mut pdata_mut.context_tags,
);

match derive_child_resp {
Expand Down
9 changes: 9 additions & 0 deletions runtime/tests/runtime_integration_tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ use openssl::{
};
use zerocopy::{AsBytes, FromBytes};

pub const TEST_LABEL: [u8; 48] = [
48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25,
24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
];
pub const TEST_DIGEST: [u8; 48] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
];

// Run a test which boots ROM -> FMC -> test_bin. If test_bin_name is None,
// run the production runtime image.
pub fn run_rt_test(
Expand Down
11 changes: 1 addition & 10 deletions runtime/tests/runtime_integration_tests/test_invoke_dpe.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Licensed under the Apache-2.0 license.

use crate::common::{execute_dpe_cmd, run_rt_test, DpeResult};
use crate::common::{execute_dpe_cmd, run_rt_test, DpeResult, TEST_DIGEST, TEST_LABEL};
use caliptra_common::mailbox_api::{InvokeDpeReq, MailboxReq, MailboxReqHeader};
use caliptra_drivers::CaliptraError;
use caliptra_hw_model::HwModel;
Expand All @@ -21,15 +21,6 @@ use openssl::{
nid::Nid,
};

const TEST_LABEL: [u8; 48] = [
48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25,
24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
];
const TEST_DIGEST: [u8; 48] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
];

#[test]
fn test_invoke_dpe_get_profile_cmd() {
let mut model = run_rt_test(None, None, None);
Expand Down
Loading

0 comments on commit dd36931

Please sign in to comment.