• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // 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 
15 //! 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 
23 use anyhow::Context as AnyhowContext;
24 use anyhow::{anyhow, Result};
25 use lazy_static::lazy_static;
26 pub use selinux::pid_t;
27 use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
28 use selinux::SELINUX_CB_LOG;
29 use selinux_bindgen as selinux;
30 use std::ffi::{CStr, CString};
31 use std::fmt;
32 use std::io;
33 use std::marker::{Send, Sync};
34 pub use std::ops::Deref;
35 use std::os::raw::c_char;
36 use std::ptr;
37 use std::sync;
38 
39 static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
40 
41 lazy_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 
redirect_selinux_logs_to_logcat()51 fn redirect_selinux_logs_to_logcat() {
52     // `selinux_set_callback` assigns the static lifetime function pointer
53     // `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 
60 // This function must be called before any entry point into lib selinux.
61 // Or leave a comment reasoning why calling this macro is not necessary
62 // for a given entry point.
init_logger_once()63 fn 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)]
69 pub 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 
78 impl Error {
79     /// Constructs a `PermissionDenied` error.
perm() -> Self80     pub fn perm() -> Self {
81         Error::PermissionDenied
82     }
sys<T: Into<String>>(s: T) -> Self83     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.
92 #[derive(Debug)]
93 pub 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 
100 impl PartialEq for Context {
eq(&self, other: &Self) -> bool101     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 
108 impl Eq for Context {}
109 
110 impl fmt::Display for Context {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result111     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112         write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
113     }
114 }
115 
116 impl Drop for Context {
drop(&mut self)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 
127 impl Deref for Context {
128     type Target = CStr;
129 
deref(&self) -> &Self::Target130     fn deref(&self) -> &Self::Target {
131         match self {
132             Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
133             Self::CString(cstr) => &cstr,
134         }
135     }
136 }
137 
138 impl Context {
139     /// Initializes the `Context::CString` variant from a Rust string slice.
new(con: &str) -> Result<Self>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.
150 pub trait Backend {
151     /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
lookup(&self, key: &str) -> Result<Context>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.
159 pub struct KeystoreKeyBackend {
160     handle: *mut selinux::selabel_handle,
161 }
162 
163 // KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
164 unsafe impl Sync for KeystoreKeyBackend {}
165 unsafe impl Send for KeystoreKeyBackend {}
166 
167 impl 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`.
new() -> Result<Self>172     pub fn new() -> Result<Self> {
173         init_logger_once();
174         let _lock = LIB_SELINUX_LOCK.lock().unwrap();
175 
176         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 
184 impl Drop for KeystoreKeyBackend {
drop(&mut self)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 
192 // 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.
195 impl Backend for KeystoreKeyBackend {
lookup(&self, key: &str) -> Result<Context>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.
204             let _lock = LIB_SELINUX_LOCK.lock().unwrap();
205 
206             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.
getcon() -> Result<Context>231 pub fn getcon() -> Result<Context> {
232     init_logger_once();
233     let _lock = LIB_SELINUX_LOCK.lock().unwrap();
234 
235     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 
248 /// 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.
getpidcon(pid: selinux::pid_t) -> Result<Context>255 pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
256     init_logger_once();
257     let _lock = LIB_SELINUX_LOCK.lock().unwrap();
258 
259     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 
276 /// 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.
check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()>283 pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
284     init_logger_once();
285 
286     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 {
294         let _lock = LIB_SELINUX_LOCK.lock().unwrap();
295 
296         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!(
314                         "check_access: Failed with sctx: {:?} tctx: {:?}",
315                         " with target class: \"{}\" perm: \"{}\""
316                     ),
317                     source, target, tclass, perm
318                 )
319             })
320         }
321     }
322 }
323 
324 #[cfg(test)]
325 mod tests {
326     use super::*;
327     use anyhow::Result;
328 
329     /// The su_key namespace as defined in su.te and keystore_key_contexts of the
330     /// SePolicy (system/sepolicy).
331     static SU_KEY_NAMESPACE: &str = "0";
332     /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
333     /// SePolicy (system/sepolicy).
334     static SHELL_KEY_NAMESPACE: &str = "1";
335 
check_context() -> Result<(Context, &'static str, bool)>336     fn check_context() -> Result<(Context, &'static str, bool)> {
337         let context = getcon()?;
338         match context.to_str().unwrap() {
339             "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
340             "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
341             c => Err(anyhow!(format!(
342                 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
343                 c
344             ))),
345         }
346     }
347 
348     #[test]
test_getcon() -> Result<()>349     fn test_getcon() -> Result<()> {
350         check_context()?;
351         Ok(())
352     }
353 
354     #[test]
test_label_lookup() -> Result<()>355     fn test_label_lookup() -> Result<()> {
356         let (_context, namespace, is_su) = check_context()?;
357         let backend = crate::KeystoreKeyBackend::new()?;
358         let context = backend.lookup(namespace)?;
359         if is_su {
360             assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
361         } else {
362             assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
363         }
364         Ok(())
365     }
366 
367     #[test]
context_from_string() -> Result<()>368     fn context_from_string() -> Result<()> {
369         let tctx = Context::new("u:object_r:keystore:s0").unwrap();
370         let sctx = Context::new("u:r:system_server:s0").unwrap();
371         check_access(&sctx, &tctx, "keystore2_key", "use")?;
372         Ok(())
373     }
374 
375     mod perm {
376         use super::super::*;
377         use super::*;
378         use anyhow::Result;
379 
380         /// check_key_perm(perm, privileged, priv_domain)
381         /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
382         /// indicating whether the permission is considered privileged.
383         /// Privileged permissions are expected to be denied to `shell` users but granted
384         /// to the given priv_domain.
385         macro_rules! check_key_perm {
386             // "use" is a keyword and cannot be used as an identifier, but we must keep
387             // the permission string intact. So we map the identifier name on use_ while using
388             // the permission string "use". In all other cases we can simply use the stringified
389             // identifier as permission string.
390             (use, $privileged:expr) => {
391                 check_key_perm!(use_, $privileged, "use");
392             };
393             ($perm:ident, $privileged:expr) => {
394                 check_key_perm!($perm, $privileged, stringify!($perm));
395             };
396             ($perm:ident, $privileged:expr, $p_str:expr) => {
397                 #[test]
398                 fn $perm() -> Result<()> {
399                     android_logger::init_once(
400                         android_logger::Config::default()
401                             .with_tag("keystore_selinux_tests")
402                             .with_min_level(log::Level::Debug),
403                     );
404                     let scontext = Context::new("u:r:shell:s0")?;
405                     let backend = KeystoreKeyBackend::new()?;
406                     let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
407 
408                     if $privileged {
409                         assert_eq!(
410                             Some(&Error::perm()),
411                             check_access(
412                                 &scontext,
413                                 &tcontext,
414                                 "keystore2_key",
415                                 $p_str
416                             )
417                             .err()
418                             .unwrap()
419                             .root_cause()
420                             .downcast_ref::<Error>()
421                         );
422                     } else {
423                         assert!(check_access(
424                             &scontext,
425                             &tcontext,
426                             "keystore2_key",
427                             $p_str
428                         )
429                         .is_ok());
430                     }
431                     Ok(())
432                 }
433             };
434         }
435 
436         check_key_perm!(manage_blob, true);
437         check_key_perm!(delete, false);
438         check_key_perm!(use_dev_id, true);
439         check_key_perm!(req_forced_op, true);
440         check_key_perm!(gen_unique_id, true);
441         check_key_perm!(grant, true);
442         check_key_perm!(get_info, false);
443         check_key_perm!(rebind, false);
444         check_key_perm!(update, false);
445         check_key_perm!(use, false);
446 
447         macro_rules! check_keystore_perm {
448             ($perm:ident) => {
449                 #[test]
450                 fn $perm() -> Result<()> {
451                     let ks_context = Context::new("u:object_r:keystore:s0")?;
452                     let priv_context = Context::new("u:r:system_server:s0")?;
453                     let unpriv_context = Context::new("u:r:shell:s0")?;
454                     assert!(check_access(
455                         &priv_context,
456                         &ks_context,
457                         "keystore2",
458                         stringify!($perm)
459                     )
460                     .is_ok());
461                     assert_eq!(
462                         Some(&Error::perm()),
463                         check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
464                             .err()
465                             .unwrap()
466                             .root_cause()
467                             .downcast_ref::<Error>()
468                     );
469                     Ok(())
470                 }
471             };
472         }
473 
474         check_keystore_perm!(add_auth);
475         check_keystore_perm!(clear_ns);
476         check_keystore_perm!(lock);
477         check_keystore_perm!(reset);
478         check_keystore_perm!(unlock);
479     }
480 
481     #[test]
test_getpidcon()482     fn test_getpidcon() {
483         // Check that `getpidcon` of our pid is equal to what `getcon` returns.
484         // And by using `unwrap` we make sure that both also have to return successfully
485         // fully to pass the test.
486         assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
487     }
488 }
489