Merge changes Ibc7851ba,Ie743cff7
* changes: Cache the KeystoreKeyBackend with a lazy static. Implement Keystore 2.0 access control
This commit is contained in:
commit
ce8c6af02a
4 changed files with 927 additions and 0 deletions
|
@ -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",
|
||||
|
|
|
@ -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<Context> {
|
||||
let mut con: *mut c_char = ptr::null_mut();
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
|
||||
pub mod database;
|
||||
pub mod error;
|
||||
pub mod permission;
|
||||
|
|
916
keystore2/src/permission.rs
Normal file
916
keystore2/src/permission.rs
Normal file
|
@ -0,0 +1,916 @@
|
|||
// 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;
|
||||
|
||||
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<selinux::Context> {
|
||||
KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string())
|
||||
}
|
||||
|
||||
/// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements
|
||||
/// * `From<i32> for `MyPerm`, where each unknown numeric value is mapped to the given default,
|
||||
/// here `None`
|
||||
/// * `Into<MyPermission> 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
|
||||
// `<enum variant> = <integer_literal>, selinux name: <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 `<enum variant> = <integer_literal>, 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
|
||||
// `<enum variant> = <integer_literal>, <selinux_name>, <selinux_name>;`
|
||||
// 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<i32> 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<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.0 as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPermSet {
|
||||
/// Returns true iff this permission set has all of the permissions that are in `other`.
|
||||
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.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;
|
||||
|
||||
let target_context = match key.domain {
|
||||
Domain::App => getcon().context("check_grant_permission: getcon failed.")?,
|
||||
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)),
|
||||
};
|
||||
|
||||
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<KeyPermSet>,
|
||||
) -> anyhow::Result<()> {
|
||||
use aidl::Domain;
|
||||
|
||||
let target_context = match key.domain {
|
||||
// apps get the default keystore context
|
||||
Domain::App => getcon().context("check_key_permission: getcon failed.")?,
|
||||
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) => {
|
||||
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 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.
|
||||
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> {
|
||||
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::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::<KsError>()
|
||||
);
|
||||
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));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue