From 78bd48c704e618ca3d66a76616b3d24c6f638604 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Tue, 21 Jul 2020 12:27:13 -0700 Subject: [PATCH 1/2] Implement Keystore 2.0 access control This patch provides higher level functions to query Keystore 2.0 specific SEPolicy. It provides abstractions for the permissions of the security classes "keystore2" and "keystore2_key". It also provides functions to check permission for general Keystore requests as well as Keystore key and grant requests. Bug: 159466840 Test: keystore2_test Change-Id: Ie743cff76fe27f8ad96b2405f5d77b298ba35293 --- keystore2/src/lib.rs | 1 + keystore2/src/permission.rs | 923 ++++++++++++++++++++++++++++++++++++ 2 files changed, 924 insertions(+) create mode 100644 keystore2/src/permission.rs diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs index dfdd2288..b37773a1 100644 --- a/keystore2/src/lib.rs +++ b/keystore2/src/lib.rs @@ -16,3 +16,4 @@ pub mod database; pub mod error; +pub mod permission; diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs new file mode 100644 index 00000000..a5a63ee9 --- /dev/null +++ b/keystore2/src/permission.rs @@ -0,0 +1,923 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate provides access control primitives for Keystore 2.0. +//! It provides high level functions for checking permissions in the keystore2 and keystore2_key +//! SELinux classes based on the keystore2_selinux backend. +//! It also provides KeystorePerm and KeyPerm as convenience wrappers for the SELinux permission +//! defined by keystore2 and keystore2_key respectively. + +use keystore_aidl_generated as aidl; + +use std::cmp::PartialEq; +use std::convert::From; + +use crate::error::Error as KsError; +use keystore2_selinux as selinux; + +use anyhow::Context as AnyhowContext; + +use selinux::Backend; + +// Replace getcon with a mock in the test situation +#[cfg(not(test))] +use selinux::getcon; +#[cfg(test)] +use tests::test_getcon as getcon; + +/// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements +/// * `From for `MyPerm`, where each unknown numeric value is mapped to the given default, +/// here `None` +/// * `Into for `MyPerm` +/// * `MyPerm::foo()` and `MyPerm::bar()` which construct MyPerm instances representing +/// `MyPermission::Foo` and `MyPermission::Bar` respectively. +/// * `MyPerm.to_selinux(&self)`, which returns the selinux string representation of the +/// represented permission. +/// * Tests in the given test namespace for each permision that check that the numeric +/// representations of MyPermission and MyPerm match. (TODO replace with static assert if +/// they become available.) +/// +/// ## Special behavior +/// If the keyword `use` appears as an selinux name `use_` is used as identifier for the +/// constructor function (e.g. `MePerm::use_()`) but the string returned by `to_selinux` will +/// still be `"use"`. +/// +/// ## Example +/// ``` +/// #[i32] +/// enum MyPermission { +/// None = 0, +/// Foo = 1, +/// Bar = 2, +/// } +/// +/// implement_permission!( +/// /// MyPerm documentation. +/// #[derive(Clone, Copy, Debug, PartialEq)] +/// MyPermission as MyPerm with default (None = 0, none) +/// and test namespace my_perm_tests { +/// Foo = 1, selinux name: foo; +/// Bar = 2, selinux name: bar; +/// } +/// ); +/// ``` +macro_rules! implement_permission { + // This rule provides the public interface of the macro. And starts the preprocessing + // recursion (see below). + ($(#[$m:meta])* $t:ty as $name:ident with default ($($def:tt)*) + and test namespace $tn:ident { $($element:tt)* }) + => { + implement_permission!(@replace_use $($m)*, $t, $name, $tn, ($($def)*), [] , $($element)*); + }; + + + // The following three rules recurse through the elements of the form + // ` = , selinux name: ;` + // preprocessing the input. + + // The first rule terminates the recursion and passes the processed arguments to the final + // rule that spills out the implementation. + (@replace_use $($m:meta)*, $t:ty, $name:ident, $tn:ident, ($($def:tt)*), [$($out:tt)*], ) => { + implement_permission!(@end $($m)*, $t, $name, $tn, ($($def)*) { $($out)* } ); + }; + + // The second rule is triggered if the selinux name of an element is literally `use`. + // It produces the tuple ` = , use_, use;` + // and appends it to the out list. + (@replace_use $($m:meta)*, $t:ty, $name:ident, $tn:ident, ($($def:tt)*), [$($out:tt)*], + $e_name:ident = $e_val:expr, selinux name: use; $($element:tt)*) + => { + implement_permission!(@replace_use $($m)*, $t, $name, $tn, ($($def)*), + [$($out)* $e_name = $e_val, use_, use;], $($element)*); + }; + + // The third rule is the default rule which replaces every input tuple with + // ` = , , ;` + // and appends the result to the out list. + (@replace_use $($m:meta)*, $t:ty, $name:ident, $tn:ident, ($($def:tt)*), [$($out:tt)*], + $e_name:ident = $e_val:expr, selinux name: $e_str:ident; $($element:tt)*) + => { + implement_permission!(@replace_use $($m)*, $t, $name, $tn, ($($def)*), + [$($out)* $e_name = $e_val, $e_str, $e_str;], $($element)*); + }; + + (@end $($m:meta)*, $t:ty, $name:ident, $tn:ident, + ($def_name:ident = $def:expr, $def_selinux_name:ident) { + $($element_name:ident = $element_val:expr, $element_identifier:ident, + $selinux_name:ident;)* + }) + => + { + $(#[$m])* + pub struct $name($t); + + impl From for $name { + fn from (p: i32) -> Self { + match p { + $def => Self(<$t>::$def_name), + $($element_val => Self(<$t>::$element_name),)* + _ => Self(<$t>::$def_name), + } + } + } + + impl Into<$t> for $name { + fn into(self) -> $t { + self.0 + } + } + + impl $name { + /// Returns a string representation of the permission as required by + /// `selinux::check_access`. + pub fn to_selinux(&self) -> &'static str { + match self { + Self(<$t>::$def_name) => stringify!($def_selinux_name), + $(Self(<$t>::$element_name) => stringify!($selinux_name),)* + } + } + + /// Creates an instance representing a permission with the same name. + pub const fn $def_selinux_name() -> Self { Self(<$t>::$def_name) } + $( + /// Creates an instance representing a permission with the same name. + pub const fn $element_identifier() -> Self { Self(<$t>::$element_name) } + )* + } + #[cfg(test)] + mod $tn { + use super::*; + + #[test] + fn $def_selinux_name() { + assert_eq!($name::$def_selinux_name(), (<$t>::$def_name as i32).into()); + } + $( + #[test] + fn $element_identifier() { + assert_eq!($name::$element_identifier(), (<$t>::$element_name as i32).into()); + } + )* + } + }; + + +} + +implement_permission!( + /// KeyPerm provides a convenient abstraction from the SELinux class `keystore2_key`. + /// At the same time it maps `KeyPermissions` from the Keystore 2.0 AIDL Grant interface to + /// the SELinux permissions. With the implement_permission macro, we conveniently + /// provide mappings between the wire type bit field values, the rust enum and the SELinux + /// string representation. + /// + /// ## Example + /// + /// In this access check `KeyPerm::get_info().to_selinux()` would return the SELinux representation + /// "info". + /// ``` + /// selinux::check_access(source_context, target_context, "keystore2_key", + /// KeyPerm::get_info().to_selinux()); + /// ``` + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + aidl::KeyPermission as KeyPerm with default (None = 0, none) + and test namespace key_perm_tests { + Delete = 1, selinux name: delete; + GenUniqueId = 2, selinux name: gen_unique_id; + GetInfo = 4, selinux name: get_info; + Grant = 8, selinux name: grant; + List = 0x10, selinux name: list; + ManageBlob = 0x20, selinux name: manage_blob; + Rebind = 0x40, selinux name: rebind; + ReqForcedOp = 0x80, selinux name: req_forced_op; + Update = 0x100, selinux name: update; + Use = 0x200, selinux name: use; + UseDevId = 0x400, selinux name: use_dev_id; + } +); + +/// KeystorePermission defines values for the SELinux `keystore2` security class. +/// Countrary to `KeyPermission`, this enum is not generated by AIDL and need not be +/// wrapped by newtype pattern. But we conveniently use the implement_permission macro +/// to provide the same feature that we did for `KeyPermission` to this set of permissions. +#[repr(i32)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KeystorePermission { + /// `None` is not a permission that can ever be granted. It is not known to the SEPolicy. + None = 0, + /// Checked when a new auth token is installed. + AddAuth = 1, + /// Checked when an app is uninstalled or wiped. + ClearNs = 2, + /// Checked when the locked state of Keystore 2.0 is queried. + GetState = 4, + /// Checked when Keystore 2.0 gets locked. + Lock = 8, + /// Checked when Keystore 2.0 shall be reset. + Reset = 0x10, + /// Checked when Keystore 2.0 shall be unlocked. + Unlock = 0x20, +} + +implement_permission!( + /// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`. + /// Using the implement_permission macro we get the same features as `KeyPerm`. + #[derive(Clone, Copy, Debug, PartialEq)] + KeystorePermission as KeystorePerm with default (None = 0, none) + and test namespace keystore_perm_tests { + AddAuth = 1, selinux name: add_auth; + ClearNs = 2, selinux name: clear_ns; + GetState = 4, selinux name: get_state; + Lock = 8, selinux name: lock; + Reset = 0x10, selinux name: reset; + Unlock = 0x20, selinux name: unlock; + } +); + +/// Represents a set of `KeyPerm` permissions. +/// `IntoIterator` is implemented for this struct allowing the iteration through all the +/// permissions in the set. +/// It also implements a function `includes(self, other)` that checks if the permissions +/// in `other` are included in `self`. +/// +/// KeyPermSet can be created with the macro `key_perm_set![]`. +/// +/// ## Example +/// ``` +/// let perms1 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob(), KeyPerm::grant()]; +/// let perms2 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob()]; +/// +/// assert!(perms1.includes(perms2)) +/// assert!(!perms2.includes(perms1)) +/// +/// let i = perms1.into_iter(); +/// // iteration in ascending order of the permission's numeric representation. +/// assert_eq(Some(KeyPerm::manage_blob()), i.next()); +/// assert_eq(Some(KeyPerm::grant()), i.next()); +/// assert_eq(Some(KeyPerm::use_()), i.next()); +/// assert_eq(None, i.next()); +/// ``` +#[derive(Copy, Clone)] +pub struct KeyPermSet(i32); + +mod perm { + use super::*; + + pub struct IntoIter { + vec: KeyPermSet, + pos: u8, + } + + impl IntoIter { + pub fn new(v: KeyPermSet) -> Self { + Self { vec: v, pos: 0 } + } + } + + impl std::iter::Iterator for IntoIter { + type Item = KeyPerm; + + fn next(&mut self) -> Option { + loop { + if self.pos == 32 { + return None; + } + let p = self.vec.0 & (1 << self.pos); + self.pos += 1; + if p != 0 { + return Some(KeyPerm::from(p)); + } + } + } + } +} + +impl From for KeyPermSet { + fn from(p: KeyPerm) -> Self { + Self(p.0 as i32) + } +} + +impl KeyPermSet { + /// Returns true iff this permission set has all of the permissions that are in `other`. + fn includes>(&self, other: T) -> bool { + let o: KeyPermSet = other.into(); + (self.0 & o.0) == o.0 + } +} + +/// This macro can be used to create a `KeyPermSet` from a list of `KeyPerm` values. +/// +/// ## Example +/// ``` +/// let v = key_perm_set![Perm::delete(), Perm::manage_blob()]; +/// ``` +#[macro_export] +macro_rules! key_perm_set { + () => { KeyPermSet(0) }; + ($head:expr $(, $tail:expr)* $(,)?) => { + KeyPermSet($head.0 as i32 $(| $tail.0 as i32)*) + }; +} + +impl IntoIterator for KeyPermSet { + type Item = KeyPerm; + type IntoIter = perm::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter::new(self) + } +} + +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` may access +/// the given permision `perm` of the `keystore2` security class. +pub fn check_keystore_permission( + caller_ctx: &selinux::Context, + perm: KeystorePerm, +) -> anyhow::Result<()> { + let target_context = getcon().context("check_keystore_permission: getcon failed.")?; + selinux::check_access(caller_ctx, &target_context, "keystore2", perm.to_selinux()) +} + +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` has +/// all the permissions indicated in `access_vec` for the target domain indicated by the key +/// descriptor `key` in the security class `keystore2_key`. +/// +/// Also checks if the caller has the grant permission for the given target domain. +/// +/// Attempts to grant the grant permission are always denied. +/// +/// The only viable target domains are +/// * `Domain::App` in which case u:r:keystore:s0 is used as target context and +/// * `Domain::SELinux` in which case the `key.namespace_` parameter is looked up in +/// SELinux keystore key backend, and the result is used +/// as target context. +pub fn check_grant_permission( + caller_ctx: &selinux::Context, + access_vec: KeyPermSet, + key: &aidl::KeyDescriptor, +) -> anyhow::Result<()> { + use aidl::Domain; + use selinux::KeystoreKeyBackend; + + let target_context = match key.domain { + Domain::App => getcon().context("check_grant_permission: getcon failed.")?, + Domain::SELinux => { + // TODO cache an open backend, possible use a lazy static. + let backend = KeystoreKeyBackend::new().context(concat!( + "check_grant_permission: Domain::SELinux: ", + "Failed to create selinux keystore backend." + ))?; + backend + .lookup(format!("{}", key.namespace_).as_str()) + .context("check_grant_permission: Domain::SELinux: Failed to lookup namespace")? + } + _ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)), + }; + + selinux::check_access(caller_ctx, &target_context, "keystore2_key", "grant") + .context("Grant permission is required when granting.")?; + + if access_vec.includes(KeyPerm::grant()) { + return Err(selinux::Error::perm()).context("Grant permission cannot be granted."); + } + + for p in access_vec.into_iter() { + selinux::check_access(caller_ctx, &target_context, "keystore2_key", p.to_selinux()) + .context(concat!( + "check_grant_permission: check_access failed. ", + "The caller may have tried to grant a permission that they don't possess." + ))? + } + Ok(()) +} + +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` +/// has the permissions indicated by `perm` for the target domain indicated by the key +/// descriptor `key` in the security class `keystore2_key`. +/// +/// The behavior differs slightly depending on the selected target domain: +/// * `Domain::App` u:r:keystore:s0 is used as target context. +/// * `Domain::SELinux` `key.namespace_` parameter is looked up in the SELinux keystore key +/// backend, and the result is used as target context. +/// * `Domain::Blob` Same as SELinux but the "manage_blob" permission is always checked additionally +/// to the one supplied in `perm`. +/// * `Domain::Grant` Does not use selinux::check_access. Instead the `access_vector` +/// parameter is queried for permission, which must be supplied in this case. +/// +/// ## Return values. +/// * Ok(()) If the requested permissions were granted. +/// * Err(selinux::Error::perm()) If the requested permissions were denied. +/// * Err(KsError::sys()) This error is produced if `Domain::Grant` is selected but no `access_vec` +/// was supplied. It is also produced if `Domain::KeyId` was selected, and +/// on various unexpected backend failures. +pub fn check_key_permission( + caller_ctx: &selinux::Context, + perm: KeyPerm, + key: &aidl::KeyDescriptor, + access_vector: &Option, +) -> anyhow::Result<()> { + use aidl::Domain; + use selinux::KeystoreKeyBackend; + + let target_context = match key.domain { + // apps get the default keystore context + Domain::App => getcon().context("check_key_permission: getcon failed.")?, + Domain::SELinux => { + // TODO cache an open backend, possible use a lasy static. + let backend = KeystoreKeyBackend::new().context( + "check_key_permission: Domain::SELinux: Failed to create selinux keystore backend.", + )?; + backend + .lookup(format!("{}", key.namespace_).as_str()) + .context("check_key_permission: Domain::SELinux: Failed to lookup namespace")? + } + Domain::Grant => { + match access_vector { + Some(pv) => { + if pv.includes(perm) { + return Ok(()); + } else { + return Err(selinux::Error::perm()) + .context(format!("\"{}\" not granted", perm.to_selinux())); + } + } + None => { + // If DOMAIN_GRANT was selected an access vector must be supplied. + return Err(KsError::sys()).context( + "Cannot check permission for Domain::Grant without access vector.", + ); + } + } + } + Domain::KeyId => { + // We should never be called with `Domain::KeyId. The database + // lookup should have converted this into one of `Domain::App` + // or `Domain::SELinux`. + return Err(KsError::sys()).context("Cannot check permission for Domain::KeyId."); + } + Domain::Blob => { + let backend = KeystoreKeyBackend::new() + .context("Domain::Blob: Failed to create selinux keystore backend.")?; + let tctx = backend + .lookup(format!("{}", key.namespace_).as_str()) + .context("Domain::Blob: Failed to lookup namespace.")?; + // If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob" + // permission in addition to the requested permission. + selinux::check_access( + caller_ctx, + &tctx, + "keystore2_key", + KeyPerm::manage_blob().to_selinux(), + )?; + + tctx + } + }; + + selinux::check_access(caller_ctx, &target_context, "keystore2_key", perm.to_selinux()) +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::anyhow; + use anyhow::Result; + use keystore2_selinux::*; + use keystore_aidl_generated as aidl; + + const ALL_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + + const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + // No KeyPerm::grant() + KeyPerm::get_info(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + + const UNPRIV_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::delete(), + KeyPerm::get_info(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + + /// The su_key namespace as defined in su.te and keystore_key_contexts of the + /// SePolicy (system/sepolicy). + const SU_KEY_NAMESPACE: i32 = 0; + /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the + /// SePolicy (system/sepolicy). + const SHELL_KEY_NAMESPACE: i32 = 1; + + pub fn test_getcon() -> Result { + Context::new("u:object_r:keystore:s0") + } + + // This macro evaluates the given expression and checks that + // a) evaluated to Result::Err() and that + // b) the wrapped error is selinux::Error::perm() (permission denied). + // We use a macro here because a function would mask which invocation caused the failure. + // + // TODO b/164121720 Replace this macro with a function when `track_caller` is available. + macro_rules! assert_perm_failed { + ($test_function:expr) => { + let result = $test_function; + assert!(result.is_err(), "Permission check should have failed."); + assert_eq!( + Some(&selinux::Error::perm()), + result.err().unwrap().root_cause().downcast_ref::() + ); + }; + } + + fn check_context() -> Result<(selinux::Context, i32, bool)> { + // Calling the non mocked selinux::getcon here intended. + let context = selinux::getcon()?; + match context.to_str().unwrap() { + "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)), + "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)), + c => Err(anyhow!(format!( + "This test must be run as \"su\" or \"shell\". Current context: \"{}\"", + c + ))), + } + } + + #[test] + fn check_keystore_permission_test() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::add_auth()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_ns()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::get_state()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::lock()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::reset()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::unlock()).is_ok()); + let shell_ctx = Context::new("u:r:shell:s0")?; + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::add_auth())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_ns())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::lock())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::reset())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::unlock())); + Ok(()) + } + + #[test] + fn check_grant_permission_app() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + let shell_ctx = Context::new("u:r:shell:s0")?; + use aidl::Domain; + let key = + aidl::KeyDescriptor { domain: Domain::App, namespace_: 0, alias: None, blob: None }; + assert!(check_grant_permission(&system_server_ctx, NOT_GRANT_PERMS, &key).is_ok()); + // attempts to grant the grant permission must always fail even when privileged. + + assert_perm_failed!(check_grant_permission( + &system_server_ctx, + KeyPerm::grant().into(), + &key + )); + // unprivileged grant attempts always fail. shell does not have the grant permission. + assert_perm_failed!(check_grant_permission(&shell_ctx, UNPRIV_PERMS, &key)); + Ok(()) + } + + #[test] + fn check_grant_permission_selinux() -> Result<()> { + use aidl::Domain; + let (sctx, namespace, is_su) = check_context()?; + let key = aidl::KeyDescriptor { + domain: Domain::SELinux, + namespace_: namespace as i64, + alias: None, + blob: None, + }; + if is_su { + assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok()); + // attempts to grant the grant permission must always fail even when privileged. + assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::grant().into(), &key)); + } else { + // unprivileged grant attempts always fail. shell does not have the grant permission. + assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key)); + } + Ok(()) + } + + #[test] + fn check_key_permission_domain_grant() -> Result<()> { + use aidl::Domain; + let key = + aidl::KeyDescriptor { domain: Domain::Grant, namespace_: 0, alias: None, blob: None }; + + assert_perm_failed!(check_key_permission( + &selinux::Context::new("ignored").unwrap(), + KeyPerm::grant(), + &key, + &Some(UNPRIV_PERMS) + )); + + check_key_permission( + &selinux::Context::new("ignored").unwrap(), + KeyPerm::use_(), + &key, + &Some(ALL_PERMS), + ) + } + + #[test] + fn check_key_permission_domain_app() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + let shell_ctx = Context::new("u:r:shell:s0")?; + let gmscore_app = Context::new("u:r:gmscore_app:s0")?; + use aidl::Domain; + + let key = + aidl::KeyDescriptor { domain: Domain::App, namespace_: 0, alias: None, blob: None }; + + assert!(check_key_permission(&system_server_ctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::list(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::update(), &key, &None).is_ok()); + assert!(check_key_permission(&system_server_ctx, KeyPerm::grant(), &key, &None).is_ok()); + assert!( + check_key_permission(&system_server_ctx, KeyPerm::use_dev_id(), &key, &None).is_ok() + ); + assert!(check_key_permission(&gmscore_app, KeyPerm::gen_unique_id(), &key, &None).is_ok()); + + assert!(check_key_permission(&shell_ctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(&shell_ctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(&shell_ctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(&shell_ctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(&shell_ctx, KeyPerm::list(), &key, &None).is_ok()); + assert!(check_key_permission(&shell_ctx, KeyPerm::update(), &key, &None).is_ok()); + assert_perm_failed!(check_key_permission(&shell_ctx, KeyPerm::grant(), &key, &None)); + assert_perm_failed!(check_key_permission( + &shell_ctx, + KeyPerm::req_forced_op(), + &key, + &None + )); + assert_perm_failed!(check_key_permission(&shell_ctx, KeyPerm::manage_blob(), &key, &None)); + assert_perm_failed!(check_key_permission(&shell_ctx, KeyPerm::use_dev_id(), &key, &None)); + assert_perm_failed!(check_key_permission( + &shell_ctx, + KeyPerm::gen_unique_id(), + &key, + &None + )); + + Ok(()) + } + + #[test] + fn check_key_permission_domain_selinux() -> Result<()> { + use aidl::Domain; + let (sctx, namespace, is_su) = check_context()?; + let key = aidl::KeyDescriptor { + domain: Domain::SELinux, + namespace_: namespace as i64, + alias: None, + blob: None, + }; + + if is_su { + assert!(check_key_permission(&sctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::list(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::update(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::grant(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::manage_blob(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::use_dev_id(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::gen_unique_id(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::req_forced_op(), &key, &None).is_ok()); + } else { + assert!(check_key_permission(&sctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::list(), &key, &None).is_ok()); + assert!(check_key_permission(&sctx, KeyPerm::update(), &key, &None).is_ok()); + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::grant(), &key, &None)); + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::req_forced_op(), &key, &None)); + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::manage_blob(), &key, &None)); + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::use_dev_id(), &key, &None)); + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::gen_unique_id(), &key, &None)); + } + Ok(()) + } + + #[test] + fn check_key_permission_domain_blob() -> Result<()> { + use aidl::Domain; + let (sctx, namespace, is_su) = check_context()?; + let key = aidl::KeyDescriptor { + domain: Domain::Blob, + namespace_: namespace as i64, + alias: None, + blob: None, + }; + + if is_su { + check_key_permission(&sctx, KeyPerm::use_(), &key, &None) + } else { + assert_perm_failed!(check_key_permission(&sctx, KeyPerm::use_(), &key, &None)); + Ok(()) + } + } + + #[test] + fn check_key_permission_domain_key_id() -> Result<()> { + use aidl::Domain; + let key = + aidl::KeyDescriptor { domain: Domain::KeyId, namespace_: 0, alias: None, blob: None }; + + assert_eq!( + Some(&KsError::sys()), + check_key_permission( + &selinux::Context::new("ignored").unwrap(), + KeyPerm::use_(), + &key, + &None + ) + .err() + .unwrap() + .root_cause() + .downcast_ref::() + ); + Ok(()) + } + + #[test] + fn key_perm_set_all_test() { + let v = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_() // Test if the macro accepts missing comma at the end of the list. + ]; + let mut i = v.into_iter(); + assert_eq!(i.next().unwrap().to_selinux(), "delete"); + assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id"); + assert_eq!(i.next().unwrap().to_selinux(), "get_info"); + assert_eq!(i.next().unwrap().to_selinux(), "grant"); + assert_eq!(i.next().unwrap().to_selinux(), "list"); + assert_eq!(i.next().unwrap().to_selinux(), "manage_blob"); + assert_eq!(i.next().unwrap().to_selinux(), "rebind"); + assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op"); + assert_eq!(i.next().unwrap().to_selinux(), "update"); + assert_eq!(i.next().unwrap().to_selinux(), "use"); + assert_eq!(i.next().unwrap().to_selinux(), "use_dev_id"); + assert_eq!(None, i.next()); + } + #[test] + fn key_perm_set_sparse_test() { + let v = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::list(), + KeyPerm::update(), + KeyPerm::use_(), // Test if macro accepts the comma at the end of the list. + ]; + let mut i = v.into_iter(); + assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id"); + assert_eq!(i.next().unwrap().to_selinux(), "list"); + assert_eq!(i.next().unwrap().to_selinux(), "manage_blob"); + assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op"); + assert_eq!(i.next().unwrap().to_selinux(), "update"); + assert_eq!(i.next().unwrap().to_selinux(), "use"); + assert_eq!(None, i.next()); + } + #[test] + fn key_perm_set_empty_test() { + let v = key_perm_set![]; + let mut i = v.into_iter(); + assert_eq!(None, i.next()); + } + #[test] + fn key_perm_set_include_subset_test() { + let v1 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + let v2 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + assert!(v1.includes(v2)); + assert!(!v2.includes(v1)); + } + #[test] + fn key_perm_set_include_equal_test() { + let v1 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + let v2 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + assert!(v1.includes(v2)); + assert!(v2.includes(v1)); + } + #[test] + fn key_perm_set_include_overlap_test() { + let v1 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::grant(), // only in v1 + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + let v2 = key_perm_set![ + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::req_forced_op(), // only in v2 + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + assert!(!v1.includes(v2)); + assert!(!v2.includes(v1)); + } + #[test] + fn key_perm_set_include_no_overlap_test() { + let v1 = key_perm_set![KeyPerm::manage_blob(), KeyPerm::delete(), KeyPerm::grant(),]; + let v2 = key_perm_set![ + KeyPerm::req_forced_op(), + KeyPerm::list(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; + assert!(!v1.includes(v2)); + assert!(!v2.includes(v1)); + } +} From 4ad056ffad5294ed566a015149303442f9b700ef Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Wed, 5 Aug 2020 19:46:46 +0000 Subject: [PATCH 2/2] Cache the KeystoreKeyBackend with a lazy static. This patch makes KeystoreKeyBackend Sync and uses a lazy static to cache the back end in the permissions module. Test: atest keystore2_test Bug: 159466840 Change-Id: Ibc7851baede3506acbdf962e59c281fa16cfaf0e --- keystore2/Android.bp | 2 ++ keystore2/selinux/src/lib.rs | 8 +++++++ keystore2/src/permission.rs | 43 +++++++++++++++--------------------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/keystore2/Android.bp b/keystore2/Android.bp index a48396ab..154b675d 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -21,6 +21,7 @@ rust_library { "libanyhow", "libkeystore_aidl_generated", "libkeystore2_selinux", + "liblazy_static", "liblibsqlite3_sys", "liblog_rust", "librand", @@ -40,6 +41,7 @@ rust_test { "libanyhow", "libkeystore_aidl_generated", "libkeystore2_selinux", + "liblazy_static", "liblibsqlite3_sys", "liblog_rust", "librusqlite", diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs index 30a235ab..05f9db8d 100644 --- a/keystore2/selinux/src/lib.rs +++ b/keystore2/selinux/src/lib.rs @@ -25,6 +25,7 @@ use std::ffi::{CStr, CString}; use std::fmt; use std::io; +use std::marker::{Send, Sync}; pub use std::ops::Deref; use std::os::raw::c_char; use std::ptr; @@ -141,6 +142,10 @@ pub struct KeystoreKeyBackend { handle: *mut selinux::selabel_handle, } +// KeystoreKeyBackend is Sync because selabel_lookup is thread safe. +unsafe impl Sync for KeystoreKeyBackend {} +unsafe impl Send for KeystoreKeyBackend {} + impl KeystoreKeyBackend { const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32; @@ -164,6 +169,9 @@ impl Drop for KeystoreKeyBackend { } } +// Because KeystoreKeyBackend is Sync and Send, member function must never call +// non thread safe libselinux functions. As of this writing no non thread safe +// functions exist that could be called on a label backend handle. impl Backend for KeystoreKeyBackend { fn lookup(&self, key: &str) -> Result { let mut con: *mut c_char = ptr::null_mut(); diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs index a5a63ee9..0db56dda 100644 --- a/keystore2/src/permission.rs +++ b/keystore2/src/permission.rs @@ -30,12 +30,25 @@ use anyhow::Context as AnyhowContext; use selinux::Backend; +use lazy_static::lazy_static; + // Replace getcon with a mock in the test situation #[cfg(not(test))] use selinux::getcon; #[cfg(test)] use tests::test_getcon as getcon; +lazy_static! { + // Panicking here is allowed because keystore cannot function without this backend + // and it would happen early and indicate a gross misconfiguration of the device. + static ref KEYSTORE2_KEY_LABEL_BACKEND: selinux::KeystoreKeyBackend = + selinux::KeystoreKeyBackend::new().unwrap(); +} + +fn lookup_keystore2_key_context(namespace: i64) -> anyhow::Result { + KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string()) +} + /// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements /// * `From for `MyPerm`, where each unknown numeric value is mapped to the given default, /// here `None` @@ -369,20 +382,11 @@ pub fn check_grant_permission( key: &aidl::KeyDescriptor, ) -> anyhow::Result<()> { use aidl::Domain; - use selinux::KeystoreKeyBackend; let target_context = match key.domain { Domain::App => getcon().context("check_grant_permission: getcon failed.")?, - Domain::SELinux => { - // TODO cache an open backend, possible use a lazy static. - let backend = KeystoreKeyBackend::new().context(concat!( - "check_grant_permission: Domain::SELinux: ", - "Failed to create selinux keystore backend." - ))?; - backend - .lookup(format!("{}", key.namespace_).as_str()) - .context("check_grant_permission: Domain::SELinux: Failed to lookup namespace")? - } + Domain::SELinux => lookup_keystore2_key_context(key.namespace_) + .context("check_grant_permission: Domain::SELinux: Failed to lookup namespace.")?, _ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)), }; @@ -429,20 +433,12 @@ pub fn check_key_permission( access_vector: &Option, ) -> anyhow::Result<()> { use aidl::Domain; - use selinux::KeystoreKeyBackend; let target_context = match key.domain { // apps get the default keystore context Domain::App => getcon().context("check_key_permission: getcon failed.")?, - Domain::SELinux => { - // TODO cache an open backend, possible use a lasy static. - let backend = KeystoreKeyBackend::new().context( - "check_key_permission: Domain::SELinux: Failed to create selinux keystore backend.", - )?; - backend - .lookup(format!("{}", key.namespace_).as_str()) - .context("check_key_permission: Domain::SELinux: Failed to lookup namespace")? - } + Domain::SELinux => lookup_keystore2_key_context(key.namespace_) + .context("check_key_permission: Domain::SELinux: Failed to lookup namespace.")?, Domain::Grant => { match access_vector { Some(pv) => { @@ -468,10 +464,7 @@ pub fn check_key_permission( return Err(KsError::sys()).context("Cannot check permission for Domain::KeyId."); } Domain::Blob => { - let backend = KeystoreKeyBackend::new() - .context("Domain::Blob: Failed to create selinux keystore backend.")?; - let tctx = backend - .lookup(format!("{}", key.namespace_).as_str()) + let tctx = lookup_keystore2_key_context(key.namespace_) .context("Domain::Blob: Failed to lookup namespace.")?; // If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob" // permission in addition to the requested permission.