Keystore 2.0: Refactor permissions. 3/5
* Add trait ClassPermission and fn check_permission. This binds
together permission names and their class name.
* Rename implement_permission! to implement_class!.
* Add #[selinux(class_name = <name>)] stanza to the syntax of
implement_class!.
Test: keystore2_test for regressions.
Bug: 203555519
This reverts commit b8fd77fba016c4c908d371d546a5d86aff4a78d7.
Change-Id: I6863269ea4af5a6d0b36cf17e0238c81bc713d48
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index aeb0e15..1d9ac12 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -333,14 +333,21 @@
}
}
+/// Represents an SEPolicy permission belonging to a specific class.
+pub trait ClassPermission {
+ /// The permission string of the given instance as specified in the class vector.
+ fn name(&self) -> &'static str;
+ /// The class of the permission.
+ fn class_name(&self) -> &'static str;
+}
+
/// 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
+/// The example below implements `enum MyPermission with public visibility:
/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
-/// any variant not specified to the default.
+/// any variant not specified to the default `None` with value `0`.
/// * Every variant has a constructor with a name corresponding to its lower case SELinux string
/// representation.
-/// * `MyPermission::to_selinux(&self)` returns the SELinux string representation of the
-/// corresponding permission.
+/// * `MyPermission` implements ClassPermission.
/// * An implicit default values `MyPermission::None` is created with a numeric representation
/// of `0` and a string representation of `"none"`.
/// * Specifying a value is optional. If the value is omitted it is set to the value of the
@@ -348,35 +355,62 @@
///
/// ## Example
/// ```
-/// implement_permission!(
+/// implement_class!(
/// /// MyPermission documentation.
/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+/// #[selinux(class_name = my_class)]
/// pub enum MyPermission {
/// #[selinux(name = foo)]
/// Foo = 1,
/// #[selinux(name = bar)]
/// Bar = 2,
/// #[selinux(name = snafu)]
-/// Snafu, // Implicit value: MyPermission::Bar << 1 = 4
+/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4
/// }
+/// assert_eq!(MyPermission::Foo.name(), &"foo");
+/// assert_eq!(MyPermission::Foo.class_name(), &"my_class");
+/// assert_eq!(MyPermission::Snafu as i32, 4);
/// );
/// ```
#[macro_export]
-macro_rules! implement_permission {
+macro_rules! implement_class {
+ // First rule: Public interface.
(
- $(#[$enum_meta:meta])*
+ $(#[$($enum_meta:tt)+])*
+ $enum_vis:vis enum $enum_name:ident $body:tt
+ ) => {
+ implement_class! {
+ @extract_class
+ []
+ [$(#[$($enum_meta)+])*]
+ $enum_vis enum $enum_name $body
+ }
+ };
+
+ // The next two rules extract the #[selinux(class_name = <name>)] meta field from
+ // the types meta list.
+ // This first rule finds the field and terminates the recursion through the meta fields.
+ (
+ @extract_class
+ [$(#[$mout:meta])*]
+ [
+ #[selinux(class_name = $class_name:ident)]
+ $(#[$($mtail:tt)+])*
+ ]
$enum_vis:vis enum $enum_name:ident {
$(
$(#[$($emeta:tt)+])*
- $vname:ident$( = $vval:tt)?
+ $vname:ident$( = $vval:expr)?
),* $(,)?
}
) => {
- implement_permission!{
- @extract_attr
- $(#[$enum_meta])*
+ implement_class!{
+ @extract_perm_name
+ $class_name
+ $(#[$mout])*
+ $(#[$($mtail)+])*
$enum_vis enum $enum_name {
- 1
+ 1;
[]
[$(
[] [$(#[$($emeta)+])*]
@@ -386,11 +420,39 @@
}
};
+ // The second rule iterates through the type global meta fields.
(
- @extract_attr
+ @extract_class
+ [$(#[$mout:meta])*]
+ [
+ #[$front:meta]
+ $(#[$($mtail:tt)+])*
+ ]
+ $enum_vis:vis enum $enum_name:ident $body:tt
+ ) => {
+ implement_class!{
+ @extract_class
+ [
+ $(#[$mout])*
+ #[$front]
+ ]
+ [$(#[$($mtail)+])*]
+ $enum_vis enum $enum_name $body
+ }
+ };
+
+ // The next four rules implement two nested recursions. The outer iterates through
+ // the enum variants and the inner iterates through the meta fields of each variant.
+ // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner
+ // recursion and descend a level in the outer recursion.
+ // The first rule matches variants with explicit initializer $vval. And updates the next
+ // value to ($vval << 1).
+ (
+ @extract_perm_name
+ $class_name:ident
$(#[$enum_meta:meta])*
$enum_vis:vis enum $enum_name:ident {
- $next_val:tt
+ $next_val:expr;
[$($out:tt)*]
[
[$(#[$mout:meta])*]
@@ -398,16 +460,17 @@
#[selinux(name = $selinux_name:ident)]
$(#[$($mtail:tt)+])*
]
- $vname:ident = $vval:tt,
+ $vname:ident = $vval:expr,
$($tail:tt)*
]
}
) => {
- implement_permission!{
- @extract_attr
+ implement_class!{
+ @extract_perm_name
+ $class_name
$(#[$enum_meta])*
$enum_vis enum $enum_name {
- ($vval << 1)
+ ($vval << 1);
[
$($out)*
$(#[$mout])*
@@ -419,11 +482,14 @@
}
};
+ // The second rule differs form the previous in that there is no explicit initializer.
+ // Instead $next_val is used as initializer and the next value is set to (&next_val << 1).
(
- @extract_attr
+ @extract_perm_name
+ $class_name:ident
$(#[$enum_meta:meta])*
$enum_vis:vis enum $enum_name:ident {
- $next_val:tt
+ $next_val:expr;
[$($out:tt)*]
[
[$(#[$mout:meta])*]
@@ -436,11 +502,12 @@
]
}
) => {
- implement_permission!{
- @extract_attr
+ implement_class!{
+ @extract_perm_name
+ $class_name
$(#[$enum_meta])*
$enum_vis enum $enum_name {
- ($next_val << 1)
+ ($next_val << 1);
[
$($out)*
$(#[$mout])*
@@ -452,12 +519,13 @@
}
};
-
+ // The third rule descends a step in the inner recursion.
(
- @extract_attr
+ @extract_perm_name
+ $class_name:ident
$(#[$enum_meta:meta])*
$enum_vis:vis enum $enum_name:ident {
- $next_val:tt
+ $next_val:expr;
[$($out:tt)*]
[
[$(#[$mout:meta])*]
@@ -465,16 +533,17 @@
#[$front:meta]
$(#[$($mtail:tt)+])*
]
- $vname:ident$( = $vval:tt)?,
+ $vname:ident$( = $vval:expr)?,
$($tail:tt)*
]
}
) => {
- implement_permission!{
- @extract_attr
+ implement_class!{
+ @extract_perm_name
+ $class_name
$(#[$enum_meta])*
$enum_vis enum $enum_name {
- $next_val
+ $next_val;
[$($out)*]
[
[
@@ -489,17 +558,21 @@
}
};
+ // The fourth rule terminates the outer recursion and transitions to the
+ // implementation phase @spill.
(
- @extract_attr
+ @extract_perm_name
+ $class_name:ident
$(#[$enum_meta:meta])*
$enum_vis:vis enum $enum_name:ident {
- $next_val:tt
+ $next_val:expr;
[$($out:tt)*]
[]
}
) => {
- implement_permission!{
+ implement_class!{
@spill
+ $class_name
$(#[$enum_meta])*
$enum_vis enum $enum_name {
$($out)*
@@ -509,17 +582,18 @@
(
@spill
+ $class_name:ident
$(#[$enum_meta:meta])*
$enum_vis:vis enum $enum_name:ident {
$(
$(#[$emeta:meta])*
- $selinux_name:ident $vname:ident = $vval:tt,
+ $selinux_name:ident $vname:ident = $vval:expr,
)*
}
) => {
$(#[$enum_meta])*
$enum_vis enum $enum_name {
- /// The default variant of an enum.
+ /// The default variant of the enum.
None = 0,
$(
$(#[$emeta])*
@@ -547,17 +621,19 @@
}
}
- impl $enum_name {
-
- /// Returns a string representation of the permission as required by
- /// `selinux::check_access`.
- pub fn to_selinux(self) -> &'static str {
+ impl ClassPermission for $enum_name {
+ fn name(&self) -> &'static str {
match self {
Self::None => &"none",
$(Self::$vname => stringify!($selinux_name),)*
}
}
+ fn class_name(&self) -> &'static str {
+ stringify!($class_name)
+ }
+ }
+ impl $enum_name {
/// Creates an instance representing a permission with the same name.
pub const fn none() -> Self { Self::None }
$(
@@ -568,6 +644,11 @@
};
}
+/// Calls `check_access` on the given class permission.
+pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> {
+ check_access(source, target, perm.class_name(), perm.name())
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index fad5636..89cbd5d 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -25,7 +25,7 @@
use anyhow::Context as AnyhowContext;
use keystore2_selinux as selinux;
use lazy_static::lazy_static;
-use selinux::{implement_permission, Backend};
+use selinux::{implement_class, Backend, ClassPermission};
use std::cmp::PartialEq;
use std::convert::From;
use std::ffi::CStr;
@@ -64,12 +64,12 @@
/// 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
+/// * `MyPerm.name()(&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
+/// constructor function (e.g. `MePerm::use_()`) but the string returned by `name()` will
/// still be `"use"`.
///
/// ## Example
@@ -149,17 +149,20 @@
}
}
- impl $name {
- /// Returns a string representation of the permission as required by
- /// `selinux::check_access`.
- pub fn to_selinux(self) -> &'static str {
+ impl ClassPermission for $name {
+ fn name(&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),
}
}
+ fn class_name(&self) -> &'static str {
+ "keystore2_key"
+ }
+ }
+ impl $name {
/// Creates an instance representing a permission with the same name.
pub const fn $def_selinux_name() -> Self { Self($aidl_name::$def_name) }
$(
@@ -182,8 +185,7 @@
/// 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());
+ /// selinux::check_permission(source_context, target_context, KeyPerm::get_info());
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
KeyPerm from KeyPermission with default (NONE, none) {
@@ -201,9 +203,10 @@
}
);
-implement_permission!(
+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)]
pub enum KeystorePerm {
/// Checked when a new auth token is installed.
@@ -363,14 +366,14 @@
}
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` may access
+/// 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_access(caller_ctx, &target_context, "keystore2", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` has
+/// 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`.
///
@@ -395,7 +398,7 @@
_ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)),
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", "grant")
+ selinux::check_permission(caller_ctx, &target_context, KeyPerm::grant())
.context("Grant permission is required when granting.")?;
if access_vec.includes(KeyPerm::grant()) {
@@ -403,19 +406,16 @@
}
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
- ))?
+ selinux::check_permission(caller_ctx, &target_context, p).context(format!(
+ "check_grant_permission: check_permission 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`
+/// 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`.
///
@@ -425,7 +425,7 @@
/// 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`
+/// * `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.
@@ -469,7 +469,7 @@
match access_vector {
Some(_) => {
return Err(selinux::Error::perm())
- .context(format!("\"{}\" not granted", perm.to_selinux()));
+ .context(format!("\"{}\" not granted", perm.name()));
}
None => {
// If DOMAIN_GRANT was selected an access vector must be supplied.
@@ -490,12 +490,7 @@
.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(),
- )?;
+ selinux::check_permission(caller_ctx, &tctx, KeyPerm::manage_blob())?;
tctx
}
@@ -505,7 +500,7 @@
}
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
#[cfg(test)]
@@ -881,16 +876,16 @@
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!(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]
@@ -903,11 +898,11 @@
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!(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]