From 134c02fdc81ed27eb4e8c815eddf046d1abbb366 Mon Sep 17 00:00:00 2001 From: Tiziano Santoro Date: Fri, 29 May 2020 11:46:09 +0100 Subject: [PATCH] Take into account downgrade privilege when reading Previously downgrade was only allowed when writing to lower labels, but not when reading from higher labels. This makes both possible, relying on the same underlying Node privilege. Credit to @aferr for the explanation and suggestions on how to implement it, and also link to the relevant Slack discussion, for reference: https://project-oak.slack.com/messages/CHE9E13C3/p1590712037024800?thread_ts=1590660390.016600 Ref #630 --- .../rust/oak_runtime/src/runtime/mod.rs | 62 +++++++++------ .../rust/oak_runtime/src/runtime/tests.rs | 76 ++++++++++++++++++- 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/oak/server/rust/oak_runtime/src/runtime/mod.rs b/oak/server/rust/oak_runtime/src/runtime/mod.rs index c84cd8e1f9e..1c9a622337d 100644 --- a/oak/server/rust/oak_runtime/src/runtime/mod.rs +++ b/oak/server/rust/oak_runtime/src/runtime/mod.rs @@ -501,16 +501,21 @@ impl Runtime { } /// Returns the least restrictive (i.e. least confidential, most trusted) label that this Node - /// may downgrade to. This takes into account all the [downgrade privilege](NodeInfo::privilege) - /// that the node possesses. - fn get_node_downgraded_label(&self, node_id: NodeId) -> Label { - // Original (static) Node label. - let node_label = self.get_node_label(node_id); + /// may downgrade `initial_label` to. This takes into account all the [downgrade + /// privilege](NodeInfo::privilege) that the node possesses. + /// + /// When reading from a Channel, `initial_label` is the Label of the Channel from which the Node + /// is reading: the thing being downgraded is the data being read into the Node (protected by + /// the Channel Label). + /// + /// When writing to a Channel, `initial_label` is the Label of the Node itself: the thing being + /// downgraded is the data inside the Node (protected by the Node Label). + fn get_node_downgraded_label(&self, node_id: NodeId, initial_label: &Label) -> Label { // Retrieve the set of tags that the node may downgrade. let node_privilege = self.get_node_privilege(node_id); Label { // Remove all the confidentiality tags that the Node may declassify. - confidentiality_tags: node_label + confidentiality_tags: initial_label .confidentiality_tags .iter() .filter(|t| { @@ -521,7 +526,7 @@ impl Runtime { .cloned() .collect(), // Add all the integrity tags that the Node may endorse. - integrity_tags: node_label + integrity_tags: initial_label .integrity_tags .iter() .chain(node_privilege.can_endorse_integrity_tags.iter()) @@ -571,20 +576,23 @@ impl Runtime { fn validate_can_read_from_label( &self, node_id: NodeId, - label: &Label, + source_label: &Label, ) -> Result<(), OakStatus> { - let downgraded_node_label = self.get_node_downgraded_label(node_id); + // We downgrade the source Label, since its data is being read into the current Node. + let downgraded_source_label = self.get_node_downgraded_label(node_id, source_label); + let target_label = self.get_node_label(node_id); + trace!("{:?}: original source label: {:?}?", node_id, source_label); trace!( - "{:?}: can {:?} read from {:?}?", + "{:?}: downgraded source label: {:?}?", node_id, - downgraded_node_label, - label + downgraded_source_label ); - if label.flows_to(&downgraded_node_label) { - trace!("{:?}: can read from {:?}", node_id, label); + trace!("{:?}: target label: {:?}?", node_id, target_label); + if downgraded_source_label.flows_to(&target_label) { + trace!("{:?}: can read from {:?}", node_id, source_label); Ok(()) } else { - debug!("{:?}: cannot read from {:?}", node_id, label); + debug!("{:?}: cannot read from {:?}", node_id, source_label); Err(OakStatus::ErrPermissionDenied) } } @@ -602,19 +610,27 @@ impl Runtime { /// Returns whether the given Node is allowed to write to an entity with the provided [`Label`], /// taking into account all the [downgrade privilege](NodeInfo::privilege) the Node possesses. - fn validate_can_write_to_label(&self, node_id: NodeId, label: &Label) -> Result<(), OakStatus> { - let downgraded_node_label = self.get_node_downgraded_label(node_id); + fn validate_can_write_to_label( + &self, + node_id: NodeId, + target_label: &Label, + ) -> Result<(), OakStatus> { + // We downgrade the current Node Label, since its data is being written into the + // destination. + let source_label = self.get_node_label(node_id); + let downgraded_source_label = self.get_node_downgraded_label(node_id, &source_label); + trace!("{:?}: original source label: {:?}?", node_id, source_label); trace!( - "{:?}: can {:?} write to {:?}?", + "{:?}: downgraded source label: {:?}?", node_id, - downgraded_node_label, - label + downgraded_source_label ); - if downgraded_node_label.flows_to(&label) { - trace!("{:?}: can write to {:?}", node_id, label); + trace!("{:?}: target label: {:?}?", node_id, target_label); + if downgraded_source_label.flows_to(&target_label) { + trace!("{:?}: can write to {:?}", node_id, target_label); Ok(()) } else { - debug!("{:?}: cannot write to {:?}", node_id, label); + debug!("{:?}: cannot write to {:?}", node_id, target_label); Err(OakStatus::ErrPermissionDenied) } } diff --git a/oak/server/rust/oak_runtime/src/runtime/tests.rs b/oak/server/rust/oak_runtime/src/runtime/tests.rs index 1f5f92bcdbc..3015182e2b6 100644 --- a/oak/server/rust/oak_runtime/src/runtime/tests.rs +++ b/oak/server/rust/oak_runtime/src/runtime/tests.rs @@ -226,7 +226,11 @@ fn create_channel_less_confidential_label_no_privilege_err() { ); } -/// Create a test Node that creates a Channel with a more confidential label and succeeds. +/// Create a test Node with no privilege that: +/// +/// - creates a Channel with a more confidential label and succeeds +/// - writes to the newly created channel and succeeds +/// - reads from the newly created channel and fails /// /// Data is always allowed to flow to more confidential labels. #[test] @@ -247,6 +251,76 @@ fn create_channel_more_confidential_label_ok() { Box::new(move |runtime| { let result = runtime.channel_create(&more_confidential_label); assert_eq!(true, result.is_ok()); + + let (write_handle, read_handle) = result.unwrap(); + + { + // Writing to a more confidential Channel is allowed. + let message = NodeMessage { + data: vec![14, 12, 88], + handles: vec![], + }; + let result = runtime.channel_write(write_handle, message); + assert_eq!(true, result.is_ok()); + } + + { + // Reading from a more confidential Channel is not allowed. + let result = runtime.channel_read(read_handle); + assert_eq!(false, result.is_ok()); + } + + Ok(()) + }), + ); +} + +/// Create a test Node with downgrading privilege that: +/// +/// - creates a Channel with a more confidential label and succeeds (same as previous test case) +/// - writes to the newly created channel and succeeds (same as previous test case) +/// - reads from the newly created channel and succeeds (different from previous test case, thanks +/// to the newly added privilege) +#[test] +fn create_channel_more_confidential_label_privilege_ok() { + let tag_0 = oak_abi::label::authorization_bearer_token_hmac_tag(&[1, 1, 1]); + let tag_1 = oak_abi::label::authorization_bearer_token_hmac_tag(&[2, 2, 2]); + let initial_label = Label { + confidentiality_tags: vec![tag_0.clone()], + integrity_tags: vec![], + }; + let more_confidential_label = Label { + confidentiality_tags: vec![tag_0, tag_1.clone()], + integrity_tags: vec![], + }; + run_node_body( + &initial_label, + &NodePrivilege { + can_declassify_confidentiality_tags: hashset! { tag_1 }, + can_endorse_integrity_tags: hashset! {}, + }, + Box::new(move |runtime| { + let result = runtime.channel_create(&more_confidential_label); + assert_eq!(true, result.is_ok()); + + let (write_handle, read_handle) = result.unwrap(); + + { + // Writing to a more confidential Channel is allowed. + let message = NodeMessage { + data: vec![14, 12, 88], + handles: vec![], + }; + let result = runtime.channel_write(write_handle, message); + assert_eq!(true, result.is_ok()); + } + + { + // Reading from a more confidential Channel is allowed because of the privilege. + let result = runtime.channel_read(read_handle); + assert_eq!(true, result.is_ok()); + } + Ok(()) }), );