blob: 8bc3bc447fffdbae4f8f205daa25259d7744298b [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
15// #![allow(missing_docs)]
16
17//! This crate provides some safe wrappers around the libselinux API. It is currently limited
18//! to the API surface that Keystore 2.0 requires to perform permission checks against
19//! the SEPolicy. Notably, it provides wrappers for:
20//! * getcon
21//! * selinux_check_access
22//! * selabel_lookup for the keystore2_key backend.
23//! And it provides an owning wrapper around context strings `Context`.
24
25use std::ffi::{CStr, CString};
26use std::fmt;
27use std::io;
Janis Danisevskis4ad056f2020-08-05 19:46:46 +000028use std::marker::{Send, Sync};
Janis Danisevskisce995432020-07-21 12:22:34 -070029pub use std::ops::Deref;
30use std::os::raw::c_char;
31use std::ptr;
32use std::sync;
33
34use selinux_bindgen as selinux;
35
36use anyhow::Context as AnyhowContext;
37use anyhow::{anyhow, Result};
38
39use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
40use selinux::SELINUX_CB_LOG;
41
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070042pub use selinux::pid_t;
43
Janis Danisevskisce995432020-07-21 12:22:34 -070044static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
45
46fn redirect_selinux_logs_to_logcat() {
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070047 // `selinux_set_callback` assigns the static lifetime function pointer
Janis Danisevskisce995432020-07-21 12:22:34 -070048 // `selinux_log_callback` to a static lifetime variable.
49 let cb = selinux::selinux_callback { func_log: Some(selinux::selinux_log_callback) };
50 unsafe {
51 selinux::selinux_set_callback(SELINUX_CB_LOG as i32, cb);
52 }
53}
54
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070055// This function must be called before any entry point into lib selinux.
Janis Danisevskisce995432020-07-21 12:22:34 -070056// Or leave a comment reasoning why calling this macro is not necessary
57// for a given entry point.
58fn init_logger_once() {
59 SELINUX_LOG_INIT.call_once(redirect_selinux_logs_to_logcat)
60}
61
62/// Selinux Error code.
63#[derive(thiserror::Error, Debug, PartialEq)]
64pub enum Error {
65 /// Indicates that an access check yielded no access.
66 #[error("Permission Denied")]
67 PermissionDenied,
68 /// Indicates an unexpected system error. Nested string provides some details.
69 #[error("Selinux SystemError: {0}")]
70 SystemError(String),
71}
72
73impl Error {
74 /// Constructs a `PermissionDenied` error.
75 pub fn perm() -> Self {
76 Error::PermissionDenied
77 }
78 fn sys<T: Into<String>>(s: T) -> Self {
79 Error::SystemError(s.into())
80 }
81}
82
83/// Context represents an SELinux context string. It can take ownership of a raw
84/// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
85/// `freecon` to free the resources when dropped. In its second variant it stores
86/// an `std::ffi::CString` that can be initialized from a Rust string slice.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070087#[derive(Debug)]
Janis Danisevskisce995432020-07-21 12:22:34 -070088pub enum Context {
89 /// Wraps a raw context c-string as returned by libselinux.
90 Raw(*mut ::std::os::raw::c_char),
91 /// Stores a context string as `std::ffi::CString`.
92 CString(CString),
93}
94
Janis Danisevskis63c4fb02020-08-25 20:29:01 -070095impl PartialEq for Context {
96 fn eq(&self, other: &Self) -> bool {
97 // We dereference both and thereby delegate the comparison
98 // to `CStr`'s implementation of `PartialEq`.
99 **self == **other
100 }
101}
102
103impl Eq for Context {}
104
Janis Danisevskisce995432020-07-21 12:22:34 -0700105impl fmt::Display for Context {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
108 }
109}
110
111impl Drop for Context {
112 fn drop(&mut self) {
113 if let Self::Raw(p) = self {
114 // No need to initialize the logger here, because
115 // `freecon` cannot run unless `Backend::lookup` or `getcon`
116 // has run.
117 unsafe { selinux::freecon(*p) };
118 }
119 }
120}
121
122impl Deref for Context {
123 type Target = CStr;
124
125 fn deref(&self) -> &Self::Target {
126 match self {
127 Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
128 Self::CString(cstr) => &cstr,
129 }
130 }
131}
132
133impl Context {
134 /// Initializes the `Context::CString` variant from a Rust string slice.
135 pub fn new(con: &str) -> Result<Self> {
136 Ok(Self::CString(
137 CString::new(con)
138 .with_context(|| format!("Failed to create Context with \"{}\"", con))?,
139 ))
140 }
141}
142
143/// The backend trait provides a uniform interface to all libselinux context backends.
144/// Currently, we only implement the KeystoreKeyBackend though.
145pub trait Backend {
146 /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
147 fn lookup(&self, key: &str) -> Result<Context>;
148}
149
150/// Keystore key backend takes onwnership of the SELinux context handle returned by
151/// `selinux_android_keystore2_key_context_handle` and uses `selabel_close` to free
152/// the handle when dropped.
153/// It implements `Backend` to provide keystore_key label lookup functionality.
154pub struct KeystoreKeyBackend {
155 handle: *mut selinux::selabel_handle,
156}
157
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000158// KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
159unsafe impl Sync for KeystoreKeyBackend {}
160unsafe impl Send for KeystoreKeyBackend {}
161
Janis Danisevskisce995432020-07-21 12:22:34 -0700162impl KeystoreKeyBackend {
163 const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32;
164
165 /// Creates a new instance representing an SELinux context handle as returned by
166 /// `selinux_android_keystore2_key_context_handle`.
167 pub fn new() -> Result<Self> {
168 init_logger_once();
169 let handle = unsafe { selinux::selinux_android_keystore2_key_context_handle() };
170 if handle.is_null() {
171 return Err(anyhow!(Error::sys("Failed to open KeystoreKeyBackend")));
172 }
173 Ok(KeystoreKeyBackend { handle })
174 }
175}
176
177impl Drop for KeystoreKeyBackend {
178 fn drop(&mut self) {
179 // No need to initialize the logger here because it cannot be called unless
180 // KeystoreKeyBackend::new has run.
181 unsafe { selinux::selabel_close(self.handle) };
182 }
183}
184
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000185// Because KeystoreKeyBackend is Sync and Send, member function must never call
186// non thread safe libselinux functions. As of this writing no non thread safe
187// functions exist that could be called on a label backend handle.
Janis Danisevskisce995432020-07-21 12:22:34 -0700188impl Backend for KeystoreKeyBackend {
189 fn lookup(&self, key: &str) -> Result<Context> {
190 let mut con: *mut c_char = ptr::null_mut();
191 let c_key = CString::new(key).with_context(|| {
192 format!("selabel_lookup: Failed to convert key \"{}\" to CString.", key)
193 })?;
194 match unsafe {
195 // No need to initialize the logger here because it cannot run unless
196 // KeystoreKeyBackend::new has run.
197 selinux::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_TYPE)
198 } {
199 0 => {
200 if !con.is_null() {
201 Ok(Context::Raw(con))
202 } else {
203 Err(anyhow!(Error::sys(format!(
204 "selabel_lookup returned a NULL context for key \"{}\"",
205 key
206 ))))
207 }
208 }
209 _ => Err(anyhow!(io::Error::last_os_error()))
210 .with_context(|| format!("selabel_lookup failed for key \"{}\"", key)),
211 }
212 }
213}
214
215/// Safe wrapper around libselinux `getcon`. It initializes the `Context::Raw` variant of the
216/// returned `Context`.
217///
218/// ## Return
219/// * Ok(Context::Raw()) if successful.
220/// * Err(Error::sys()) if getcon succeeded but returned a NULL pointer.
221/// * Err(io::Error::last_os_error()) if getcon failed.
222pub fn getcon() -> Result<Context> {
223 init_logger_once();
224 let mut con: *mut c_char = ptr::null_mut();
225 match unsafe { selinux::getcon(&mut con) } {
226 0 => {
227 if !con.is_null() {
228 Ok(Context::Raw(con))
229 } else {
230 Err(anyhow!(Error::sys("getcon returned a NULL context")))
231 }
232 }
233 _ => Err(anyhow!(io::Error::last_os_error())).context("getcon failed"),
234 }
235}
236
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700237/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
238/// returned `Context`.
239///
240/// ## Return
241/// * Ok(Context::Raw()) if successful.
242/// * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
243/// * Err(io::Error::last_os_error()) if getpidcon failed.
244pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
245 init_logger_once();
246 let mut con: *mut c_char = ptr::null_mut();
247 match unsafe { selinux::getpidcon(pid, &mut con) } {
248 0 => {
249 if !con.is_null() {
250 Ok(Context::Raw(con))
251 } else {
252 Err(anyhow!(Error::sys(format!(
253 "getpidcon returned a NULL context for pid {}",
254 pid
255 ))))
256 }
257 }
258 _ => Err(anyhow!(io::Error::last_os_error()))
259 .context(format!("getpidcon failed for pid {}", pid)),
260 }
261}
262
Janis Danisevskisce995432020-07-21 12:22:34 -0700263/// Safe wrapper around selinux_check_access.
264///
265/// ## Return
266/// * Ok(()) iff the requested access was granted.
267/// * Err(anyhow!(Error::perm()))) if the permission was denied.
268/// * Err(anyhow!(ioError::last_os_error())) if any other error occurred while performing
269/// the access check.
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700270pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
Janis Danisevskisce995432020-07-21 12:22:34 -0700271 init_logger_once();
272 let c_tclass = CString::new(tclass).with_context(|| {
273 format!("check_access: Failed to convert tclass \"{}\" to CString.", tclass)
274 })?;
275 let c_perm = CString::new(perm).with_context(|| {
276 format!("check_access: Failed to convert perm \"{}\" to CString.", perm)
277 })?;
278
279 match unsafe {
280 selinux::selinux_check_access(
281 source.as_ptr(),
282 target.as_ptr(),
283 c_tclass.as_ptr(),
284 c_perm.as_ptr(),
285 ptr::null_mut(),
286 )
287 } {
288 0 => Ok(()),
289 _ => {
290 let e = io::Error::last_os_error();
291 match e.kind() {
292 io::ErrorKind::PermissionDenied => Err(anyhow!(Error::perm())),
293 _ => Err(anyhow!(e)),
294 }
295 .with_context(|| {
296 format!(
297 concat!(
Janis Danisevskis935e6c62020-08-18 12:52:27 -0700298 "check_access: Failed with sctx: {:?} tctx: {:?}",
Janis Danisevskisce995432020-07-21 12:22:34 -0700299 " with target class: \"{}\" perm: \"{}\""
300 ),
301 source, target, tclass, perm
302 )
303 })
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use anyhow::Result;
312
313 /// The su_key namespace as defined in su.te and keystore_key_contexts of the
314 /// SePolicy (system/sepolicy).
315 static SU_KEY_NAMESPACE: &str = "0";
316 /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
317 /// SePolicy (system/sepolicy).
318 static SHELL_KEY_NAMESPACE: &str = "1";
319
320 fn check_context() -> Result<(Context, &'static str, bool)> {
321 let context = getcon()?;
322 match context.to_str().unwrap() {
323 "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
324 "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
325 c => Err(anyhow!(format!(
326 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
327 c
328 ))),
329 }
330 }
331
332 #[test]
333 fn test_getcon() -> Result<()> {
334 check_context()?;
335 Ok(())
336 }
337
338 #[test]
339 fn test_label_lookup() -> Result<()> {
340 let (_context, namespace, is_su) = check_context()?;
341 let backend = crate::KeystoreKeyBackend::new()?;
342 let context = backend.lookup(namespace)?;
343 if is_su {
344 assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
345 } else {
346 assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
347 }
348 Ok(())
349 }
350
351 #[test]
352 fn context_from_string() -> Result<()> {
353 let tctx = Context::new("u:object_r:keystore:s0").unwrap();
354 let sctx = Context::new("u:r:system_server:s0").unwrap();
355 check_access(&sctx, &tctx, "keystore2_key", "use")?;
356 Ok(())
357 }
358
359 mod perm {
360 use super::super::*;
361 use super::*;
362 use anyhow::Result;
363
364 /// check_key_perm(perm, privileged, priv_domain)
365 /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
366 /// indicating whether the permission is considered privileged.
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700367 /// Privileged permissions are expected to be denied to `shell` users but granted
Janis Danisevskisce995432020-07-21 12:22:34 -0700368 /// to the given priv_domain.
369 macro_rules! check_key_perm {
370 // "use" is a keyword and cannot be used as an identifier, but we must keep
371 // the permission string intact. So we map the identifier name on use_ while using
372 // the permission string "use". In all other cases we can simply use the stringified
373 // identifier as permission string.
374 (use, $privileged:expr) => {
375 check_key_perm!(use_, $privileged, "use");
376 };
377 ($perm:ident, $privileged:expr) => {
378 check_key_perm!($perm, $privileged, stringify!($perm));
379 };
380 ($perm:ident, $privileged:expr, $p_str:expr) => {
381 #[test]
382 fn $perm() -> Result<()> {
383 android_logger::init_once(
384 android_logger::Config::default()
385 .with_tag("keystore_selinux_tests")
386 .with_min_level(log::Level::Debug),
387 );
388 let scontext = Context::new("u:r:shell:s0")?;
389 let backend = KeystoreKeyBackend::new()?;
390 let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
391
392 if $privileged {
393 assert_eq!(
394 Some(&Error::perm()),
395 check_access(
396 &scontext,
397 &tcontext,
398 "keystore2_key",
399 $p_str
400 )
401 .err()
402 .unwrap()
403 .root_cause()
404 .downcast_ref::<Error>()
405 );
406 } else {
407 assert!(check_access(
408 &scontext,
409 &tcontext,
410 "keystore2_key",
411 $p_str
412 )
413 .is_ok());
414 }
415 Ok(())
416 }
417 };
418 }
419
420 check_key_perm!(manage_blob, true);
421 check_key_perm!(delete, false);
422 check_key_perm!(use_dev_id, true);
423 check_key_perm!(req_forced_op, true);
424 check_key_perm!(gen_unique_id, true);
425 check_key_perm!(grant, true);
426 check_key_perm!(get_info, false);
427 check_key_perm!(list, false);
428 check_key_perm!(rebind, false);
429 check_key_perm!(update, false);
430 check_key_perm!(use, false);
431
432 macro_rules! check_keystore_perm {
433 ($perm:ident) => {
434 #[test]
435 fn $perm() -> Result<()> {
436 let ks_context = Context::new("u:object_r:keystore:s0")?;
437 let priv_context = Context::new("u:r:system_server:s0")?;
438 let unpriv_context = Context::new("u:r:shell:s0")?;
439 assert!(check_access(
440 &priv_context,
441 &ks_context,
442 "keystore2",
443 stringify!($perm)
444 )
445 .is_ok());
446 assert_eq!(
447 Some(&Error::perm()),
448 check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
449 .err()
450 .unwrap()
451 .root_cause()
452 .downcast_ref::<Error>()
453 );
454 Ok(())
455 }
456 };
457 }
458
459 check_keystore_perm!(add_auth);
460 check_keystore_perm!(clear_ns);
461 check_keystore_perm!(get_state);
462 check_keystore_perm!(lock);
463 check_keystore_perm!(reset);
464 check_keystore_perm!(unlock);
465 }
Janis Danisevskis63c4fb02020-08-25 20:29:01 -0700466
467 #[test]
468 fn test_getpidcon() {
469 // Check that `getpidcon` of our pid is equal to what `getcon` returns.
470 // And by using `unwrap` we make sure that both also have to return successfully
471 // fully to pass the test.
472 assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
473 }
Janis Danisevskisce995432020-07-21 12:22:34 -0700474}