blob: d7596a0de6d912979cca15a5c29ff28e56bc0c1b [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.
Chris Wailes1806f972024-08-19 16:37:40 -070021//!
Janis Danisevskisce995432020-07-21 12:22:34 -070022//! And it provides an owning wrapper around context strings `Context`.
23
Andrew Walbrana47698a2023-07-21 17:23:56 +010024// TODO(b/290018030): Remove this and add proper safety comments.
25#![allow(clippy::undocumented_unsafe_blocks)]
26
Janis Danisevskisff188d32021-05-13 13:27:13 -070027use anyhow::Context as AnyhowContext;
28use anyhow::{anyhow, Result};
29use lazy_static::lazy_static;
30pub use selinux::pid_t;
31use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
32use selinux::SELINUX_CB_LOG;
33use selinux_bindgen as selinux;
Janis Danisevskisce995432020-07-21 12:22:34 -070034use std::ffi::{CStr, CString};
35use std::fmt;
36use std::io;
Janis Danisevskis4ad056f2020-08-05 19:46:46 +000037use std::marker::{Send, Sync};
Janis Danisevskisce995432020-07-21 12:22:34 -070038pub use std::ops::Deref;
39use std::os::raw::c_char;
40use std::ptr;
41use std::sync;
42
Janis Danisevskisce995432020-07-21 12:22:34 -070043static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
44
Janis Danisevskisff188d32021-05-13 13:27:13 -070045lazy_static! {
46 /// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
47 /// However, avc_init is deprecated and not exported by androids version of libselinux.
48 /// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
49 /// that remains right now is to put a big lock around calls into libselinux.
50 /// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
51 /// certain of that, we leave the extra locks in place
52 static ref LIB_SELINUX_LOCK: sync::Mutex<()> = Default::default();
53}
54
Janis Danisevskisce995432020-07-21 12:22:34 -070055fn redirect_selinux_logs_to_logcat() {
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070056 // `selinux_set_callback` assigns the static lifetime function pointer
Janis Danisevskisce995432020-07-21 12:22:34 -070057 // `selinux_log_callback` to a static lifetime variable.
58 let cb = selinux::selinux_callback { func_log: Some(selinux::selinux_log_callback) };
59 unsafe {
60 selinux::selinux_set_callback(SELINUX_CB_LOG as i32, cb);
61 }
62}
63
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070064// This function must be called before any entry point into lib selinux.
Janis Danisevskisce995432020-07-21 12:22:34 -070065// Or leave a comment reasoning why calling this macro is not necessary
66// for a given entry point.
67fn init_logger_once() {
68 SELINUX_LOG_INIT.call_once(redirect_selinux_logs_to_logcat)
69}
70
71/// Selinux Error code.
Chris Wailes263de9f2022-08-11 15:00:51 -070072#[derive(thiserror::Error, Debug, PartialEq, Eq)]
Janis Danisevskisce995432020-07-21 12:22:34 -070073pub enum Error {
74 /// Indicates that an access check yielded no access.
75 #[error("Permission Denied")]
76 PermissionDenied,
77 /// Indicates an unexpected system error. Nested string provides some details.
78 #[error("Selinux SystemError: {0}")]
79 SystemError(String),
80}
81
82impl Error {
83 /// Constructs a `PermissionDenied` error.
84 pub fn perm() -> Self {
85 Error::PermissionDenied
86 }
87 fn sys<T: Into<String>>(s: T) -> Self {
88 Error::SystemError(s.into())
89 }
90}
91
92/// Context represents an SELinux context string. It can take ownership of a raw
93/// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
94/// `freecon` to free the resources when dropped. In its second variant it stores
95/// an `std::ffi::CString` that can be initialized from a Rust string slice.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070096#[derive(Debug)]
Janis Danisevskisce995432020-07-21 12:22:34 -070097pub enum Context {
98 /// Wraps a raw context c-string as returned by libselinux.
99 Raw(*mut ::std::os::raw::c_char),
100 /// Stores a context string as `std::ffi::CString`.
101 CString(CString),
102}
103
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700104impl PartialEq for Context {
105 fn eq(&self, other: &Self) -> bool {
106 // We dereference both and thereby delegate the comparison
107 // to `CStr`'s implementation of `PartialEq`.
108 **self == **other
109 }
110}
111
112impl Eq for Context {}
113
Janis Danisevskisce995432020-07-21 12:22:34 -0700114impl fmt::Display for Context {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
117 }
118}
119
120impl Drop for Context {
121 fn drop(&mut self) {
122 if let Self::Raw(p) = self {
123 // No need to initialize the logger here, because
124 // `freecon` cannot run unless `Backend::lookup` or `getcon`
125 // has run.
126 unsafe { selinux::freecon(*p) };
127 }
128 }
129}
130
131impl Deref for Context {
132 type Target = CStr;
133
134 fn deref(&self) -> &Self::Target {
135 match self {
136 Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
Chris Wailesd5aaaef2021-07-27 16:04:33 -0700137 Self::CString(cstr) => cstr,
Janis Danisevskisce995432020-07-21 12:22:34 -0700138 }
139 }
140}
141
142impl Context {
143 /// Initializes the `Context::CString` variant from a Rust string slice.
144 pub fn new(con: &str) -> Result<Self> {
145 Ok(Self::CString(
146 CString::new(con)
147 .with_context(|| format!("Failed to create Context with \"{}\"", con))?,
148 ))
149 }
150}
151
152/// The backend trait provides a uniform interface to all libselinux context backends.
153/// Currently, we only implement the KeystoreKeyBackend though.
154pub trait Backend {
155 /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
156 fn lookup(&self, key: &str) -> Result<Context>;
157}
158
159/// Keystore key backend takes onwnership of the SELinux context handle returned by
160/// `selinux_android_keystore2_key_context_handle` and uses `selabel_close` to free
161/// the handle when dropped.
162/// It implements `Backend` to provide keystore_key label lookup functionality.
163pub struct KeystoreKeyBackend {
164 handle: *mut selinux::selabel_handle,
165}
166
Andrew Walbrana47698a2023-07-21 17:23:56 +0100167// SAFETY: KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000168unsafe impl Sync for KeystoreKeyBackend {}
Andrew Walbrana47698a2023-07-21 17:23:56 +0100169// SAFETY: KeystoreKeyBackend is Send because selabel_lookup is thread safe.
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000170unsafe impl Send for KeystoreKeyBackend {}
171
Janis Danisevskisce995432020-07-21 12:22:34 -0700172impl KeystoreKeyBackend {
173 const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32;
174
175 /// Creates a new instance representing an SELinux context handle as returned by
176 /// `selinux_android_keystore2_key_context_handle`.
177 pub fn new() -> Result<Self> {
178 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700179 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
180
Janis Danisevskisce995432020-07-21 12:22:34 -0700181 let handle = unsafe { selinux::selinux_android_keystore2_key_context_handle() };
182 if handle.is_null() {
183 return Err(anyhow!(Error::sys("Failed to open KeystoreKeyBackend")));
184 }
185 Ok(KeystoreKeyBackend { handle })
186 }
187}
188
189impl Drop for KeystoreKeyBackend {
190 fn drop(&mut self) {
191 // No need to initialize the logger here because it cannot be called unless
192 // KeystoreKeyBackend::new has run.
193 unsafe { selinux::selabel_close(self.handle) };
194 }
195}
196
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000197// Because KeystoreKeyBackend is Sync and Send, member function must never call
198// non thread safe libselinux functions. As of this writing no non thread safe
199// functions exist that could be called on a label backend handle.
Janis Danisevskisce995432020-07-21 12:22:34 -0700200impl Backend for KeystoreKeyBackend {
201 fn lookup(&self, key: &str) -> Result<Context> {
202 let mut con: *mut c_char = ptr::null_mut();
203 let c_key = CString::new(key).with_context(|| {
204 format!("selabel_lookup: Failed to convert key \"{}\" to CString.", key)
205 })?;
206 match unsafe {
207 // No need to initialize the logger here because it cannot run unless
208 // KeystoreKeyBackend::new has run.
Janis Danisevskisff188d32021-05-13 13:27:13 -0700209 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
210
Janis Danisevskisce995432020-07-21 12:22:34 -0700211 selinux::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_TYPE)
212 } {
213 0 => {
214 if !con.is_null() {
215 Ok(Context::Raw(con))
216 } else {
217 Err(anyhow!(Error::sys(format!(
218 "selabel_lookup returned a NULL context for key \"{}\"",
219 key
220 ))))
221 }
222 }
223 _ => Err(anyhow!(io::Error::last_os_error()))
224 .with_context(|| format!("selabel_lookup failed for key \"{}\"", key)),
225 }
226 }
227}
228
229/// Safe wrapper around libselinux `getcon`. It initializes the `Context::Raw` variant of the
230/// returned `Context`.
231///
232/// ## Return
233/// * Ok(Context::Raw()) if successful.
234/// * Err(Error::sys()) if getcon succeeded but returned a NULL pointer.
235/// * Err(io::Error::last_os_error()) if getcon failed.
236pub fn getcon() -> Result<Context> {
237 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700238 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
239
Janis Danisevskisce995432020-07-21 12:22:34 -0700240 let mut con: *mut c_char = ptr::null_mut();
241 match unsafe { selinux::getcon(&mut con) } {
242 0 => {
243 if !con.is_null() {
244 Ok(Context::Raw(con))
245 } else {
246 Err(anyhow!(Error::sys("getcon returned a NULL context")))
247 }
248 }
249 _ => Err(anyhow!(io::Error::last_os_error())).context("getcon failed"),
250 }
251}
252
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700253/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
254/// returned `Context`.
255///
256/// ## Return
257/// * Ok(Context::Raw()) if successful.
258/// * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
259/// * Err(io::Error::last_os_error()) if getpidcon failed.
260pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
261 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700262 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
263
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700264 let mut con: *mut c_char = ptr::null_mut();
265 match unsafe { selinux::getpidcon(pid, &mut con) } {
266 0 => {
267 if !con.is_null() {
268 Ok(Context::Raw(con))
269 } else {
270 Err(anyhow!(Error::sys(format!(
271 "getpidcon returned a NULL context for pid {}",
272 pid
273 ))))
274 }
275 }
276 _ => Err(anyhow!(io::Error::last_os_error()))
277 .context(format!("getpidcon failed for pid {}", pid)),
278 }
279}
280
Janis Danisevskisce995432020-07-21 12:22:34 -0700281/// Safe wrapper around selinux_check_access.
282///
283/// ## Return
284/// * Ok(()) iff the requested access was granted.
285/// * Err(anyhow!(Error::perm()))) if the permission was denied.
286/// * Err(anyhow!(ioError::last_os_error())) if any other error occurred while performing
287/// the access check.
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700288pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
Janis Danisevskisce995432020-07-21 12:22:34 -0700289 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700290
Janis Danisevskisce995432020-07-21 12:22:34 -0700291 let c_tclass = CString::new(tclass).with_context(|| {
292 format!("check_access: Failed to convert tclass \"{}\" to CString.", tclass)
293 })?;
294 let c_perm = CString::new(perm).with_context(|| {
295 format!("check_access: Failed to convert perm \"{}\" to CString.", perm)
296 })?;
297
298 match unsafe {
Janis Danisevskisff188d32021-05-13 13:27:13 -0700299 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
300
Janis Danisevskisce995432020-07-21 12:22:34 -0700301 selinux::selinux_check_access(
302 source.as_ptr(),
303 target.as_ptr(),
304 c_tclass.as_ptr(),
305 c_perm.as_ptr(),
306 ptr::null_mut(),
307 )
308 } {
309 0 => Ok(()),
310 _ => {
311 let e = io::Error::last_os_error();
312 match e.kind() {
313 io::ErrorKind::PermissionDenied => Err(anyhow!(Error::perm())),
314 _ => Err(anyhow!(e)),
315 }
316 .with_context(|| {
317 format!(
318 concat!(
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700319 "check_access: Failed with sctx: {:?} tctx: {:?}",
Janis Danisevskisce995432020-07-21 12:22:34 -0700320 " with target class: \"{}\" perm: \"{}\""
321 ),
322 source, target, tclass, perm
323 )
324 })
325 }
326 }
327}
328
Janis Danisevskisa578d392021-09-20 15:44:06 -0700329/// Safe wrapper around setcon.
330pub fn setcon(target: &CStr) -> std::io::Result<()> {
331 // SAFETY: `setcon` takes a const char* and only performs read accesses on it
332 // using strdup and strcmp. `setcon` does not retain a pointer to `target`
333 // and `target` outlives the call to `setcon`.
334 if unsafe { selinux::setcon(target.as_ptr()) } != 0 {
335 Err(std::io::Error::last_os_error())
336 } else {
337 Ok(())
338 }
339}
340
Janis Danisevskis56af0312021-10-18 16:11:41 -0700341/// Represents an SEPolicy permission belonging to a specific class.
342pub trait ClassPermission {
343 /// The permission string of the given instance as specified in the class vector.
344 fn name(&self) -> &'static str;
345 /// The class of the permission.
346 fn class_name(&self) -> &'static str;
347}
348
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700349/// This macro implements an enum with values mapped to SELinux permission names.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700350/// The example below implements `enum MyPermission with public visibility:
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700351/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
Janis Danisevskis56af0312021-10-18 16:11:41 -0700352/// any variant not specified to the default `None` with value `0`.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700353/// * `MyPermission` implements ClassPermission.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700354/// * An implicit default values `MyPermission::None` is created with a numeric representation
355/// of `0` and a string representation of `"none"`.
356/// * Specifying a value is optional. If the value is omitted it is set to the value of the
357/// previous variant left shifted by 1.
358///
359/// ## Example
360/// ```
Janis Danisevskis56af0312021-10-18 16:11:41 -0700361/// implement_class!(
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700362/// /// MyPermission documentation.
363/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700364/// #[selinux(class_name = my_class)]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700365/// pub enum MyPermission {
366/// #[selinux(name = foo)]
367/// Foo = 1,
368/// #[selinux(name = bar)]
369/// Bar = 2,
370/// #[selinux(name = snafu)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700371/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700372/// }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700373/// assert_eq!(MyPermission::Foo.name(), &"foo");
374/// assert_eq!(MyPermission::Foo.class_name(), &"my_class");
375/// assert_eq!(MyPermission::Snafu as i32, 4);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700376/// );
377/// ```
378#[macro_export]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700379macro_rules! implement_class {
380 // First rule: Public interface.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700381 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700382 $(#[$($enum_meta:tt)+])*
383 $enum_vis:vis enum $enum_name:ident $body:tt
384 ) => {
385 implement_class! {
386 @extract_class
387 []
388 [$(#[$($enum_meta)+])*]
389 $enum_vis enum $enum_name $body
390 }
391 };
392
393 // The next two rules extract the #[selinux(class_name = <name>)] meta field from
394 // the types meta list.
395 // This first rule finds the field and terminates the recursion through the meta fields.
396 (
397 @extract_class
398 [$(#[$mout:meta])*]
399 [
400 #[selinux(class_name = $class_name:ident)]
401 $(#[$($mtail:tt)+])*
402 ]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700403 $enum_vis:vis enum $enum_name:ident {
404 $(
405 $(#[$($emeta:tt)+])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700406 $vname:ident$( = $vval:expr)?
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700407 ),* $(,)?
408 }
409 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700410 implement_class!{
411 @extract_perm_name
412 $class_name
413 $(#[$mout])*
414 $(#[$($mtail)+])*
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700415 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700416 1;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700417 []
418 [$(
419 [] [$(#[$($emeta)+])*]
420 $vname$( = $vval)?,
421 )*]
422 }
423 }
424 };
425
Janis Danisevskis56af0312021-10-18 16:11:41 -0700426 // The second rule iterates through the type global meta fields.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700427 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700428 @extract_class
429 [$(#[$mout:meta])*]
430 [
431 #[$front:meta]
432 $(#[$($mtail:tt)+])*
433 ]
434 $enum_vis:vis enum $enum_name:ident $body:tt
435 ) => {
436 implement_class!{
437 @extract_class
438 [
439 $(#[$mout])*
440 #[$front]
441 ]
442 [$(#[$($mtail)+])*]
443 $enum_vis enum $enum_name $body
444 }
445 };
446
447 // The next four rules implement two nested recursions. The outer iterates through
448 // the enum variants and the inner iterates through the meta fields of each variant.
449 // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner
450 // recursion and descend a level in the outer recursion.
451 // The first rule matches variants with explicit initializer $vval. And updates the next
452 // value to ($vval << 1).
453 (
454 @extract_perm_name
455 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700456 $(#[$enum_meta:meta])*
457 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700458 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700459 [$($out:tt)*]
460 [
461 [$(#[$mout:meta])*]
462 [
463 #[selinux(name = $selinux_name:ident)]
464 $(#[$($mtail:tt)+])*
465 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700466 $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700467 $($tail:tt)*
468 ]
469 }
470 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700471 implement_class!{
472 @extract_perm_name
473 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700474 $(#[$enum_meta])*
475 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700476 ($vval << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700477 [
478 $($out)*
479 $(#[$mout])*
480 $(#[$($mtail)+])*
481 $selinux_name $vname = $vval,
482 ]
483 [$($tail)*]
484 }
485 }
486 };
487
Janis Danisevskis56af0312021-10-18 16:11:41 -0700488 // The second rule differs form the previous in that there is no explicit initializer.
489 // Instead $next_val is used as initializer and the next value is set to (&next_val << 1).
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700490 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700491 @extract_perm_name
492 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700493 $(#[$enum_meta:meta])*
494 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700495 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700496 [$($out:tt)*]
497 [
498 [$(#[$mout:meta])*]
499 [
500 #[selinux(name = $selinux_name:ident)]
501 $(#[$($mtail:tt)+])*
502 ]
503 $vname:ident,
504 $($tail:tt)*
505 ]
506 }
507 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700508 implement_class!{
509 @extract_perm_name
510 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700511 $(#[$enum_meta])*
512 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700513 ($next_val << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700514 [
515 $($out)*
516 $(#[$mout])*
517 $(#[$($mtail)+])*
518 $selinux_name $vname = $next_val,
519 ]
520 [$($tail)*]
521 }
522 }
523 };
524
Janis Danisevskis56af0312021-10-18 16:11:41 -0700525 // The third rule descends a step in the inner recursion.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700526 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700527 @extract_perm_name
528 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700529 $(#[$enum_meta:meta])*
530 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700531 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700532 [$($out:tt)*]
533 [
534 [$(#[$mout:meta])*]
535 [
536 #[$front:meta]
537 $(#[$($mtail:tt)+])*
538 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700539 $vname:ident$( = $vval:expr)?,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700540 $($tail:tt)*
541 ]
542 }
543 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700544 implement_class!{
545 @extract_perm_name
546 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700547 $(#[$enum_meta])*
548 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700549 $next_val;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700550 [$($out)*]
551 [
552 [
553 $(#[$mout])*
554 #[$front]
555 ]
556 [$(#[$($mtail)+])*]
557 $vname$( = $vval)?,
558 $($tail)*
559 ]
560 }
561 }
562 };
563
Janis Danisevskis56af0312021-10-18 16:11:41 -0700564 // The fourth rule terminates the outer recursion and transitions to the
565 // implementation phase @spill.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700566 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700567 @extract_perm_name
568 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700569 $(#[$enum_meta:meta])*
570 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700571 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700572 [$($out:tt)*]
573 []
574 }
575 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700576 implement_class!{
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700577 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700578 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700579 $(#[$enum_meta])*
580 $enum_vis enum $enum_name {
581 $($out)*
582 }
583 }
584 };
585
586 (
587 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700588 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700589 $(#[$enum_meta:meta])*
590 $enum_vis:vis enum $enum_name:ident {
591 $(
592 $(#[$emeta:meta])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700593 $selinux_name:ident $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700594 )*
595 }
596 ) => {
597 $(#[$enum_meta])*
598 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700599 /// The default variant of the enum.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700600 None = 0,
601 $(
602 $(#[$emeta])*
603 $vname = $vval,
604 )*
605 }
606
607 impl From<i32> for $enum_name {
608 #[allow(non_upper_case_globals)]
609 fn from (p: i32) -> Self {
610 // Creating constants forces the compiler to evaluate the value expressions
611 // so that they can be used in the match statement below.
612 $(const $vname: i32 = $vval;)*
613 match p {
614 0 => Self::None,
615 $($vname => Self::$vname,)*
616 _ => Self::None,
617 }
618 }
619 }
620
621 impl From<$enum_name> for i32 {
622 fn from(p: $enum_name) -> i32 {
623 p as i32
624 }
625 }
626
Janis Danisevskis56af0312021-10-18 16:11:41 -0700627 impl ClassPermission for $enum_name {
628 fn name(&self) -> &'static str {
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700629 match self {
630 Self::None => &"none",
631 $(Self::$vname => stringify!($selinux_name),)*
632 }
633 }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700634 fn class_name(&self) -> &'static str {
635 stringify!($class_name)
636 }
637 }
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700638 };
639}
640
Janis Danisevskis56af0312021-10-18 16:11:41 -0700641/// Calls `check_access` on the given class permission.
642pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> {
643 check_access(source, target, perm.class_name(), perm.name())
644}
645
Janis Danisevskisce995432020-07-21 12:22:34 -0700646#[cfg(test)]
647mod tests {
648 use super::*;
649 use anyhow::Result;
650
651 /// The su_key namespace as defined in su.te and keystore_key_contexts of the
652 /// SePolicy (system/sepolicy).
653 static SU_KEY_NAMESPACE: &str = "0";
654 /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
655 /// SePolicy (system/sepolicy).
656 static SHELL_KEY_NAMESPACE: &str = "1";
657
658 fn check_context() -> Result<(Context, &'static str, bool)> {
659 let context = getcon()?;
660 match context.to_str().unwrap() {
661 "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
662 "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
663 c => Err(anyhow!(format!(
664 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
665 c
666 ))),
667 }
668 }
669
670 #[test]
671 fn test_getcon() -> Result<()> {
672 check_context()?;
673 Ok(())
674 }
675
676 #[test]
677 fn test_label_lookup() -> Result<()> {
678 let (_context, namespace, is_su) = check_context()?;
679 let backend = crate::KeystoreKeyBackend::new()?;
680 let context = backend.lookup(namespace)?;
681 if is_su {
682 assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
683 } else {
684 assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
685 }
686 Ok(())
687 }
688
689 #[test]
690 fn context_from_string() -> Result<()> {
691 let tctx = Context::new("u:object_r:keystore:s0").unwrap();
692 let sctx = Context::new("u:r:system_server:s0").unwrap();
693 check_access(&sctx, &tctx, "keystore2_key", "use")?;
694 Ok(())
695 }
696
697 mod perm {
698 use super::super::*;
699 use super::*;
700 use anyhow::Result;
701
702 /// check_key_perm(perm, privileged, priv_domain)
703 /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
704 /// indicating whether the permission is considered privileged.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700705 /// Privileged permissions are expected to be denied to `shell` users but granted
Janis Danisevskisce995432020-07-21 12:22:34 -0700706 /// to the given priv_domain.
707 macro_rules! check_key_perm {
708 // "use" is a keyword and cannot be used as an identifier, but we must keep
709 // the permission string intact. So we map the identifier name on use_ while using
710 // the permission string "use". In all other cases we can simply use the stringified
711 // identifier as permission string.
712 (use, $privileged:expr) => {
713 check_key_perm!(use_, $privileged, "use");
714 };
715 ($perm:ident, $privileged:expr) => {
716 check_key_perm!($perm, $privileged, stringify!($perm));
717 };
718 ($perm:ident, $privileged:expr, $p_str:expr) => {
719 #[test]
720 fn $perm() -> Result<()> {
721 android_logger::init_once(
722 android_logger::Config::default()
723 .with_tag("keystore_selinux_tests")
Jeff Vander Stoep153d1aa2024-02-07 14:33:36 +0100724 .with_max_level(log::LevelFilter::Debug),
Janis Danisevskisce995432020-07-21 12:22:34 -0700725 );
726 let scontext = Context::new("u:r:shell:s0")?;
727 let backend = KeystoreKeyBackend::new()?;
728 let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
729
730 if $privileged {
731 assert_eq!(
732 Some(&Error::perm()),
733 check_access(
734 &scontext,
735 &tcontext,
736 "keystore2_key",
737 $p_str
738 )
739 .err()
740 .unwrap()
741 .root_cause()
742 .downcast_ref::<Error>()
743 );
744 } else {
745 assert!(check_access(
746 &scontext,
747 &tcontext,
748 "keystore2_key",
749 $p_str
750 )
751 .is_ok());
752 }
753 Ok(())
754 }
755 };
756 }
757
758 check_key_perm!(manage_blob, true);
759 check_key_perm!(delete, false);
760 check_key_perm!(use_dev_id, true);
761 check_key_perm!(req_forced_op, true);
762 check_key_perm!(gen_unique_id, true);
763 check_key_perm!(grant, true);
764 check_key_perm!(get_info, false);
Janis Danisevskisce995432020-07-21 12:22:34 -0700765 check_key_perm!(rebind, false);
766 check_key_perm!(update, false);
767 check_key_perm!(use, false);
768
769 macro_rules! check_keystore_perm {
770 ($perm:ident) => {
771 #[test]
772 fn $perm() -> Result<()> {
773 let ks_context = Context::new("u:object_r:keystore:s0")?;
774 let priv_context = Context::new("u:r:system_server:s0")?;
775 let unpriv_context = Context::new("u:r:shell:s0")?;
776 assert!(check_access(
777 &priv_context,
778 &ks_context,
779 "keystore2",
780 stringify!($perm)
781 )
782 .is_ok());
783 assert_eq!(
784 Some(&Error::perm()),
785 check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
786 .err()
787 .unwrap()
788 .root_cause()
789 .downcast_ref::<Error>()
790 );
791 Ok(())
792 }
793 };
794 }
795
796 check_keystore_perm!(add_auth);
797 check_keystore_perm!(clear_ns);
Janis Danisevskisce995432020-07-21 12:22:34 -0700798 check_keystore_perm!(lock);
799 check_keystore_perm!(reset);
800 check_keystore_perm!(unlock);
801 }
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700802
803 #[test]
804 fn test_getpidcon() {
805 // Check that `getpidcon` of our pid is equal to what `getcon` returns.
806 // And by using `unwrap` we make sure that both also have to return successfully
807 // fully to pass the test.
808 assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
809 }
Janis Danisevskisce995432020-07-21 12:22:34 -0700810}