// 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 android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, }; use std::cmp::PartialEq; use std::convert::From; use std::ffi::CStr; use crate::error::Error as KsError; use keystore2_selinux as selinux; 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()) } /// ## Background /// /// AIDL enums are represented as constants of the form: /// ``` /// mod EnumName { /// pub type EnumName = i32; /// pub const Variant1: EnumName = ; /// pub const Variant2: EnumName = ; /// ... /// } ///``` /// This macro wraps the enum in a new type, e.g., `MyPerm` and maps each variant to an SELinux /// permission while providing the following interface: /// * From and Into are implemented. Where the implementation of From maps /// any variant not specified to the default. /// * Every variant has a constructor with a name corresponding to its lower case SELinux string /// representation. /// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the /// represented permission. /// /// ## 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 /// ``` /// /// implement_permission!( /// /// MyPerm documentation. /// #[derive(Clone, Copy, Debug, PartialEq)] /// MyPerm from EnumName with default (None, none) {} /// Variant1, selinux name: variant1; /// Variant2, selinux name: variant1; /// } /// ); /// ``` macro_rules! implement_permission_aidl { // This rule provides the public interface of the macro. And starts the preprocessing // recursion (see below). ($(#[$m:meta])* $name:ident from $aidl_name:ident with default ($($def:tt)*) { $($element:tt)* }) => { implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($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)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], ) => { implement_permission_aidl!(@end $($m)*, $name, $aidl_name, ($($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)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], $e_name:ident, selinux name: use; $($element:tt)*) => { implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), [$($out)* $e_name, 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)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], $e_name:ident, selinux name: $e_str:ident; $($element:tt)*) => { implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), [$($out)* $e_name, $e_str, $e_str;], $($element)*); }; (@end $($m:meta)*, $name:ident, $aidl_name:ident, ($def_name:ident, $def_selinux_name:ident) { $($element_name:ident, $element_identifier:ident, $selinux_name:ident;)* }) => { $(#[$m])* pub struct $name(pub $aidl_name); impl From<$aidl_name> for $name { fn from (p: $aidl_name) -> Self { match p { $aidl_name::$def_name => Self($aidl_name::$def_name), $($aidl_name::$element_name => Self($aidl_name::$element_name),)* _ => Self($aidl_name::$def_name), } } } impl From<$name> for $aidl_name { fn from(p: $name) -> $aidl_name { p.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($aidl_name::$def_name) => stringify!($def_selinux_name), $(Self($aidl_name::$element_name) => stringify!($selinux_name),)* _ => stringify!($def_selinux_name), } } /// Creates an instance representing a permission with the same name. pub const fn $def_selinux_name() -> Self { Self($aidl_name::$def_name) } $( /// Creates an instance representing a permission with the same name. pub const fn $element_identifier() -> Self { Self($aidl_name::$element_name) } )* } }; } implement_permission_aidl!( /// 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)] KeyPerm from KeyPermission with default (NONE, none) { CONVERT_STORAGE_KEY_TO_EPHEMERAL, selinux name: convert_storage_key_to_ephemeral; DELETE, selinux name: delete; GEN_UNIQUE_ID, selinux name: gen_unique_id; GET_INFO, selinux name: get_info; GRANT, selinux name: grant; MANAGE_BLOB, selinux name: manage_blob; REBIND, selinux name: rebind; REQ_FORCED_OP, selinux name: req_forced_op; UPDATE, selinux name: update; USE, selinux name: use; USE_DEV_ID, selinux name: use_dev_id; } ); /// This macro implements an enum with values mapped to SELinux permission names. /// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements /// * From and Into are implemented. Where the implementation of From maps /// any variant not specified to the default. /// * Every variant has a constructor with a name corresponding to its lower case SELinux string /// representation. /// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the /// represented permission. /// /// ## Example /// ``` /// implement_permission!( /// /// MyPerm documentation. /// #[derive(Clone, Copy, Debug, Eq, PartialEq)] /// MyPerm with default (None = 0, none) { /// 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])* $name:ident with default ($def_name:ident = $def_val:expr, $def_selinux_name:ident) { $($(#[$element_meta:meta])* $element_name:ident = $element_val:expr, selinux name: $selinux_name:ident;)* }) => { $(#[$m])* pub enum $name { /// The default variant of an enum. $def_name = $def_val, $( $(#[$element_meta])* $element_name = $element_val, )* } impl From for $name { fn from (p: i32) -> Self { match p { $def_val => Self::$def_name, $($element_val => Self::$element_name,)* _ => Self::$def_name, } } } impl From<$name> for i32 { fn from(p: $name) -> i32 { p as i32 } } 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::$def_name => stringify!($def_selinux_name), $(Self::$element_name => stringify!($selinux_name),)* } } /// Creates an instance representing a permission with the same name. pub const fn $def_selinux_name() -> Self { Self::$def_name } $( /// Creates an instance representing a permission with the same name. pub const fn $selinux_name() -> Self { Self::$element_name } )* } }; } 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)] KeystorePerm with default (None = 0, none) { /// Checked when a new auth token is installed. AddAuth = 1, selinux name: add_auth; /// Checked when an app is uninstalled or wiped. ClearNs = 2, selinux name: clear_ns; /// Checked when the user state is queried from Keystore 2.0. GetState = 4, selinux name: get_state; /// Checked when Keystore 2.0 is asked to list a namespace that the caller /// does not have the get_info permission for. List = 8, selinux name: list; /// Checked when Keystore 2.0 gets locked. Lock = 0x10, selinux name: lock; /// Checked when Keystore 2.0 shall be reset. Reset = 0x20, selinux name: reset; /// Checked when Keystore 2.0 shall be unlocked. Unlock = 0x40, selinux name: unlock; /// Checked when user is added or removed. ChangeUser = 0x80, selinux name: change_user; /// Checked when password of the user is changed. ChangePassword = 0x100, selinux name: change_password; /// Checked when a UID is cleared. ClearUID = 0x200, selinux name: clear_uid; /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens. GetAuthToken = 0x400, selinux name: get_auth_token; /// Checked when earlyBootEnded() is called. EarlyBootEnded = 0x800, selinux name: early_boot_ended; /// Checked when IKeystoreMaintenance::onDeviceOffBody is called. ReportOffBody = 0x1000, selinux name: report_off_body; /// Checked when IkeystoreMetrics::pullMetris is called. PullMetrics = 0x2000, selinux name: pull_metrics; /// Checked when IKeystoreMaintenance::deleteAllKeys is called. DeleteAllKeys = 0x4000, selinux name: delete_all_keys; } ); /// 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, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct KeyPermSet(pub 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(KeyPermission(p))); } } } } } impl From for KeyPermSet { fn from(p: KeyPerm) -> Self { Self((p.0).0 as i32) } } /// allow conversion from the AIDL wire type i32 to a permission set. impl From for KeyPermSet { fn from(p: i32) -> Self { Self(p) } } impl From for i32 { fn from(p: KeyPermSet) -> i32 { p.0 } } impl KeyPermSet { /// Returns true iff this permission set has all of the permissions that are in `other`. pub 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).0 $(| ($tail.0).0)*) }; } 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: &CStr, 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.nspace` parameter is looked up in /// SELinux keystore key backend, and the result is used /// as target context. pub fn check_grant_permission( caller_ctx: &CStr, access_vec: KeyPermSet, key: &KeyDescriptor, ) -> anyhow::Result<()> { let target_context = match key.domain { Domain::APP => getcon().context("check_grant_permission: getcon failed.")?, Domain::SELINUX => lookup_keystore2_key_context(key.nspace) .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(format!( concat!( "check_grant_permission: check_access failed. ", "The caller may have tried to grant a permission that they don't possess. {:?}" ), p ))? } 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.nspace` 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::KEY_ID` was selected, and /// on various unexpected backend failures. pub fn check_key_permission( caller_uid: u32, caller_ctx: &CStr, perm: KeyPerm, key: &KeyDescriptor, access_vector: &Option, ) -> anyhow::Result<()> { // If an access vector was supplied, the key is either accessed by GRANT or by KEY_ID. // In the former case, key.domain was set to GRANT and we check the failure cases // further below. If the access is requested by KEY_ID, key.domain would have been // resolved to APP or SELINUX depending on where the key actually resides. // Either way we can return here immediately if the access vector covers the requested // permission. If it does not, we can still check if the caller has access by means of // ownership. if let Some(access_vector) = access_vector { if access_vector.includes(perm) { return Ok(()); } } let target_context = match key.domain { // apps get the default keystore context Domain::APP => { if caller_uid as i64 != key.nspace { return Err(selinux::Error::perm()) .context("Trying to access key without ownership."); } getcon().context("check_key_permission: getcon failed.")? } Domain::SELINUX => lookup_keystore2_key_context(key.nspace) .context("check_key_permission: Domain::SELINUX: Failed to lookup namespace.")?, Domain::GRANT => { match access_vector { Some(_) => { 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::KEY_ID => { // We should never be called with `Domain::KEY_ID. 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::KEY_ID."); } Domain::BLOB => { let tctx = lookup_keystore2_key_context(key.nspace) .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 } _ => { return Err(KsError::sys()) .context(format!("Unknown domain value: \"{:?}\".", key.domain)) } }; 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::*; 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::rebind(), KeyPerm::update(), KeyPerm::use_(), KeyPerm::convert_storage_key_to_ephemeral(), ]; const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![ KeyPerm::delete(), KeyPerm::use_dev_id(), // No KeyPerm::grant() KeyPerm::get_info(), 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::rebind(), KeyPerm::update(), KeyPerm::use_(), KeyPerm::convert_storage_key_to_ephemeral(), ]; const UNPRIV_PERMS: KeyPermSet = key_perm_set![ KeyPerm::delete(), KeyPerm::get_info(), 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()); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::change_user()).is_ok()); assert!( check_keystore_permission(&system_server_ctx, KeystorePerm::change_password()).is_ok() ); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_uid()).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!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state()).is_ok()); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::list())); 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())); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_user())); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_password())); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_uid())); 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")?; let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; check_grant_permission(&system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key) .expect("Grant permission check failed."); // 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<()> { let (sctx, namespace, is_su) = check_context()?; let key = KeyDescriptor { domain: Domain::SELINUX, nspace: 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<()> { let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None }; assert_perm_failed!(check_key_permission( 0, &selinux::Context::new("ignored").unwrap(), KeyPerm::grant(), &key, &Some(UNPRIV_PERMS) )); check_key_permission( 0, &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")?; let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; assert!(check_key_permission(0, &system_server_ctx, KeyPerm::use_(), &key, &None).is_ok()); assert!(check_key_permission(0, &system_server_ctx, KeyPerm::delete(), &key, &None).is_ok()); assert!( check_key_permission(0, &system_server_ctx, KeyPerm::get_info(), &key, &None).is_ok() ); assert!(check_key_permission(0, &system_server_ctx, KeyPerm::rebind(), &key, &None).is_ok()); assert!(check_key_permission(0, &system_server_ctx, KeyPerm::update(), &key, &None).is_ok()); assert!(check_key_permission(0, &system_server_ctx, KeyPerm::grant(), &key, &None).is_ok()); assert!( check_key_permission(0, &system_server_ctx, KeyPerm::use_dev_id(), &key, &None).is_ok() ); assert!( check_key_permission(0, &gmscore_app, KeyPerm::gen_unique_id(), &key, &None).is_ok() ); assert!(check_key_permission(0, &shell_ctx, KeyPerm::use_(), &key, &None).is_ok()); assert!(check_key_permission(0, &shell_ctx, KeyPerm::delete(), &key, &None).is_ok()); assert!(check_key_permission(0, &shell_ctx, KeyPerm::get_info(), &key, &None).is_ok()); assert!(check_key_permission(0, &shell_ctx, KeyPerm::rebind(), &key, &None).is_ok()); assert!(check_key_permission(0, &shell_ctx, KeyPerm::update(), &key, &None).is_ok()); assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::grant(), &key, &None)); assert_perm_failed!(check_key_permission( 0, &shell_ctx, KeyPerm::req_forced_op(), &key, &None )); assert_perm_failed!(check_key_permission( 0, &shell_ctx, KeyPerm::manage_blob(), &key, &None )); assert_perm_failed!(check_key_permission( 0, &shell_ctx, KeyPerm::use_dev_id(), &key, &None )); assert_perm_failed!(check_key_permission( 0, &shell_ctx, KeyPerm::gen_unique_id(), &key, &None )); // Also make sure that the permission fails if the caller is not the owner. assert_perm_failed!(check_key_permission( 1, // the owner is 0 &system_server_ctx, KeyPerm::use_(), &key, &None )); // Unless there was a grant. assert!(check_key_permission( 1, &system_server_ctx, KeyPerm::use_(), &key, &Some(key_perm_set![KeyPerm::use_()]) ) .is_ok()); // But fail if the grant did not cover the requested permission. assert_perm_failed!(check_key_permission( 1, &system_server_ctx, KeyPerm::use_(), &key, &Some(key_perm_set![KeyPerm::get_info()]) )); Ok(()) } #[test] fn check_key_permission_domain_selinux() -> Result<()> { let (sctx, namespace, is_su) = check_context()?; let key = KeyDescriptor { domain: Domain::SELINUX, nspace: namespace as i64, alias: None, blob: None, }; if is_su { assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::manage_blob(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::gen_unique_id(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::req_forced_op(), &key, &None).is_ok()); } else { assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok()); assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok()); assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None)); assert_perm_failed!(check_key_permission( 0, &sctx, KeyPerm::req_forced_op(), &key, &None )); assert_perm_failed!(check_key_permission( 0, &sctx, KeyPerm::manage_blob(), &key, &None )); assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None)); assert_perm_failed!(check_key_permission( 0, &sctx, KeyPerm::gen_unique_id(), &key, &None )); } Ok(()) } #[test] fn check_key_permission_domain_blob() -> Result<()> { let (sctx, namespace, is_su) = check_context()?; let key = KeyDescriptor { domain: Domain::BLOB, nspace: namespace as i64, alias: None, blob: None, }; if is_su { check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None) } else { assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None)); Ok(()) } } #[test] fn check_key_permission_domain_key_id() -> Result<()> { let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None }; assert_eq!( Some(&KsError::sys()), check_key_permission( 0, &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::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(), "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::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(), "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::rebind(), KeyPerm::update(), KeyPerm::use_(), ]; let v2 = key_perm_set![ KeyPerm::manage_blob(), KeyPerm::delete(), 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::rebind(), KeyPerm::update(), KeyPerm::use_(), ]; let v2 = key_perm_set![ KeyPerm::manage_blob(), KeyPerm::delete(), 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::rebind(), KeyPerm::update(), KeyPerm::use_(), ]; let v2 = key_perm_set![ KeyPerm::manage_blob(), KeyPerm::delete(), KeyPerm::req_forced_op(), // only in v2 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::rebind(), KeyPerm::update(), KeyPerm::use_(), ]; assert!(!v1.includes(v2)); assert!(!v2.includes(v1)); } }