| // 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 crate::error::Error as KsError; |
| use crate::error::ResponseCode; |
| use crate::ks_err; |
| use android_system_keystore2::aidl::android::system::keystore2::{ |
| Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, |
| }; |
| use anyhow::Context as AnyhowContext; |
| use keystore2_selinux as selinux; |
| use lazy_static::lazy_static; |
| use selinux::{implement_class, Backend, ClassPermission}; |
| use std::cmp::PartialEq; |
| use std::convert::From; |
| use std::ffi::CStr; |
| |
| // 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<selinux::Context> { |
| KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string()) |
| } |
| |
| implement_class!( |
| /// 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. |
| #[repr(i32)] |
| #[selinux(class_name = keystore2_key)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum KeyPerm { |
| /// Checked when convert_storage_key_to_ephemeral is called. |
| #[selinux(name = convert_storage_key_to_ephemeral)] |
| ConvertStorageKeyToEphemeral = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0, |
| /// Checked when the caller tries do delete a key. |
| #[selinux(name = delete)] |
| Delete = KeyPermission::DELETE.0, |
| /// Checked when the caller tries to use a unique id. |
| #[selinux(name = gen_unique_id)] |
| GenUniqueId = KeyPermission::GEN_UNIQUE_ID.0, |
| /// Checked when the caller tries to load a key. |
| #[selinux(name = get_info)] |
| GetInfo = KeyPermission::GET_INFO.0, |
| /// Checked when the caller attempts to grant a key to another uid. |
| /// Also used for gating key migration attempts. |
| #[selinux(name = grant)] |
| Grant = KeyPermission::GRANT.0, |
| /// Checked when the caller attempts to use Domain::BLOB. |
| #[selinux(name = manage_blob)] |
| ManageBlob = KeyPermission::MANAGE_BLOB.0, |
| /// Checked when the caller tries to create a key which implies rebinding |
| /// an alias to the new key. |
| #[selinux(name = rebind)] |
| Rebind = KeyPermission::REBIND.0, |
| /// Checked when the caller attempts to create a forced operation. |
| #[selinux(name = req_forced_op)] |
| ReqForcedOp = KeyPermission::REQ_FORCED_OP.0, |
| /// Checked when the caller attempts to update public key artifacts. |
| #[selinux(name = update)] |
| Update = KeyPermission::UPDATE.0, |
| /// Checked when the caller attempts to use a private or public key. |
| #[selinux(name = use)] |
| Use = KeyPermission::USE.0, |
| /// Does nothing, and is not checked. For use of device identifiers, |
| /// the caller must hold the READ_PRIVILEGED_PHONE_STATE Android |
| /// permission. |
| #[selinux(name = use_dev_id)] |
| UseDevId = KeyPermission::USE_DEV_ID.0, |
| } |
| ); |
| |
| implement_class!( |
| /// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`. |
| /// Using the implement_permission macro we get the same features as `KeyPerm`. |
| #[selinux(class_name = keystore2)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum KeystorePerm { |
| /// Checked when a new auth token is installed. |
| #[selinux(name = add_auth)] |
| AddAuth, |
| /// Checked when an app is uninstalled or wiped. |
| #[selinux(name = clear_ns)] |
| ClearNs, |
| /// Checked when the user state is queried from Keystore 2.0. |
| #[selinux(name = get_state)] |
| GetState, |
| /// Checked when Keystore 2.0 is asked to list a namespace that the caller |
| /// does not have the get_info permission for. |
| #[selinux(name = list)] |
| List, |
| /// Checked when Keystore 2.0 gets locked. |
| #[selinux(name = lock)] |
| Lock, |
| /// Checked when Keystore 2.0 shall be reset. |
| #[selinux(name = reset)] |
| Reset, |
| /// Checked when Keystore 2.0 shall be unlocked. |
| #[selinux(name = unlock)] |
| Unlock, |
| /// Checked when user is added or removed. |
| #[selinux(name = change_user)] |
| ChangeUser, |
| /// Checked when password of the user is changed. |
| #[selinux(name = change_password)] |
| ChangePassword, |
| /// Checked when a UID is cleared. |
| #[selinux(name = clear_uid)] |
| ClearUID, |
| /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens. |
| #[selinux(name = get_auth_token)] |
| GetAuthToken, |
| /// Checked when earlyBootEnded() is called. |
| #[selinux(name = early_boot_ended)] |
| EarlyBootEnded, |
| /// Checked when IKeystoreMaintenance::onDeviceOffBody is called. |
| #[selinux(name = report_off_body)] |
| ReportOffBody, |
| /// Checked when IkeystoreMetrics::pullMetrics is called. |
| #[selinux(name = pull_metrics)] |
| PullMetrics, |
| /// Checked when IKeystoreMaintenance::deleteAllKeys is called. |
| #[selinux(name = delete_all_keys)] |
| DeleteAllKeys, |
| /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey |
| #[selinux(name = get_attestation_key)] |
| GetAttestationKey, |
| } |
| ); |
| |
| /// 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::ManageBlob, KeyPerm::Grant]; |
| /// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob]; |
| /// |
| /// 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::ManageBlob), 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<Self::Item> { |
| 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<KeyPerm> for KeyPermSet { |
| fn from(p: KeyPerm) -> Self { |
| Self(p as i32) |
| } |
| } |
| |
| /// allow conversion from the AIDL wire type i32 to a permission set. |
| impl From<i32> for KeyPermSet { |
| fn from(p: i32) -> Self { |
| Self(p) |
| } |
| } |
| |
| impl From<KeyPermSet> 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<T: Into<KeyPermSet>>(&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 as i32 $(| $tail 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_permission` 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_permission(caller_ctx, &target_context, perm) |
| } |
| |
| /// Uses `selinux::check_permission` 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_permission(caller_ctx, &target_context, KeyPerm::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_permission(caller_ctx, &target_context, p).context(ks_err!( |
| "check_permission failed. \ |
| The caller may have tried to grant a permission that they don't possess. {:?}", |
| p |
| ))? |
| } |
| Ok(()) |
| } |
| |
| /// Uses `selinux::check_permission` 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_permission. 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<KeyPermSet>, |
| ) -> 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(ks_err!("getcon failed."))? |
| } |
| Domain::SELINUX => lookup_keystore2_key_context(key.nspace) |
| .context(ks_err!("Domain::SELINUX: Failed to lookup namespace."))?, |
| Domain::GRANT => { |
| match access_vector { |
| Some(_) => { |
| return Err(selinux::Error::perm()) |
| .context(format!("\"{}\" not granted", perm.name())); |
| } |
| None => { |
| // If DOMAIN_GRANT was selected an access vector must be supplied. |
| return Err(KsError::sys()).context(ks_err!( |
| "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(ks_err!("Cannot check permission for Domain::KEY_ID.",)); |
| } |
| Domain::BLOB => { |
| let tctx = lookup_keystore2_key_context(key.nspace) |
| .context(ks_err!("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_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?; |
| |
| tctx |
| } |
| _ => { |
| return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) |
| .context(format!("Unknown domain value: \"{:?}\".", key.domain)) |
| } |
| }; |
| |
| selinux::check_permission(caller_ctx, &target_context, perm) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use anyhow::anyhow; |
| use anyhow::Result; |
| use keystore2_selinux::*; |
| |
| const ALL_PERMS: KeyPermSet = key_perm_set![ |
| KeyPerm::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::UseDevId, |
| KeyPerm::ReqForcedOp, |
| KeyPerm::GenUniqueId, |
| KeyPerm::Grant, |
| KeyPerm::GetInfo, |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| KeyPerm::ConvertStorageKeyToEphemeral, |
| ]; |
| |
| const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![ |
| KeyPerm::Delete, |
| KeyPerm::UseDevId, |
| // No KeyPerm::Grant |
| KeyPerm::GetInfo, |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| ]; |
| |
| const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![ |
| KeyPerm::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::UseDevId, |
| KeyPerm::ReqForcedOp, |
| KeyPerm::GenUniqueId, |
| // No KeyPerm::Grant |
| KeyPerm::GetInfo, |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| KeyPerm::ConvertStorageKeyToEphemeral, |
| ]; |
| |
| const UNPRIV_PERMS: KeyPermSet = key_perm_set![ |
| KeyPerm::Delete, |
| KeyPerm::GetInfo, |
| 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> { |
| 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::<selinux::Error>() |
| ); |
| }; |
| } |
| |
| 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::AddAuth).is_ok()); |
| assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok()); |
| assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::GetState).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::ChangeUser).is_ok()); |
| assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok()); |
| assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok()); |
| let shell_ctx = Context::new("u:r:shell:s0")?; |
| assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth)); |
| assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs)); |
| assert!(check_keystore_permission(&shell_ctx, KeystorePerm::GetState).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::ChangeUser)); |
| assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword)); |
| assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID)); |
| 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::GetInfo, &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::UseDevId, &key, &None).is_ok()); |
| assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &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::GetInfo, &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::ReqForcedOp, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &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::GetInfo]) |
| )); |
| |
| 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, |
| }; |
| |
| 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::GetInfo, &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()); |
| |
| if is_su { |
| assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok()); |
| assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok()); |
| assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok()); |
| assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok()); |
| assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok()); |
| } else { |
| assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None)); |
| assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &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::<KsError>() |
| ); |
| Ok(()) |
| } |
| |
| #[test] |
| fn key_perm_set_all_test() { |
| let v = key_perm_set![ |
| KeyPerm::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::UseDevId, |
| KeyPerm::ReqForcedOp, |
| KeyPerm::GenUniqueId, |
| KeyPerm::Grant, |
| KeyPerm::GetInfo, |
| 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().name(), "delete"); |
| assert_eq!(i.next().unwrap().name(), "gen_unique_id"); |
| assert_eq!(i.next().unwrap().name(), "get_info"); |
| assert_eq!(i.next().unwrap().name(), "grant"); |
| assert_eq!(i.next().unwrap().name(), "manage_blob"); |
| assert_eq!(i.next().unwrap().name(), "rebind"); |
| assert_eq!(i.next().unwrap().name(), "req_forced_op"); |
| assert_eq!(i.next().unwrap().name(), "update"); |
| assert_eq!(i.next().unwrap().name(), "use"); |
| assert_eq!(i.next().unwrap().name(), "use_dev_id"); |
| assert_eq!(None, i.next()); |
| } |
| #[test] |
| fn key_perm_set_sparse_test() { |
| let v = key_perm_set![ |
| KeyPerm::ManageBlob, |
| KeyPerm::ReqForcedOp, |
| KeyPerm::GenUniqueId, |
| 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().name(), "gen_unique_id"); |
| assert_eq!(i.next().unwrap().name(), "manage_blob"); |
| assert_eq!(i.next().unwrap().name(), "req_forced_op"); |
| assert_eq!(i.next().unwrap().name(), "update"); |
| assert_eq!(i.next().unwrap().name(), "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::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::UseDevId, |
| KeyPerm::ReqForcedOp, |
| KeyPerm::GenUniqueId, |
| KeyPerm::Grant, |
| KeyPerm::GetInfo, |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| ]; |
| let v2 = key_perm_set![ |
| KeyPerm::ManageBlob, |
| 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::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| ]; |
| let v2 = key_perm_set![ |
| KeyPerm::ManageBlob, |
| 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::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::Grant, // only in v1 |
| KeyPerm::Rebind, |
| KeyPerm::Update, |
| KeyPerm::Use, |
| ]; |
| let v2 = key_perm_set![ |
| KeyPerm::ManageBlob, |
| KeyPerm::Delete, |
| KeyPerm::ReqForcedOp, // 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::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,]; |
| let v2 = |
| key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,]; |
| assert!(!v1.includes(v2)); |
| assert!(!v2.includes(v1)); |
| } |
| } |