blob: 1d9ac1220dd386d9887bf3478418acba6021a163 [file] [log] [blame]
Janis Danisevskisce995432020-07-21 12:22:34 -07001// Copyright 2020, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Janis Danisevskisce995432020-07-21 12:22:34 -070015//! This crate provides some safe wrappers around the libselinux API. It is currently limited
16//! to the API surface that Keystore 2.0 requires to perform permission checks against
17//! the SEPolicy. Notably, it provides wrappers for:
18//! * getcon
19//! * selinux_check_access
20//! * selabel_lookup for the keystore2_key backend.
21//! And it provides an owning wrapper around context strings `Context`.
22
Janis Danisevskisff188d32021-05-13 13:27:13 -070023use anyhow::Context as AnyhowContext;
24use anyhow::{anyhow, Result};
25use lazy_static::lazy_static;
26pub use selinux::pid_t;
27use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
28use selinux::SELINUX_CB_LOG;
29use selinux_bindgen as selinux;
Janis Danisevskisce995432020-07-21 12:22:34 -070030use std::ffi::{CStr, CString};
31use std::fmt;
32use std::io;
Janis Danisevskis4ad056f2020-08-05 19:46:46 +000033use std::marker::{Send, Sync};
Janis Danisevskisce995432020-07-21 12:22:34 -070034pub use std::ops::Deref;
35use std::os::raw::c_char;
36use std::ptr;
37use std::sync;
38
Janis Danisevskisce995432020-07-21 12:22:34 -070039static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
40
Janis Danisevskisff188d32021-05-13 13:27:13 -070041lazy_static! {
42 /// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
43 /// However, avc_init is deprecated and not exported by androids version of libselinux.
44 /// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
45 /// that remains right now is to put a big lock around calls into libselinux.
46 /// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
47 /// certain of that, we leave the extra locks in place
48 static ref LIB_SELINUX_LOCK: sync::Mutex<()> = Default::default();
49}
50
Janis Danisevskisce995432020-07-21 12:22:34 -070051fn redirect_selinux_logs_to_logcat() {
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070052 // `selinux_set_callback` assigns the static lifetime function pointer
Janis Danisevskisce995432020-07-21 12:22:34 -070053 // `selinux_log_callback` to a static lifetime variable.
54 let cb = selinux::selinux_callback { func_log: Some(selinux::selinux_log_callback) };
55 unsafe {
56 selinux::selinux_set_callback(SELINUX_CB_LOG as i32, cb);
57 }
58}
59
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070060// This function must be called before any entry point into lib selinux.
Janis Danisevskisce995432020-07-21 12:22:34 -070061// Or leave a comment reasoning why calling this macro is not necessary
62// for a given entry point.
63fn init_logger_once() {
64 SELINUX_LOG_INIT.call_once(redirect_selinux_logs_to_logcat)
65}
66
67/// Selinux Error code.
68#[derive(thiserror::Error, Debug, PartialEq)]
69pub enum Error {
70 /// Indicates that an access check yielded no access.
71 #[error("Permission Denied")]
72 PermissionDenied,
73 /// Indicates an unexpected system error. Nested string provides some details.
74 #[error("Selinux SystemError: {0}")]
75 SystemError(String),
76}
77
78impl Error {
79 /// Constructs a `PermissionDenied` error.
80 pub fn perm() -> Self {
81 Error::PermissionDenied
82 }
83 fn sys<T: Into<String>>(s: T) -> Self {
84 Error::SystemError(s.into())
85 }
86}
87
88/// Context represents an SELinux context string. It can take ownership of a raw
89/// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
90/// `freecon` to free the resources when dropped. In its second variant it stores
91/// an `std::ffi::CString` that can be initialized from a Rust string slice.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070092#[derive(Debug)]
Janis Danisevskisce995432020-07-21 12:22:34 -070093pub enum Context {
94 /// Wraps a raw context c-string as returned by libselinux.
95 Raw(*mut ::std::os::raw::c_char),
96 /// Stores a context string as `std::ffi::CString`.
97 CString(CString),
98}
99
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700100impl PartialEq for Context {
101 fn eq(&self, other: &Self) -> bool {
102 // We dereference both and thereby delegate the comparison
103 // to `CStr`'s implementation of `PartialEq`.
104 **self == **other
105 }
106}
107
108impl Eq for Context {}
109
Janis Danisevskisce995432020-07-21 12:22:34 -0700110impl fmt::Display for Context {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
113 }
114}
115
116impl Drop for Context {
117 fn drop(&mut self) {
118 if let Self::Raw(p) = self {
119 // No need to initialize the logger here, because
120 // `freecon` cannot run unless `Backend::lookup` or `getcon`
121 // has run.
122 unsafe { selinux::freecon(*p) };
123 }
124 }
125}
126
127impl Deref for Context {
128 type Target = CStr;
129
130 fn deref(&self) -> &Self::Target {
131 match self {
132 Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
Chris Wailesd5aaaef2021-07-27 16:04:33 -0700133 Self::CString(cstr) => cstr,
Janis Danisevskisce995432020-07-21 12:22:34 -0700134 }
135 }
136}
137
138impl Context {
139 /// Initializes the `Context::CString` variant from a Rust string slice.
140 pub fn new(con: &str) -> Result<Self> {
141 Ok(Self::CString(
142 CString::new(con)
143 .with_context(|| format!("Failed to create Context with \"{}\"", con))?,
144 ))
145 }
146}
147
148/// The backend trait provides a uniform interface to all libselinux context backends.
149/// Currently, we only implement the KeystoreKeyBackend though.
150pub trait Backend {
151 /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
152 fn lookup(&self, key: &str) -> Result<Context>;
153}
154
155/// Keystore key backend takes onwnership of the SELinux context handle returned by
156/// `selinux_android_keystore2_key_context_handle` and uses `selabel_close` to free
157/// the handle when dropped.
158/// It implements `Backend` to provide keystore_key label lookup functionality.
159pub struct KeystoreKeyBackend {
160 handle: *mut selinux::selabel_handle,
161}
162
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000163// KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
164unsafe impl Sync for KeystoreKeyBackend {}
165unsafe impl Send for KeystoreKeyBackend {}
166
Janis Danisevskisce995432020-07-21 12:22:34 -0700167impl KeystoreKeyBackend {
168 const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32;
169
170 /// Creates a new instance representing an SELinux context handle as returned by
171 /// `selinux_android_keystore2_key_context_handle`.
172 pub fn new() -> Result<Self> {
173 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700174 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
175
Janis Danisevskisce995432020-07-21 12:22:34 -0700176 let handle = unsafe { selinux::selinux_android_keystore2_key_context_handle() };
177 if handle.is_null() {
178 return Err(anyhow!(Error::sys("Failed to open KeystoreKeyBackend")));
179 }
180 Ok(KeystoreKeyBackend { handle })
181 }
182}
183
184impl Drop for KeystoreKeyBackend {
185 fn drop(&mut self) {
186 // No need to initialize the logger here because it cannot be called unless
187 // KeystoreKeyBackend::new has run.
188 unsafe { selinux::selabel_close(self.handle) };
189 }
190}
191
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000192// Because KeystoreKeyBackend is Sync and Send, member function must never call
193// non thread safe libselinux functions. As of this writing no non thread safe
194// functions exist that could be called on a label backend handle.
Janis Danisevskisce995432020-07-21 12:22:34 -0700195impl Backend for KeystoreKeyBackend {
196 fn lookup(&self, key: &str) -> Result<Context> {
197 let mut con: *mut c_char = ptr::null_mut();
198 let c_key = CString::new(key).with_context(|| {
199 format!("selabel_lookup: Failed to convert key \"{}\" to CString.", key)
200 })?;
201 match unsafe {
202 // No need to initialize the logger here because it cannot run unless
203 // KeystoreKeyBackend::new has run.
Janis Danisevskisff188d32021-05-13 13:27:13 -0700204 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
205
Janis Danisevskisce995432020-07-21 12:22:34 -0700206 selinux::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_TYPE)
207 } {
208 0 => {
209 if !con.is_null() {
210 Ok(Context::Raw(con))
211 } else {
212 Err(anyhow!(Error::sys(format!(
213 "selabel_lookup returned a NULL context for key \"{}\"",
214 key
215 ))))
216 }
217 }
218 _ => Err(anyhow!(io::Error::last_os_error()))
219 .with_context(|| format!("selabel_lookup failed for key \"{}\"", key)),
220 }
221 }
222}
223
224/// Safe wrapper around libselinux `getcon`. It initializes the `Context::Raw` variant of the
225/// returned `Context`.
226///
227/// ## Return
228/// * Ok(Context::Raw()) if successful.
229/// * Err(Error::sys()) if getcon succeeded but returned a NULL pointer.
230/// * Err(io::Error::last_os_error()) if getcon failed.
231pub fn getcon() -> Result<Context> {
232 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700233 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
234
Janis Danisevskisce995432020-07-21 12:22:34 -0700235 let mut con: *mut c_char = ptr::null_mut();
236 match unsafe { selinux::getcon(&mut con) } {
237 0 => {
238 if !con.is_null() {
239 Ok(Context::Raw(con))
240 } else {
241 Err(anyhow!(Error::sys("getcon returned a NULL context")))
242 }
243 }
244 _ => Err(anyhow!(io::Error::last_os_error())).context("getcon failed"),
245 }
246}
247
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700248/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
249/// returned `Context`.
250///
251/// ## Return
252/// * Ok(Context::Raw()) if successful.
253/// * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
254/// * Err(io::Error::last_os_error()) if getpidcon failed.
255pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
256 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700257 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
258
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700259 let mut con: *mut c_char = ptr::null_mut();
260 match unsafe { selinux::getpidcon(pid, &mut con) } {
261 0 => {
262 if !con.is_null() {
263 Ok(Context::Raw(con))
264 } else {
265 Err(anyhow!(Error::sys(format!(
266 "getpidcon returned a NULL context for pid {}",
267 pid
268 ))))
269 }
270 }
271 _ => Err(anyhow!(io::Error::last_os_error()))
272 .context(format!("getpidcon failed for pid {}", pid)),
273 }
274}
275
Janis Danisevskisce995432020-07-21 12:22:34 -0700276/// Safe wrapper around selinux_check_access.
277///
278/// ## Return
279/// * Ok(()) iff the requested access was granted.
280/// * Err(anyhow!(Error::perm()))) if the permission was denied.
281/// * Err(anyhow!(ioError::last_os_error())) if any other error occurred while performing
282/// the access check.
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700283pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
Janis Danisevskisce995432020-07-21 12:22:34 -0700284 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700285
Janis Danisevskisce995432020-07-21 12:22:34 -0700286 let c_tclass = CString::new(tclass).with_context(|| {
287 format!("check_access: Failed to convert tclass \"{}\" to CString.", tclass)
288 })?;
289 let c_perm = CString::new(perm).with_context(|| {
290 format!("check_access: Failed to convert perm \"{}\" to CString.", perm)
291 })?;
292
293 match unsafe {
Janis Danisevskisff188d32021-05-13 13:27:13 -0700294 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
295
Janis Danisevskisce995432020-07-21 12:22:34 -0700296 selinux::selinux_check_access(
297 source.as_ptr(),
298 target.as_ptr(),
299 c_tclass.as_ptr(),
300 c_perm.as_ptr(),
301 ptr::null_mut(),
302 )
303 } {
304 0 => Ok(()),
305 _ => {
306 let e = io::Error::last_os_error();
307 match e.kind() {
308 io::ErrorKind::PermissionDenied => Err(anyhow!(Error::perm())),
309 _ => Err(anyhow!(e)),
310 }
311 .with_context(|| {
312 format!(
313 concat!(
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700314 "check_access: Failed with sctx: {:?} tctx: {:?}",
Janis Danisevskisce995432020-07-21 12:22:34 -0700315 " with target class: \"{}\" perm: \"{}\""
316 ),
317 source, target, tclass, perm
318 )
319 })
320 }
321 }
322}
323
Janis Danisevskisa578d392021-09-20 15:44:06 -0700324/// Safe wrapper around setcon.
325pub fn setcon(target: &CStr) -> std::io::Result<()> {
326 // SAFETY: `setcon` takes a const char* and only performs read accesses on it
327 // using strdup and strcmp. `setcon` does not retain a pointer to `target`
328 // and `target` outlives the call to `setcon`.
329 if unsafe { selinux::setcon(target.as_ptr()) } != 0 {
330 Err(std::io::Error::last_os_error())
331 } else {
332 Ok(())
333 }
334}
335
Janis Danisevskis56af0312021-10-18 16:11:41 -0700336/// Represents an SEPolicy permission belonging to a specific class.
337pub trait ClassPermission {
338 /// The permission string of the given instance as specified in the class vector.
339 fn name(&self) -> &'static str;
340 /// The class of the permission.
341 fn class_name(&self) -> &'static str;
342}
343
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700344/// This macro implements an enum with values mapped to SELinux permission names.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700345/// The example below implements `enum MyPermission with public visibility:
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700346/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
Janis Danisevskis56af0312021-10-18 16:11:41 -0700347/// any variant not specified to the default `None` with value `0`.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700348/// * Every variant has a constructor with a name corresponding to its lower case SELinux string
349/// representation.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700350/// * `MyPermission` implements ClassPermission.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700351/// * An implicit default values `MyPermission::None` is created with a numeric representation
352/// of `0` and a string representation of `"none"`.
353/// * Specifying a value is optional. If the value is omitted it is set to the value of the
354/// previous variant left shifted by 1.
355///
356/// ## Example
357/// ```
Janis Danisevskis56af0312021-10-18 16:11:41 -0700358/// implement_class!(
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700359/// /// MyPermission documentation.
360/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700361/// #[selinux(class_name = my_class)]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700362/// pub enum MyPermission {
363/// #[selinux(name = foo)]
364/// Foo = 1,
365/// #[selinux(name = bar)]
366/// Bar = 2,
367/// #[selinux(name = snafu)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700368/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700369/// }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700370/// assert_eq!(MyPermission::Foo.name(), &"foo");
371/// assert_eq!(MyPermission::Foo.class_name(), &"my_class");
372/// assert_eq!(MyPermission::Snafu as i32, 4);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700373/// );
374/// ```
375#[macro_export]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700376macro_rules! implement_class {
377 // First rule: Public interface.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700378 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700379 $(#[$($enum_meta:tt)+])*
380 $enum_vis:vis enum $enum_name:ident $body:tt
381 ) => {
382 implement_class! {
383 @extract_class
384 []
385 [$(#[$($enum_meta)+])*]
386 $enum_vis enum $enum_name $body
387 }
388 };
389
390 // The next two rules extract the #[selinux(class_name = <name>)] meta field from
391 // the types meta list.
392 // This first rule finds the field and terminates the recursion through the meta fields.
393 (
394 @extract_class
395 [$(#[$mout:meta])*]
396 [
397 #[selinux(class_name = $class_name:ident)]
398 $(#[$($mtail:tt)+])*
399 ]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700400 $enum_vis:vis enum $enum_name:ident {
401 $(
402 $(#[$($emeta:tt)+])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700403 $vname:ident$( = $vval:expr)?
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700404 ),* $(,)?
405 }
406 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700407 implement_class!{
408 @extract_perm_name
409 $class_name
410 $(#[$mout])*
411 $(#[$($mtail)+])*
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700412 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700413 1;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700414 []
415 [$(
416 [] [$(#[$($emeta)+])*]
417 $vname$( = $vval)?,
418 )*]
419 }
420 }
421 };
422
Janis Danisevskis56af0312021-10-18 16:11:41 -0700423 // The second rule iterates through the type global meta fields.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700424 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700425 @extract_class
426 [$(#[$mout:meta])*]
427 [
428 #[$front:meta]
429 $(#[$($mtail:tt)+])*
430 ]
431 $enum_vis:vis enum $enum_name:ident $body:tt
432 ) => {
433 implement_class!{
434 @extract_class
435 [
436 $(#[$mout])*
437 #[$front]
438 ]
439 [$(#[$($mtail)+])*]
440 $enum_vis enum $enum_name $body
441 }
442 };
443
444 // The next four rules implement two nested recursions. The outer iterates through
445 // the enum variants and the inner iterates through the meta fields of each variant.
446 // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner
447 // recursion and descend a level in the outer recursion.
448 // The first rule matches variants with explicit initializer $vval. And updates the next
449 // value to ($vval << 1).
450 (
451 @extract_perm_name
452 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700453 $(#[$enum_meta:meta])*
454 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700455 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700456 [$($out:tt)*]
457 [
458 [$(#[$mout:meta])*]
459 [
460 #[selinux(name = $selinux_name:ident)]
461 $(#[$($mtail:tt)+])*
462 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700463 $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700464 $($tail:tt)*
465 ]
466 }
467 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700468 implement_class!{
469 @extract_perm_name
470 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700471 $(#[$enum_meta])*
472 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700473 ($vval << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700474 [
475 $($out)*
476 $(#[$mout])*
477 $(#[$($mtail)+])*
478 $selinux_name $vname = $vval,
479 ]
480 [$($tail)*]
481 }
482 }
483 };
484
Janis Danisevskis56af0312021-10-18 16:11:41 -0700485 // The second rule differs form the previous in that there is no explicit initializer.
486 // Instead $next_val is used as initializer and the next value is set to (&next_val << 1).
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700487 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700488 @extract_perm_name
489 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700490 $(#[$enum_meta:meta])*
491 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700492 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700493 [$($out:tt)*]
494 [
495 [$(#[$mout:meta])*]
496 [
497 #[selinux(name = $selinux_name:ident)]
498 $(#[$($mtail:tt)+])*
499 ]
500 $vname:ident,
501 $($tail:tt)*
502 ]
503 }
504 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700505 implement_class!{
506 @extract_perm_name
507 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700508 $(#[$enum_meta])*
509 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700510 ($next_val << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700511 [
512 $($out)*
513 $(#[$mout])*
514 $(#[$($mtail)+])*
515 $selinux_name $vname = $next_val,
516 ]
517 [$($tail)*]
518 }
519 }
520 };
521
Janis Danisevskis56af0312021-10-18 16:11:41 -0700522 // The third rule descends a step in the inner recursion.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700523 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700524 @extract_perm_name
525 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700526 $(#[$enum_meta:meta])*
527 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700528 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700529 [$($out:tt)*]
530 [
531 [$(#[$mout:meta])*]
532 [
533 #[$front:meta]
534 $(#[$($mtail:tt)+])*
535 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700536 $vname:ident$( = $vval:expr)?,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700537 $($tail:tt)*
538 ]
539 }
540 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700541 implement_class!{
542 @extract_perm_name
543 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700544 $(#[$enum_meta])*
545 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700546 $next_val;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700547 [$($out)*]
548 [
549 [
550 $(#[$mout])*
551 #[$front]
552 ]
553 [$(#[$($mtail)+])*]
554 $vname$( = $vval)?,
555 $($tail)*
556 ]
557 }
558 }
559 };
560
Janis Danisevskis56af0312021-10-18 16:11:41 -0700561 // The fourth rule terminates the outer recursion and transitions to the
562 // implementation phase @spill.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700563 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700564 @extract_perm_name
565 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700566 $(#[$enum_meta:meta])*
567 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700568 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700569 [$($out:tt)*]
570 []
571 }
572 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700573 implement_class!{
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700574 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700575 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700576 $(#[$enum_meta])*
577 $enum_vis enum $enum_name {
578 $($out)*
579 }
580 }
581 };
582
583 (
584 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700585 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700586 $(#[$enum_meta:meta])*
587 $enum_vis:vis enum $enum_name:ident {
588 $(
589 $(#[$emeta:meta])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700590 $selinux_name:ident $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700591 )*
592 }
593 ) => {
594 $(#[$enum_meta])*
595 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700596 /// The default variant of the enum.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700597 None = 0,
598 $(
599 $(#[$emeta])*
600 $vname = $vval,
601 )*
602 }
603
604 impl From<i32> for $enum_name {
605 #[allow(non_upper_case_globals)]
606 fn from (p: i32) -> Self {
607 // Creating constants forces the compiler to evaluate the value expressions
608 // so that they can be used in the match statement below.
609 $(const $vname: i32 = $vval;)*
610 match p {
611 0 => Self::None,
612 $($vname => Self::$vname,)*
613 _ => Self::None,
614 }
615 }
616 }
617
618 impl From<$enum_name> for i32 {
619 fn from(p: $enum_name) -> i32 {
620 p as i32
621 }
622 }
623
Janis Danisevskis56af0312021-10-18 16:11:41 -0700624 impl ClassPermission for $enum_name {
625 fn name(&self) -> &'static str {
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700626 match self {
627 Self::None => &"none",
628 $(Self::$vname => stringify!($selinux_name),)*
629 }
630 }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700631 fn class_name(&self) -> &'static str {
632 stringify!($class_name)
633 }
634 }
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700635
Janis Danisevskis56af0312021-10-18 16:11:41 -0700636 impl $enum_name {
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700637 /// Creates an instance representing a permission with the same name.
638 pub const fn none() -> Self { Self::None }
639 $(
640 /// Creates an instance representing a permission with the same name.
641 pub const fn $selinux_name() -> Self { Self::$vname }
642 )*
643 }
644 };
645}
646
Janis Danisevskis56af0312021-10-18 16:11:41 -0700647/// Calls `check_access` on the given class permission.
648pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> {
649 check_access(source, target, perm.class_name(), perm.name())
650}
651
Janis Danisevskisce995432020-07-21 12:22:34 -0700652#[cfg(test)]
653mod tests {
654 use super::*;
655 use anyhow::Result;
656
657 /// The su_key namespace as defined in su.te and keystore_key_contexts of the
658 /// SePolicy (system/sepolicy).
659 static SU_KEY_NAMESPACE: &str = "0";
660 /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
661 /// SePolicy (system/sepolicy).
662 static SHELL_KEY_NAMESPACE: &str = "1";
663
664 fn check_context() -> Result<(Context, &'static str, bool)> {
665 let context = getcon()?;
666 match context.to_str().unwrap() {
667 "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
668 "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
669 c => Err(anyhow!(format!(
670 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
671 c
672 ))),
673 }
674 }
675
676 #[test]
677 fn test_getcon() -> Result<()> {
678 check_context()?;
679 Ok(())
680 }
681
682 #[test]
683 fn test_label_lookup() -> Result<()> {
684 let (_context, namespace, is_su) = check_context()?;
685 let backend = crate::KeystoreKeyBackend::new()?;
686 let context = backend.lookup(namespace)?;
687 if is_su {
688 assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
689 } else {
690 assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
691 }
692 Ok(())
693 }
694
695 #[test]
696 fn context_from_string() -> Result<()> {
697 let tctx = Context::new("u:object_r:keystore:s0").unwrap();
698 let sctx = Context::new("u:r:system_server:s0").unwrap();
699 check_access(&sctx, &tctx, "keystore2_key", "use")?;
700 Ok(())
701 }
702
703 mod perm {
704 use super::super::*;
705 use super::*;
706 use anyhow::Result;
707
708 /// check_key_perm(perm, privileged, priv_domain)
709 /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
710 /// indicating whether the permission is considered privileged.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700711 /// Privileged permissions are expected to be denied to `shell` users but granted
Janis Danisevskisce995432020-07-21 12:22:34 -0700712 /// to the given priv_domain.
713 macro_rules! check_key_perm {
714 // "use" is a keyword and cannot be used as an identifier, but we must keep
715 // the permission string intact. So we map the identifier name on use_ while using
716 // the permission string "use". In all other cases we can simply use the stringified
717 // identifier as permission string.
718 (use, $privileged:expr) => {
719 check_key_perm!(use_, $privileged, "use");
720 };
721 ($perm:ident, $privileged:expr) => {
722 check_key_perm!($perm, $privileged, stringify!($perm));
723 };
724 ($perm:ident, $privileged:expr, $p_str:expr) => {
725 #[test]
726 fn $perm() -> Result<()> {
727 android_logger::init_once(
728 android_logger::Config::default()
729 .with_tag("keystore_selinux_tests")
730 .with_min_level(log::Level::Debug),
731 );
732 let scontext = Context::new("u:r:shell:s0")?;
733 let backend = KeystoreKeyBackend::new()?;
734 let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
735
736 if $privileged {
737 assert_eq!(
738 Some(&Error::perm()),
739 check_access(
740 &scontext,
741 &tcontext,
742 "keystore2_key",
743 $p_str
744 )
745 .err()
746 .unwrap()
747 .root_cause()
748 .downcast_ref::<Error>()
749 );
750 } else {
751 assert!(check_access(
752 &scontext,
753 &tcontext,
754 "keystore2_key",
755 $p_str
756 )
757 .is_ok());
758 }
759 Ok(())
760 }
761 };
762 }
763
764 check_key_perm!(manage_blob, true);
765 check_key_perm!(delete, false);
766 check_key_perm!(use_dev_id, true);
767 check_key_perm!(req_forced_op, true);
768 check_key_perm!(gen_unique_id, true);
769 check_key_perm!(grant, true);
770 check_key_perm!(get_info, false);
Janis Danisevskisce995432020-07-21 12:22:34 -0700771 check_key_perm!(rebind, false);
772 check_key_perm!(update, false);
773 check_key_perm!(use, false);
774
775 macro_rules! check_keystore_perm {
776 ($perm:ident) => {
777 #[test]
778 fn $perm() -> Result<()> {
779 let ks_context = Context::new("u:object_r:keystore:s0")?;
780 let priv_context = Context::new("u:r:system_server:s0")?;
781 let unpriv_context = Context::new("u:r:shell:s0")?;
782 assert!(check_access(
783 &priv_context,
784 &ks_context,
785 "keystore2",
786 stringify!($perm)
787 )
788 .is_ok());
789 assert_eq!(
790 Some(&Error::perm()),
791 check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
792 .err()
793 .unwrap()
794 .root_cause()
795 .downcast_ref::<Error>()
796 );
797 Ok(())
798 }
799 };
800 }
801
802 check_keystore_perm!(add_auth);
803 check_keystore_perm!(clear_ns);
Janis Danisevskisce995432020-07-21 12:22:34 -0700804 check_keystore_perm!(lock);
805 check_keystore_perm!(reset);
806 check_keystore_perm!(unlock);
807 }
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700808
809 #[test]
810 fn test_getpidcon() {
811 // Check that `getpidcon` of our pid is equal to what `getcon` returns.
812 // And by using `unwrap` we make sure that both also have to return successfully
813 // fully to pass the test.
814 assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
815 }
Janis Danisevskisce995432020-07-21 12:22:34 -0700816}