From e8f266088a2ad5ed283da7283031df7f95e022e7 Mon Sep 17 00:00:00 2001 From: Sree Revoori Date: Mon, 11 Dec 2023 01:35:10 +0000 Subject: [PATCH] Implement full DPE validation DPE validation does two things: 1. Checks for illegal state in DPE 2. Checks that the DPE context tree is well-formed 2 was done in a previous commit, but this commit improves upon that implementation in a few ways: 1. Previously we were returning a bool from the context tree validation function. Now we return an error code so the caller knows exactly what is wrong with the shape of the DPE context tree 2. We weren't considering simulation contexts before. Simulation contexts forming a different connected component is not an error. --- dpe/src/context.rs | 2 + dpe/src/dpe_instance.rs | 81 ----------- dpe/src/lib.rs | 1 + dpe/src/response.rs | 6 +- dpe/src/validation.rs | 306 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 313 insertions(+), 83 deletions(-) create mode 100644 dpe/src/validation.rs diff --git a/dpe/src/context.rs b/dpe/src/context.rs index c4b68fef..cbb8667e 100644 --- a/dpe/src/context.rs +++ b/dpe/src/context.rs @@ -96,6 +96,8 @@ impl Context { self.state = ContextState::Inactive; self.uses_internal_input_info = false.into(); self.uses_internal_input_dice = false.into(); + self.allow_ca = false.into(); + self.allow_x509 = false.into(); self.parent_idx = Self::ROOT_INDEX; } diff --git a/dpe/src/dpe_instance.rs b/dpe/src/dpe_instance.rs index 148723ff..9060db2a 100644 --- a/dpe/src/dpe_instance.rs +++ b/dpe/src/dpe_instance.rs @@ -394,49 +394,6 @@ impl DpeInstance { Ok(hasher.finish()?) } - /// Determines if the context array represents a valid tree by checking that - /// there is only 1 connected component and that all nodes lead up to - /// the root node. - /// - /// # Arguments - /// - /// * `root_idx` - The index of the root context - pub fn validate_context_tree(&self, root_idx: usize) -> bool { - let mut seen = [false; MAX_HANDLES]; - - // dfs from the root node and try to discover invalid subtrees - if self.detect_invalid_subtree(root_idx, &mut seen) { - return false; - } - - for (i, node_visited) in seen.iter().enumerate().take(MAX_HANDLES) { - // If a node was not seen when doing a dfs from the root, there must be multiple - // connected components or the root is not actually the root - if i != root_idx && self.contexts[i].state != ContextState::Inactive && !node_visited { - return false; - } - } - true - } - - fn detect_invalid_subtree(&self, curr_idx: usize, seen: &mut [bool; MAX_HANDLES]) -> bool { - // if the current node was already visited we have a cycle - if curr_idx >= MAX_HANDLES - || self.contexts[curr_idx].state == ContextState::Inactive - || seen[curr_idx] - { - return true; - } - seen[curr_idx] = true; - // dfs on all child nodes - for child_idx in flags_iter(self.contexts[curr_idx].children, MAX_HANDLES) { - if child_idx >= MAX_HANDLES || self.detect_invalid_subtree(child_idx, seen) { - return true; - } - } - false - } - /// Count number of contexts satisfying some predicate /// /// # Arguments @@ -861,44 +818,6 @@ pub mod tests { assert_eq!(answer, cdi_with_internal_input_dice) } - #[test] - fn test_validate_context_tree() { - let mut env = DpeEnv:: { - crypto: OpensslCrypto::new(), - platform: DefaultPlatform, - }; - let mut dpe = DpeInstance::new(&mut env, SUPPORT).unwrap(); - - dpe.contexts[0].state = ContextState::Active; - dpe.contexts[0].children = 0b100; - dpe.contexts[1].state = ContextState::Active; - dpe.contexts[1].children = 0b100; - dpe.contexts[2].state = ContextState::Active; - // validation fails on graph where child has multiple parents - assert_eq!(dpe.validate_context_tree(0), false); - - dpe.contexts[0].children = 0b10; - // validation passes on a tree in the shape of a linked-list - assert_eq!(dpe.validate_context_tree(0), true); - - dpe.contexts[2].children = 0b1; - // validation fails on circle graph - assert_eq!(dpe.validate_context_tree(0), false); - - dpe.contexts[0].children |= 0b100; - dpe.contexts[1].children = 0; - dpe.contexts[2].children = 0; - // validation passes on a complete binary tree of size 2 - assert_eq!(dpe.validate_context_tree(0), true); - - dpe.contexts[10].state = ContextState::Active; - dpe.contexts[10].children = 1 << 11 | 1 << 12; - dpe.contexts[11].state = ContextState::Active; - dpe.contexts[12].state = ContextState::Active; - // validation fails on a graph with multiple connected components - assert_eq!(dpe.validate_context_tree(0), false); - } - #[test] fn test_new_auto_init() { let mut env = DpeEnv:: { diff --git a/dpe/src/lib.rs b/dpe/src/lib.rs index 75f0a210..faabf0b2 100644 --- a/dpe/src/lib.rs +++ b/dpe/src/lib.rs @@ -14,6 +14,7 @@ pub mod context; pub mod dpe_instance; pub mod response; pub mod support; +pub mod validation; use core::mem::size_of; use response::GetProfileResp; diff --git a/dpe/src/response.rs b/dpe/src/response.rs index 78ae42d9..c0d4daa8 100644 --- a/dpe/src/response.rs +++ b/dpe/src/response.rs @@ -5,8 +5,8 @@ Abstract: DPE reponses and serialization. --*/ use crate::{ - context::ContextHandle, CURRENT_PROFILE_MAJOR_VERSION, CURRENT_PROFILE_MINOR_VERSION, - DPE_PROFILE, MAX_CERT_SIZE, MAX_HANDLES, + context::ContextHandle, validation::ValidationError, CURRENT_PROFILE_MAJOR_VERSION, + CURRENT_PROFILE_MINOR_VERSION, DPE_PROFILE, MAX_CERT_SIZE, MAX_HANDLES, }; use crypto::CryptoError; use platform::PlatformError; @@ -152,6 +152,7 @@ pub enum DpeErrorCode { MaxTcis = 0x1003, Platform(PlatformError) = 0x01000000, Crypto(CryptoError) = 0x02000000, + Validation(ValidationError) = 0x03000000, } impl From for DpeErrorCode { @@ -181,6 +182,7 @@ impl DpeErrorCode { match self { DpeErrorCode::Platform(e) => self.discriminant() | e.discriminant() as u32, DpeErrorCode::Crypto(e) => self.discriminant() | e.discriminant() as u32, + DpeErrorCode::Validation(e) => self.discriminant() | e.discriminant() as u32, _ => self.discriminant(), } } diff --git a/dpe/src/validation.rs b/dpe/src/validation.rs new file mode 100644 index 00000000..e6420bbe --- /dev/null +++ b/dpe/src/validation.rs @@ -0,0 +1,306 @@ +// Licensed under the Apache-2.0 license. + +use crate::{ + context::{Context, ContextState, ContextType}, + dpe_instance::flags_iter, + response::DpeErrorCode, + tci::TciNodeData, + DpeInstance, MAX_HANDLES, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(u32)] +/// It is possible that there are multiple issues with the DPE state. At most one will be found. +/// There is no priority on which error will be found first if there are multiple. +pub enum ValidationError { + MultipleNormalConnectedComponents = 0x0, + CyclesInTree = 0x1, + InactiveContextInvalidParent = 0x2, + InactiveContextWithChildren = 0x3, + BadContextState = 0x4, + BadContextType = 0x5, + InvalidContextWithMeasurement = 0x6, + MixedContextLocality = 0x7, + MultipleDefaultContexts = 0x8, + SimulationNotSupported = 0x9, + ParentDoesNotExist = 0xA, + InternalDiceNotSupported = 0xB, + InternalInfoNotSupported = 0xC, + ChildDoesNotExist = 0xD, + InactiveContextWithFlagSet = 0xE, + LocalityMismatch = 0xF, + DanglingRetiredContext = 0x10, + MixedContextTypeConnectedComponents = 0x11, + ChildWithMultipleParents = 0x12, + ParentChildLinksCorrupted = 0x13, +} + +impl ValidationError { + pub fn discriminant(&self) -> u16 { + // SAFETY: Because `Self` is marked `repr(u16)`, its layout is a `repr(C)` `union` + // between `repr(C)` structs, each of which has the `u16` discriminant as its first + // field, so we can read the discriminant without offsetting the pointer. + unsafe { *<*const _>::from(self).cast::() } + } +} + +pub struct DpeValidator { + pub dpe: DpeInstance, +} + +impl DpeValidator { + /// Validates that the shape of the DPE instance is well-formed and that + /// there is no illegal state present within the DPE. + pub fn validate_dpe(&self) -> Result<(), DpeErrorCode> { + self.validate_dpe_state() + .map_err(DpeErrorCode::Validation)?; + self.validate_context_forest() + .map_err(DpeErrorCode::Validation) + } + + /// Returns an error if there is any illegal state or inconsistencies + /// present within the DPE instance. + fn validate_dpe_state(&self) -> Result<(), ValidationError> { + for i in 0..MAX_HANDLES { + let context = &self.dpe.contexts[i]; + + // check support + if !self.dpe.support.simulation() && context.context_type == ContextType::Simulation { + return Err(ValidationError::SimulationNotSupported); + } + if !self.dpe.support.internal_dice() && context.uses_internal_input_dice() { + return Err(ValidationError::InternalDiceNotSupported); + } + if !self.dpe.support.internal_info() && context.uses_internal_input_info() { + return Err(ValidationError::InternalInfoNotSupported); + } + + // Check that Inactive contexts are fully destroyed + if context.state == ContextState::Inactive { + if context.parent_idx != Context::ROOT_INDEX { + return Err(ValidationError::InactiveContextInvalidParent); + } else if context.children != 0 { + return Err(ValidationError::InactiveContextWithChildren); + } else if context.tci != TciNodeData::default() { + return Err(ValidationError::InvalidContextWithMeasurement); + } else if context.uses_internal_input_dice() + || context.allow_ca() + || context.allow_x509() + || context.uses_internal_input_info() + { + return Err(ValidationError::InactiveContextWithFlagSet); + } + } else if context.state == ContextState::Active { + self.check_children_and_parent(i)?; + } else if context.state == ContextState::Retired { + self.check_children_and_parent(i)?; + // retired contexts must have exactly one child context + if flags_iter(context.children, MAX_HANDLES).count() == 0 { + return Err(ValidationError::DanglingRetiredContext); + } + } else { + return Err(ValidationError::BadContextState); + } + + if context.context_type != ContextType::Normal + && context.context_type != ContextType::Simulation + { + return Err(ValidationError::BadContextType); + } + + if context.locality != context.tci.locality { + return Err(ValidationError::LocalityMismatch); + } + } + + self.check_context_handles_per_locality()?; + + Ok(()) + } + + /// Checks that children and parent indices of a context are valid + fn check_children_and_parent(&self, idx: usize) -> Result<(), ValidationError> { + let context = &self.dpe.contexts[idx]; + // Check if parent does not exist + if context.parent_idx as usize >= MAX_HANDLES && context.parent_idx != Context::ROOT_INDEX { + return Err(ValidationError::ParentDoesNotExist); + } + // Check that parent's children contains idx + if self.dpe.contexts[context.parent_idx as usize].children & (1 << idx) == 0 { + return Err(ValidationError::ParentChildLinksCorrupted); + } + // Check if any children do not exist + for child in flags_iter(context.children, MAX_HANDLES) { + if child >= MAX_HANDLES { + return Err(ValidationError::ChildDoesNotExist); + } + // Check that each child's parent is idx + if self.dpe.contexts[child].parent_idx as usize != idx { + return Err(ValidationError::ParentChildLinksCorrupted); + } + } + Ok(()) + } + + /// Checks if there are multiple active default contexts or a mix of default + /// and non-default contexts within the same locality. + fn check_context_handles_per_locality(&self) -> Result<(), ValidationError> { + let mut localities = [(0, 0); MAX_HANDLES]; + for (i, locality) in localities.iter_mut().enumerate().take(MAX_HANDLES) { + *locality = (i, self.dpe.contexts[i].locality); + } + // group localities consecutively + localities.sort_by(|(_, l1), (_, l2)| l1.cmp(l2)); + let mut i = 0; + while i < MAX_HANDLES { + let mut default_count = 0; + let mut non_default_count = 0; + let (_, locality) = localities[i]; + while i < MAX_HANDLES && locality == localities[i].1 { + let (idx, _) = localities[i]; + if self.dpe.contexts[idx].state != ContextState::Active { + i += 1; + continue; + } + if self.dpe.contexts[idx].handle.is_default() { + default_count += 1; + } else { + non_default_count += 1; + } + i += 1 + } + if default_count > 1 { + return Err(ValidationError::MultipleDefaultContexts); + } + if default_count > 0 && non_default_count > 0 { + return Err(ValidationError::MixedContextLocality); + } + } + + Ok(()) + } + + /// Determines if the context array represents a valid collection of disjoint + /// directed connnected acyclic graphs (forest) using depth-first search. + fn validate_context_forest(&self) -> Result<(), ValidationError> { + let mut seen = [false; MAX_HANDLES]; + let mut in_degree = [0; MAX_HANDLES]; + + // count in degree of each node + for i in 0..MAX_HANDLES { + for child in flags_iter(self.dpe.contexts[i].children, MAX_HANDLES) { + in_degree[child] += 1; + } + } + + for node_in_degree in in_degree.iter().take(MAX_HANDLES) { + // all nodes must have only one parent + if *node_in_degree > 1 { + return Err(ValidationError::ChildWithMultipleParents); + } + } + + let mut normal_tree_count = 0; + for (i, node_in_degree) in in_degree.iter().enumerate().take(MAX_HANDLES) { + // dfs from all root nodes + if *node_in_degree == 0 && self.dpe.contexts[i].state != ContextState::Inactive { + let context_type = self.dpe.contexts[i].context_type; + if context_type == ContextType::Normal { + normal_tree_count += 1; + } + self.detect_invalid_subtree(i, &mut seen, context_type)?; + } + } + // there can be at most one tree of contexts with ContextType::Normal + if normal_tree_count > 1 { + return Err(ValidationError::MultipleNormalConnectedComponents); + } + + // if any node is undiscovered the graph must have a simple cycle + for (i, node_visited) in seen.iter().enumerate().take(MAX_HANDLES) { + if self.dpe.contexts[i].state != ContextState::Inactive && !node_visited { + return Err(ValidationError::CyclesInTree); + } + } + + Ok(()) + } + + fn detect_invalid_subtree( + &self, + curr_idx: usize, + seen: &mut [bool; MAX_HANDLES], + context_type: ContextType, + ) -> Result<(), ValidationError> { + // if the current node was already visited we have a cycle + if curr_idx >= MAX_HANDLES + || self.dpe.contexts[curr_idx].state == ContextState::Inactive + || seen[curr_idx] + { + return Err(ValidationError::CyclesInTree); + } + // all nodes in the tree must have the same ContextType + if self.dpe.contexts[curr_idx].context_type != context_type { + return Err(ValidationError::MixedContextTypeConnectedComponents); + } + seen[curr_idx] = true; + // dfs on all child nodes + for child_idx in flags_iter(self.dpe.contexts[curr_idx].children, MAX_HANDLES) { + self.detect_invalid_subtree(child_idx, seen, context_type)?; + } + Ok(()) + } +} + +#[cfg(test)] +pub mod tests { + use crypto::OpensslCrypto; + use platform::default::DefaultPlatform; + + use crate::{ + context::ContextState, + dpe_instance::{tests::TestTypes, DpeEnv}, + support::test::SUPPORT, + validation::{DpeValidator, ValidationError}, + DpeInstance, + }; + + #[test] + fn test_validate_context_forest() { + let mut env = DpeEnv:: { + crypto: OpensslCrypto::new(), + platform: DefaultPlatform, + }; + let mut dpe_validator = DpeValidator { + dpe: DpeInstance::new(&mut env, SUPPORT).unwrap(), + }; + + dpe_validator.dpe.contexts[0].state = ContextState::Active; + dpe_validator.dpe.contexts[0].children = 0b100; + dpe_validator.dpe.contexts[1].state = ContextState::Active; + dpe_validator.dpe.contexts[1].children = 0b100; + dpe_validator.dpe.contexts[2].state = ContextState::Active; + // validation fails on graph where child has multiple parents + assert_eq!( + dpe_validator.validate_context_forest(), + Err(ValidationError::ChildWithMultipleParents) + ); + + dpe_validator.dpe.contexts[0].children = 0b10; + // validation passes on a tree in the shape of a linked-list + assert_eq!(dpe_validator.validate_context_forest(), Ok(())); + + dpe_validator.dpe.contexts[2].children = 0b1; + // validation fails on circle graph with a simple cycle + assert_eq!( + dpe_validator.validate_context_forest(), + Err(ValidationError::CyclesInTree) + ); + + dpe_validator.dpe.contexts[0].children |= 0b100; + dpe_validator.dpe.contexts[1].children = 0; + dpe_validator.dpe.contexts[2].children = 0; + // validation passes on a complete binary tree of size 2 + assert_eq!(dpe_validator.validate_context_forest(), Ok(())); + } +}