blob: 32fdb594881955546a281a18da167ff07eaf3f66 [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
Andrew Walbrana47698a2023-07-21 17:23:56 +010023// TODO(b/290018030): Remove this and add proper safety comments.
24#![allow(clippy::undocumented_unsafe_blocks)]
25
Janis Danisevskisff188d32021-05-13 13:27:13 -070026use anyhow::Context as AnyhowContext;
27use anyhow::{anyhow, Result};
28use lazy_static::lazy_static;
29pub use selinux::pid_t;
30use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
31use selinux::SELINUX_CB_LOG;
32use selinux_bindgen as selinux;
Janis Danisevskisce995432020-07-21 12:22:34 -070033use std::ffi::{CStr, CString};
34use std::fmt;
35use std::io;
Janis Danisevskis4ad056f2020-08-05 19:46:46 +000036use std::marker::{Send, Sync};
Janis Danisevskisce995432020-07-21 12:22:34 -070037pub use std::ops::Deref;
38use std::os::raw::c_char;
39use std::ptr;
40use std::sync;
41
Janis Danisevskisce995432020-07-21 12:22:34 -070042static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
43
Janis Danisevskisff188d32021-05-13 13:27:13 -070044lazy_static! {
45 /// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
46 /// However, avc_init is deprecated and not exported by androids version of libselinux.
47 /// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
48 /// that remains right now is to put a big lock around calls into libselinux.
49 /// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
50 /// certain of that, we leave the extra locks in place
51 static ref LIB_SELINUX_LOCK: sync::Mutex<()> = Default::default();
52}
53
Janis Danisevskisce995432020-07-21 12:22:34 -070054fn redirect_selinux_logs_to_logcat() {
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070055 // `selinux_set_callback` assigns the static lifetime function pointer
Janis Danisevskisce995432020-07-21 12:22:34 -070056 // `selinux_log_callback` to a static lifetime variable.
57 let cb = selinux::selinux_callback { func_log: Some(selinux::selinux_log_callback) };
58 unsafe {
59 selinux::selinux_set_callback(SELINUX_CB_LOG as i32, cb);
60 }
61}
62
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070063// This function must be called before any entry point into lib selinux.
Janis Danisevskisce995432020-07-21 12:22:34 -070064// Or leave a comment reasoning why calling this macro is not necessary
65// for a given entry point.
66fn init_logger_once() {
67 SELINUX_LOG_INIT.call_once(redirect_selinux_logs_to_logcat)
68}
69
70/// Selinux Error code.
Chris Wailes263de9f2022-08-11 15:00:51 -070071#[derive(thiserror::Error, Debug, PartialEq, Eq)]
Janis Danisevskisce995432020-07-21 12:22:34 -070072pub enum Error {
73 /// Indicates that an access check yielded no access.
74 #[error("Permission Denied")]
75 PermissionDenied,
76 /// Indicates an unexpected system error. Nested string provides some details.
77 #[error("Selinux SystemError: {0}")]
78 SystemError(String),
79}
80
81impl Error {
82 /// Constructs a `PermissionDenied` error.
83 pub fn perm() -> Self {
84 Error::PermissionDenied
85 }
86 fn sys<T: Into<String>>(s: T) -> Self {
87 Error::SystemError(s.into())
88 }
89}
90
91/// Context represents an SELinux context string. It can take ownership of a raw
92/// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
93/// `freecon` to free the resources when dropped. In its second variant it stores
94/// an `std::ffi::CString` that can be initialized from a Rust string slice.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070095#[derive(Debug)]
Janis Danisevskisce995432020-07-21 12:22:34 -070096pub enum Context {
97 /// Wraps a raw context c-string as returned by libselinux.
98 Raw(*mut ::std::os::raw::c_char),
99 /// Stores a context string as `std::ffi::CString`.
100 CString(CString),
101}
102
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700103impl PartialEq for Context {
104 fn eq(&self, other: &Self) -> bool {
105 // We dereference both and thereby delegate the comparison
106 // to `CStr`'s implementation of `PartialEq`.
107 **self == **other
108 }
109}
110
111impl Eq for Context {}
112
Janis Danisevskisce995432020-07-21 12:22:34 -0700113impl fmt::Display for Context {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
116 }
117}
118
119impl Drop for Context {
120 fn drop(&mut self) {
121 if let Self::Raw(p) = self {
122 // No need to initialize the logger here, because
123 // `freecon` cannot run unless `Backend::lookup` or `getcon`
124 // has run.
125 unsafe { selinux::freecon(*p) };
126 }
127 }
128}
129
130impl Deref for Context {
131 type Target = CStr;
132
133 fn deref(&self) -> &Self::Target {
134 match self {
135 Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
Chris Wailesd5aaaef2021-07-27 16:04:33 -0700136 Self::CString(cstr) => cstr,
Janis Danisevskisce995432020-07-21 12:22:34 -0700137 }
138 }
139}
140
141impl Context {
142 /// Initializes the `Context::CString` variant from a Rust string slice.
143 pub fn new(con: &str) -> Result<Self> {
144 Ok(Self::CString(
145 CString::new(con)
146 .with_context(|| format!("Failed to create Context with \"{}\"", con))?,
147 ))
148 }
149}
150
151/// The backend trait provides a uniform interface to all libselinux context backends.
152/// Currently, we only implement the KeystoreKeyBackend though.
153pub trait Backend {
154 /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
155 fn lookup(&self, key: &str) -> Result<Context>;
156}
157
158/// Keystore key backend takes onwnership of the SELinux context handle returned by
159/// `selinux_android_keystore2_key_context_handle` and uses `selabel_close` to free
160/// the handle when dropped.
161/// It implements `Backend` to provide keystore_key label lookup functionality.
162pub struct KeystoreKeyBackend {
163 handle: *mut selinux::selabel_handle,
164}
165
Andrew Walbrana47698a2023-07-21 17:23:56 +0100166// SAFETY: KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000167unsafe impl Sync for KeystoreKeyBackend {}
Andrew Walbrana47698a2023-07-21 17:23:56 +0100168// SAFETY: KeystoreKeyBackend is Send because selabel_lookup is thread safe.
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000169unsafe impl Send for KeystoreKeyBackend {}
170
Janis Danisevskisce995432020-07-21 12:22:34 -0700171impl KeystoreKeyBackend {
172 const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32;
173
174 /// Creates a new instance representing an SELinux context handle as returned by
175 /// `selinux_android_keystore2_key_context_handle`.
176 pub fn new() -> Result<Self> {
177 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700178 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
179
Janis Danisevskisce995432020-07-21 12:22:34 -0700180 let handle = unsafe { selinux::selinux_android_keystore2_key_context_handle() };
181 if handle.is_null() {
182 return Err(anyhow!(Error::sys("Failed to open KeystoreKeyBackend")));
183 }
184 Ok(KeystoreKeyBackend { handle })
185 }
186}
187
188impl Drop for KeystoreKeyBackend {
189 fn drop(&mut self) {
190 // No need to initialize the logger here because it cannot be called unless
191 // KeystoreKeyBackend::new has run.
192 unsafe { selinux::selabel_close(self.handle) };
193 }
194}
195
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000196// Because KeystoreKeyBackend is Sync and Send, member function must never call
197// non thread safe libselinux functions. As of this writing no non thread safe
198// functions exist that could be called on a label backend handle.
Janis Danisevskisce995432020-07-21 12:22:34 -0700199impl Backend for KeystoreKeyBackend {
200 fn lookup(&self, key: &str) -> Result<Context> {
201 let mut con: *mut c_char = ptr::null_mut();
202 let c_key = CString::new(key).with_context(|| {
203 format!("selabel_lookup: Failed to convert key \"{}\" to CString.", key)
204 })?;
205 match unsafe {
206 // No need to initialize the logger here because it cannot run unless
207 // KeystoreKeyBackend::new has run.
Janis Danisevskisff188d32021-05-13 13:27:13 -0700208 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
209
Janis Danisevskisce995432020-07-21 12:22:34 -0700210 selinux::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_TYPE)
211 } {
212 0 => {
213 if !con.is_null() {
214 Ok(Context::Raw(con))
215 } else {
216 Err(anyhow!(Error::sys(format!(
217 "selabel_lookup returned a NULL context for key \"{}\"",
218 key
219 ))))
220 }
221 }
222 _ => Err(anyhow!(io::Error::last_os_error()))
223 .with_context(|| format!("selabel_lookup failed for key \"{}\"", key)),
224 }
225 }
226}
227
228/// Safe wrapper around libselinux `getcon`. It initializes the `Context::Raw` variant of the
229/// returned `Context`.
230///
231/// ## Return
232/// * Ok(Context::Raw()) if successful.
233/// * Err(Error::sys()) if getcon succeeded but returned a NULL pointer.
234/// * Err(io::Error::last_os_error()) if getcon failed.
235pub fn getcon() -> Result<Context> {
236 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700237 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
238
Janis Danisevskisce995432020-07-21 12:22:34 -0700239 let mut con: *mut c_char = ptr::null_mut();
240 match unsafe { selinux::getcon(&mut con) } {
241 0 => {
242 if !con.is_null() {
243 Ok(Context::Raw(con))
244 } else {
245 Err(anyhow!(Error::sys("getcon returned a NULL context")))
246 }
247 }
248 _ => Err(anyhow!(io::Error::last_os_error())).context("getcon failed"),
249 }
250}
251
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700252/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
253/// returned `Context`.
254///
255/// ## Return
256/// * Ok(Context::Raw()) if successful.
257/// * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
258/// * Err(io::Error::last_os_error()) if getpidcon failed.
259pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
260 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700261 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
262
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700263 let mut con: *mut c_char = ptr::null_mut();
264 match unsafe { selinux::getpidcon(pid, &mut con) } {
265 0 => {
266 if !con.is_null() {
267 Ok(Context::Raw(con))
268 } else {
269 Err(anyhow!(Error::sys(format!(
270 "getpidcon returned a NULL context for pid {}",
271 pid
272 ))))
273 }
274 }
275 _ => Err(anyhow!(io::Error::last_os_error()))
276 .context(format!("getpidcon failed for pid {}", pid)),
277 }
278}
279
Janis Danisevskisce995432020-07-21 12:22:34 -0700280/// Safe wrapper around selinux_check_access.
281///
282/// ## Return
283/// * Ok(()) iff the requested access was granted.
284/// * Err(anyhow!(Error::perm()))) if the permission was denied.
285/// * Err(anyhow!(ioError::last_os_error())) if any other error occurred while performing
286/// the access check.
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700287pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
Janis Danisevskisce995432020-07-21 12:22:34 -0700288 init_logger_once();
Janis Danisevskisff188d32021-05-13 13:27:13 -0700289
Janis Danisevskisce995432020-07-21 12:22:34 -0700290 let c_tclass = CString::new(tclass).with_context(|| {
291 format!("check_access: Failed to convert tclass \"{}\" to CString.", tclass)
292 })?;
293 let c_perm = CString::new(perm).with_context(|| {
294 format!("check_access: Failed to convert perm \"{}\" to CString.", perm)
295 })?;
296
297 match unsafe {
Janis Danisevskisff188d32021-05-13 13:27:13 -0700298 let _lock = LIB_SELINUX_LOCK.lock().unwrap();
299
Janis Danisevskisce995432020-07-21 12:22:34 -0700300 selinux::selinux_check_access(
301 source.as_ptr(),
302 target.as_ptr(),
303 c_tclass.as_ptr(),
304 c_perm.as_ptr(),
305 ptr::null_mut(),
306 )
307 } {
308 0 => Ok(()),
309 _ => {
310 let e = io::Error::last_os_error();
311 match e.kind() {
312 io::ErrorKind::PermissionDenied => Err(anyhow!(Error::perm())),
313 _ => Err(anyhow!(e)),
314 }
315 .with_context(|| {
316 format!(
317 concat!(
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700318 "check_access: Failed with sctx: {:?} tctx: {:?}",
Janis Danisevskisce995432020-07-21 12:22:34 -0700319 " with target class: \"{}\" perm: \"{}\""
320 ),
321 source, target, tclass, perm
322 )
323 })
324 }
325 }
326}
327
Janis Danisevskisa578d392021-09-20 15:44:06 -0700328/// Safe wrapper around setcon.
329pub fn setcon(target: &CStr) -> std::io::Result<()> {
330 // SAFETY: `setcon` takes a const char* and only performs read accesses on it
331 // using strdup and strcmp. `setcon` does not retain a pointer to `target`
332 // and `target` outlives the call to `setcon`.
333 if unsafe { selinux::setcon(target.as_ptr()) } != 0 {
334 Err(std::io::Error::last_os_error())
335 } else {
336 Ok(())
337 }
338}
339
Janis Danisevskis56af0312021-10-18 16:11:41 -0700340/// Represents an SEPolicy permission belonging to a specific class.
341pub trait ClassPermission {
342 /// The permission string of the given instance as specified in the class vector.
343 fn name(&self) -> &'static str;
344 /// The class of the permission.
345 fn class_name(&self) -> &'static str;
346}
347
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700348/// This macro implements an enum with values mapped to SELinux permission names.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700349/// The example below implements `enum MyPermission with public visibility:
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700350/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
Janis Danisevskis56af0312021-10-18 16:11:41 -0700351/// any variant not specified to the default `None` with value `0`.
Janis Danisevskis56af0312021-10-18 16:11:41 -0700352/// * `MyPermission` implements ClassPermission.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700353/// * An implicit default values `MyPermission::None` is created with a numeric representation
354/// of `0` and a string representation of `"none"`.
355/// * Specifying a value is optional. If the value is omitted it is set to the value of the
356/// previous variant left shifted by 1.
357///
358/// ## Example
359/// ```
Janis Danisevskis56af0312021-10-18 16:11:41 -0700360/// implement_class!(
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700361/// /// MyPermission documentation.
362/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700363/// #[selinux(class_name = my_class)]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700364/// pub enum MyPermission {
365/// #[selinux(name = foo)]
366/// Foo = 1,
367/// #[selinux(name = bar)]
368/// Bar = 2,
369/// #[selinux(name = snafu)]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700370/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700371/// }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700372/// assert_eq!(MyPermission::Foo.name(), &"foo");
373/// assert_eq!(MyPermission::Foo.class_name(), &"my_class");
374/// assert_eq!(MyPermission::Snafu as i32, 4);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700375/// );
376/// ```
377#[macro_export]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700378macro_rules! implement_class {
379 // First rule: Public interface.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700380 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700381 $(#[$($enum_meta:tt)+])*
382 $enum_vis:vis enum $enum_name:ident $body:tt
383 ) => {
384 implement_class! {
385 @extract_class
386 []
387 [$(#[$($enum_meta)+])*]
388 $enum_vis enum $enum_name $body
389 }
390 };
391
392 // The next two rules extract the #[selinux(class_name = <name>)] meta field from
393 // the types meta list.
394 // This first rule finds the field and terminates the recursion through the meta fields.
395 (
396 @extract_class
397 [$(#[$mout:meta])*]
398 [
399 #[selinux(class_name = $class_name:ident)]
400 $(#[$($mtail:tt)+])*
401 ]
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700402 $enum_vis:vis enum $enum_name:ident {
403 $(
404 $(#[$($emeta:tt)+])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700405 $vname:ident$( = $vval:expr)?
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700406 ),* $(,)?
407 }
408 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700409 implement_class!{
410 @extract_perm_name
411 $class_name
412 $(#[$mout])*
413 $(#[$($mtail)+])*
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700414 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700415 1;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700416 []
417 [$(
418 [] [$(#[$($emeta)+])*]
419 $vname$( = $vval)?,
420 )*]
421 }
422 }
423 };
424
Janis Danisevskis56af0312021-10-18 16:11:41 -0700425 // The second rule iterates through the type global meta fields.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700426 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700427 @extract_class
428 [$(#[$mout:meta])*]
429 [
430 #[$front:meta]
431 $(#[$($mtail:tt)+])*
432 ]
433 $enum_vis:vis enum $enum_name:ident $body:tt
434 ) => {
435 implement_class!{
436 @extract_class
437 [
438 $(#[$mout])*
439 #[$front]
440 ]
441 [$(#[$($mtail)+])*]
442 $enum_vis enum $enum_name $body
443 }
444 };
445
446 // The next four rules implement two nested recursions. The outer iterates through
447 // the enum variants and the inner iterates through the meta fields of each variant.
448 // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner
449 // recursion and descend a level in the outer recursion.
450 // The first rule matches variants with explicit initializer $vval. And updates the next
451 // value to ($vval << 1).
452 (
453 @extract_perm_name
454 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700455 $(#[$enum_meta:meta])*
456 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700457 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700458 [$($out:tt)*]
459 [
460 [$(#[$mout:meta])*]
461 [
462 #[selinux(name = $selinux_name:ident)]
463 $(#[$($mtail:tt)+])*
464 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700465 $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700466 $($tail:tt)*
467 ]
468 }
469 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700470 implement_class!{
471 @extract_perm_name
472 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700473 $(#[$enum_meta])*
474 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700475 ($vval << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700476 [
477 $($out)*
478 $(#[$mout])*
479 $(#[$($mtail)+])*
480 $selinux_name $vname = $vval,
481 ]
482 [$($tail)*]
483 }
484 }
485 };
486
Janis Danisevskis56af0312021-10-18 16:11:41 -0700487 // The second rule differs form the previous in that there is no explicit initializer.
488 // Instead $next_val is used as initializer and the next value is set to (&next_val << 1).
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700489 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700490 @extract_perm_name
491 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700492 $(#[$enum_meta:meta])*
493 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700494 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700495 [$($out:tt)*]
496 [
497 [$(#[$mout:meta])*]
498 [
499 #[selinux(name = $selinux_name:ident)]
500 $(#[$($mtail:tt)+])*
501 ]
502 $vname:ident,
503 $($tail:tt)*
504 ]
505 }
506 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700507 implement_class!{
508 @extract_perm_name
509 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700510 $(#[$enum_meta])*
511 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700512 ($next_val << 1);
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700513 [
514 $($out)*
515 $(#[$mout])*
516 $(#[$($mtail)+])*
517 $selinux_name $vname = $next_val,
518 ]
519 [$($tail)*]
520 }
521 }
522 };
523
Janis Danisevskis56af0312021-10-18 16:11:41 -0700524 // The third rule descends a step in the inner recursion.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700525 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700526 @extract_perm_name
527 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700528 $(#[$enum_meta:meta])*
529 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700530 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700531 [$($out:tt)*]
532 [
533 [$(#[$mout:meta])*]
534 [
535 #[$front:meta]
536 $(#[$($mtail:tt)+])*
537 ]
Janis Danisevskis56af0312021-10-18 16:11:41 -0700538 $vname:ident$( = $vval:expr)?,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700539 $($tail:tt)*
540 ]
541 }
542 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700543 implement_class!{
544 @extract_perm_name
545 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700546 $(#[$enum_meta])*
547 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700548 $next_val;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700549 [$($out)*]
550 [
551 [
552 $(#[$mout])*
553 #[$front]
554 ]
555 [$(#[$($mtail)+])*]
556 $vname$( = $vval)?,
557 $($tail)*
558 ]
559 }
560 }
561 };
562
Janis Danisevskis56af0312021-10-18 16:11:41 -0700563 // The fourth rule terminates the outer recursion and transitions to the
564 // implementation phase @spill.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700565 (
Janis Danisevskis56af0312021-10-18 16:11:41 -0700566 @extract_perm_name
567 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700568 $(#[$enum_meta:meta])*
569 $enum_vis:vis enum $enum_name:ident {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700570 $next_val:expr;
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700571 [$($out:tt)*]
572 []
573 }
574 ) => {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700575 implement_class!{
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700576 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700577 $class_name
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700578 $(#[$enum_meta])*
579 $enum_vis enum $enum_name {
580 $($out)*
581 }
582 }
583 };
584
585 (
586 @spill
Janis Danisevskis56af0312021-10-18 16:11:41 -0700587 $class_name:ident
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700588 $(#[$enum_meta:meta])*
589 $enum_vis:vis enum $enum_name:ident {
590 $(
591 $(#[$emeta:meta])*
Janis Danisevskis56af0312021-10-18 16:11:41 -0700592 $selinux_name:ident $vname:ident = $vval:expr,
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700593 )*
594 }
595 ) => {
596 $(#[$enum_meta])*
597 $enum_vis enum $enum_name {
Janis Danisevskis56af0312021-10-18 16:11:41 -0700598 /// The default variant of the enum.
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700599 None = 0,
600 $(
601 $(#[$emeta])*
602 $vname = $vval,
603 )*
604 }
605
606 impl From<i32> for $enum_name {
607 #[allow(non_upper_case_globals)]
608 fn from (p: i32) -> Self {
609 // Creating constants forces the compiler to evaluate the value expressions
610 // so that they can be used in the match statement below.
611 $(const $vname: i32 = $vval;)*
612 match p {
613 0 => Self::None,
614 $($vname => Self::$vname,)*
615 _ => Self::None,
616 }
617 }
618 }
619
620 impl From<$enum_name> for i32 {
621 fn from(p: $enum_name) -> i32 {
622 p as i32
623 }
624 }
625
Janis Danisevskis56af0312021-10-18 16:11:41 -0700626 impl ClassPermission for $enum_name {
627 fn name(&self) -> &'static str {
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700628 match self {
629 Self::None => &"none",
630 $(Self::$vname => stringify!($selinux_name),)*
631 }
632 }
Janis Danisevskis56af0312021-10-18 16:11:41 -0700633 fn class_name(&self) -> &'static str {
634 stringify!($class_name)
635 }
636 }
Janis Danisevskisa2f48502021-10-18 16:07:09 -0700637 };
638}
639
Janis Danisevskis56af0312021-10-18 16:11:41 -0700640/// Calls `check_access` on the given class permission.
641pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> {
642 check_access(source, target, perm.class_name(), perm.name())
643}
644
Janis Danisevskisce995432020-07-21 12:22:34 -0700645#[cfg(test)]
646mod tests {
647 use super::*;
648 use anyhow::Result;
649
650 /// The su_key namespace as defined in su.te and keystore_key_contexts of the
651 /// SePolicy (system/sepolicy).
652 static SU_KEY_NAMESPACE: &str = "0";
653 /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
654 /// SePolicy (system/sepolicy).
655 static SHELL_KEY_NAMESPACE: &str = "1";
656
657 fn check_context() -> Result<(Context, &'static str, bool)> {
658 let context = getcon()?;
659 match context.to_str().unwrap() {
660 "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
661 "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
662 c => Err(anyhow!(format!(
663 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
664 c
665 ))),
666 }
667 }
668
669 #[test]
670 fn test_getcon() -> Result<()> {
671 check_context()?;
672 Ok(())
673 }
674
675 #[test]
676 fn test_label_lookup() -> Result<()> {
677 let (_context, namespace, is_su) = check_context()?;
678 let backend = crate::KeystoreKeyBackend::new()?;
679 let context = backend.lookup(namespace)?;
680 if is_su {
681 assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
682 } else {
683 assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
684 }
685 Ok(())
686 }
687
688 #[test]
689 fn context_from_string() -> Result<()> {
690 let tctx = Context::new("u:object_r:keystore:s0").unwrap();
691 let sctx = Context::new("u:r:system_server:s0").unwrap();
692 check_access(&sctx, &tctx, "keystore2_key", "use")?;
693 Ok(())
694 }
695
696 mod perm {
697 use super::super::*;
698 use super::*;
699 use anyhow::Result;
700
701 /// check_key_perm(perm, privileged, priv_domain)
702 /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
703 /// indicating whether the permission is considered privileged.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700704 /// Privileged permissions are expected to be denied to `shell` users but granted
Janis Danisevskisce995432020-07-21 12:22:34 -0700705 /// to the given priv_domain.
706 macro_rules! check_key_perm {
707 // "use" is a keyword and cannot be used as an identifier, but we must keep
708 // the permission string intact. So we map the identifier name on use_ while using
709 // the permission string "use". In all other cases we can simply use the stringified
710 // identifier as permission string.
711 (use, $privileged:expr) => {
712 check_key_perm!(use_, $privileged, "use");
713 };
714 ($perm:ident, $privileged:expr) => {
715 check_key_perm!($perm, $privileged, stringify!($perm));
716 };
717 ($perm:ident, $privileged:expr, $p_str:expr) => {
718 #[test]
719 fn $perm() -> Result<()> {
720 android_logger::init_once(
721 android_logger::Config::default()
722 .with_tag("keystore_selinux_tests")
723 .with_min_level(log::Level::Debug),
724 );
725 let scontext = Context::new("u:r:shell:s0")?;
726 let backend = KeystoreKeyBackend::new()?;
727 let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
728
729 if $privileged {
730 assert_eq!(
731 Some(&Error::perm()),
732 check_access(
733 &scontext,
734 &tcontext,
735 "keystore2_key",
736 $p_str
737 )
738 .err()
739 .unwrap()
740 .root_cause()
741 .downcast_ref::<Error>()
742 );
743 } else {
744 assert!(check_access(
745 &scontext,
746 &tcontext,
747 "keystore2_key",
748 $p_str
749 )
750 .is_ok());
751 }
752 Ok(())
753 }
754 };
755 }
756
757 check_key_perm!(manage_blob, true);
758 check_key_perm!(delete, false);
759 check_key_perm!(use_dev_id, true);
760 check_key_perm!(req_forced_op, true);
761 check_key_perm!(gen_unique_id, true);
762 check_key_perm!(grant, true);
763 check_key_perm!(get_info, false);
Janis Danisevskisce995432020-07-21 12:22:34 -0700764 check_key_perm!(rebind, false);
765 check_key_perm!(update, false);
766 check_key_perm!(use, false);
767
768 macro_rules! check_keystore_perm {
769 ($perm:ident) => {
770 #[test]
771 fn $perm() -> Result<()> {
772 let ks_context = Context::new("u:object_r:keystore:s0")?;
773 let priv_context = Context::new("u:r:system_server:s0")?;
774 let unpriv_context = Context::new("u:r:shell:s0")?;
775 assert!(check_access(
776 &priv_context,
777 &ks_context,
778 "keystore2",
779 stringify!($perm)
780 )
781 .is_ok());
782 assert_eq!(
783 Some(&Error::perm()),
784 check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
785 .err()
786 .unwrap()
787 .root_cause()
788 .downcast_ref::<Error>()
789 );
790 Ok(())
791 }
792 };
793 }
794
795 check_keystore_perm!(add_auth);
796 check_keystore_perm!(clear_ns);
Janis Danisevskisce995432020-07-21 12:22:34 -0700797 check_keystore_perm!(lock);
798 check_keystore_perm!(reset);
799 check_keystore_perm!(unlock);
800 }
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700801
802 #[test]
803 fn test_getpidcon() {
804 // Check that `getpidcon` of our pid is equal to what `getcon` returns.
805 // And by using `unwrap` we make sure that both also have to return successfully
806 // fully to pass the test.
807 assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
808 }
Janis Danisevskisce995432020-07-21 12:22:34 -0700809}