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