blob: 05f9db8dfa3f73522345470e5304a043aa58d3e8 [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
42static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
43
44fn redirect_selinux_logs_to_logcat() {
45 // `selinux_set_callback` assigned the static lifetime function pointer
46 // `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
53// This function must be called before any entrypoint into lib selinux.
54// 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.
85pub enum Context {
86 /// Wraps a raw context c-string as returned by libselinux.
87 Raw(*mut ::std::os::raw::c_char),
88 /// Stores a context string as `std::ffi::CString`.
89 CString(CString),
90}
91
92impl fmt::Display for Context {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(f, "{}", (**self).to_str().unwrap_or("Invalid context"))
95 }
96}
97
98impl Drop for Context {
99 fn drop(&mut self) {
100 if let Self::Raw(p) = self {
101 // No need to initialize the logger here, because
102 // `freecon` cannot run unless `Backend::lookup` or `getcon`
103 // has run.
104 unsafe { selinux::freecon(*p) };
105 }
106 }
107}
108
109impl Deref for Context {
110 type Target = CStr;
111
112 fn deref(&self) -> &Self::Target {
113 match self {
114 Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
115 Self::CString(cstr) => &cstr,
116 }
117 }
118}
119
120impl Context {
121 /// Initializes the `Context::CString` variant from a Rust string slice.
122 pub fn new(con: &str) -> Result<Self> {
123 Ok(Self::CString(
124 CString::new(con)
125 .with_context(|| format!("Failed to create Context with \"{}\"", con))?,
126 ))
127 }
128}
129
130/// The backend trait provides a uniform interface to all libselinux context backends.
131/// Currently, we only implement the KeystoreKeyBackend though.
132pub trait Backend {
133 /// Implementers use libselinux `selabel_lookup` to lookup the context for the given `key`.
134 fn lookup(&self, key: &str) -> Result<Context>;
135}
136
137/// Keystore key backend takes onwnership of the SELinux context handle returned by
138/// `selinux_android_keystore2_key_context_handle` and uses `selabel_close` to free
139/// the handle when dropped.
140/// It implements `Backend` to provide keystore_key label lookup functionality.
141pub struct KeystoreKeyBackend {
142 handle: *mut selinux::selabel_handle,
143}
144
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000145// KeystoreKeyBackend is Sync because selabel_lookup is thread safe.
146unsafe impl Sync for KeystoreKeyBackend {}
147unsafe impl Send for KeystoreKeyBackend {}
148
Janis Danisevskisce995432020-07-21 12:22:34 -0700149impl KeystoreKeyBackend {
150 const BACKEND_TYPE: i32 = SELABEL_CTX_ANDROID_KEYSTORE2_KEY as i32;
151
152 /// Creates a new instance representing an SELinux context handle as returned by
153 /// `selinux_android_keystore2_key_context_handle`.
154 pub fn new() -> Result<Self> {
155 init_logger_once();
156 let handle = unsafe { selinux::selinux_android_keystore2_key_context_handle() };
157 if handle.is_null() {
158 return Err(anyhow!(Error::sys("Failed to open KeystoreKeyBackend")));
159 }
160 Ok(KeystoreKeyBackend { handle })
161 }
162}
163
164impl Drop for KeystoreKeyBackend {
165 fn drop(&mut self) {
166 // No need to initialize the logger here because it cannot be called unless
167 // KeystoreKeyBackend::new has run.
168 unsafe { selinux::selabel_close(self.handle) };
169 }
170}
171
Janis Danisevskis4ad056f2020-08-05 19:46:46 +0000172// Because KeystoreKeyBackend is Sync and Send, member function must never call
173// non thread safe libselinux functions. As of this writing no non thread safe
174// functions exist that could be called on a label backend handle.
Janis Danisevskisce995432020-07-21 12:22:34 -0700175impl Backend for KeystoreKeyBackend {
176 fn lookup(&self, key: &str) -> Result<Context> {
177 let mut con: *mut c_char = ptr::null_mut();
178 let c_key = CString::new(key).with_context(|| {
179 format!("selabel_lookup: Failed to convert key \"{}\" to CString.", key)
180 })?;
181 match unsafe {
182 // No need to initialize the logger here because it cannot run unless
183 // KeystoreKeyBackend::new has run.
184 selinux::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_TYPE)
185 } {
186 0 => {
187 if !con.is_null() {
188 Ok(Context::Raw(con))
189 } else {
190 Err(anyhow!(Error::sys(format!(
191 "selabel_lookup returned a NULL context for key \"{}\"",
192 key
193 ))))
194 }
195 }
196 _ => Err(anyhow!(io::Error::last_os_error()))
197 .with_context(|| format!("selabel_lookup failed for key \"{}\"", key)),
198 }
199 }
200}
201
202/// Safe wrapper around libselinux `getcon`. It initializes the `Context::Raw` variant of the
203/// returned `Context`.
204///
205/// ## Return
206/// * Ok(Context::Raw()) if successful.
207/// * Err(Error::sys()) if getcon succeeded but returned a NULL pointer.
208/// * Err(io::Error::last_os_error()) if getcon failed.
209pub fn getcon() -> Result<Context> {
210 init_logger_once();
211 let mut con: *mut c_char = ptr::null_mut();
212 match unsafe { selinux::getcon(&mut con) } {
213 0 => {
214 if !con.is_null() {
215 Ok(Context::Raw(con))
216 } else {
217 Err(anyhow!(Error::sys("getcon returned a NULL context")))
218 }
219 }
220 _ => Err(anyhow!(io::Error::last_os_error())).context("getcon failed"),
221 }
222}
223
224/// Safe wrapper around selinux_check_access.
225///
226/// ## Return
227/// * Ok(()) iff the requested access was granted.
228/// * Err(anyhow!(Error::perm()))) if the permission was denied.
229/// * Err(anyhow!(ioError::last_os_error())) if any other error occurred while performing
230/// the access check.
231pub fn check_access(source: &Context, target: &Context, tclass: &str, perm: &str) -> Result<()> {
232 init_logger_once();
233 let c_tclass = CString::new(tclass).with_context(|| {
234 format!("check_access: Failed to convert tclass \"{}\" to CString.", tclass)
235 })?;
236 let c_perm = CString::new(perm).with_context(|| {
237 format!("check_access: Failed to convert perm \"{}\" to CString.", perm)
238 })?;
239
240 match unsafe {
241 selinux::selinux_check_access(
242 source.as_ptr(),
243 target.as_ptr(),
244 c_tclass.as_ptr(),
245 c_perm.as_ptr(),
246 ptr::null_mut(),
247 )
248 } {
249 0 => Ok(()),
250 _ => {
251 let e = io::Error::last_os_error();
252 match e.kind() {
253 io::ErrorKind::PermissionDenied => Err(anyhow!(Error::perm())),
254 _ => Err(anyhow!(e)),
255 }
256 .with_context(|| {
257 format!(
258 concat!(
259 "check_access: Failed with sctx: {} tctx: {}",
260 " with target class: \"{}\" perm: \"{}\""
261 ),
262 source, target, tclass, perm
263 )
264 })
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use anyhow::Result;
273
274 /// The su_key namespace as defined in su.te and keystore_key_contexts of the
275 /// SePolicy (system/sepolicy).
276 static SU_KEY_NAMESPACE: &str = "0";
277 /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
278 /// SePolicy (system/sepolicy).
279 static SHELL_KEY_NAMESPACE: &str = "1";
280
281 fn check_context() -> Result<(Context, &'static str, bool)> {
282 let context = getcon()?;
283 match context.to_str().unwrap() {
284 "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
285 "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
286 c => Err(anyhow!(format!(
287 "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
288 c
289 ))),
290 }
291 }
292
293 #[test]
294 fn test_getcon() -> Result<()> {
295 check_context()?;
296 Ok(())
297 }
298
299 #[test]
300 fn test_label_lookup() -> Result<()> {
301 let (_context, namespace, is_su) = check_context()?;
302 let backend = crate::KeystoreKeyBackend::new()?;
303 let context = backend.lookup(namespace)?;
304 if is_su {
305 assert_eq!(context.to_str(), Ok("u:object_r:su_key:s0"));
306 } else {
307 assert_eq!(context.to_str(), Ok("u:object_r:shell_key:s0"));
308 }
309 Ok(())
310 }
311
312 #[test]
313 fn context_from_string() -> Result<()> {
314 let tctx = Context::new("u:object_r:keystore:s0").unwrap();
315 let sctx = Context::new("u:r:system_server:s0").unwrap();
316 check_access(&sctx, &tctx, "keystore2_key", "use")?;
317 Ok(())
318 }
319
320 mod perm {
321 use super::super::*;
322 use super::*;
323 use anyhow::Result;
324
325 /// check_key_perm(perm, privileged, priv_domain)
326 /// `perm` is a permission of the keystore2_key class and `privileged` is a boolean
327 /// indicating whether the permission is considered privileged.
328 /// Privileged permissions are expeced to be denied to `shell` users but granted
329 /// to the given priv_domain.
330 macro_rules! check_key_perm {
331 // "use" is a keyword and cannot be used as an identifier, but we must keep
332 // the permission string intact. So we map the identifier name on use_ while using
333 // the permission string "use". In all other cases we can simply use the stringified
334 // identifier as permission string.
335 (use, $privileged:expr) => {
336 check_key_perm!(use_, $privileged, "use");
337 };
338 ($perm:ident, $privileged:expr) => {
339 check_key_perm!($perm, $privileged, stringify!($perm));
340 };
341 ($perm:ident, $privileged:expr, $p_str:expr) => {
342 #[test]
343 fn $perm() -> Result<()> {
344 android_logger::init_once(
345 android_logger::Config::default()
346 .with_tag("keystore_selinux_tests")
347 .with_min_level(log::Level::Debug),
348 );
349 let scontext = Context::new("u:r:shell:s0")?;
350 let backend = KeystoreKeyBackend::new()?;
351 let tcontext = backend.lookup(SHELL_KEY_NAMESPACE)?;
352
353 if $privileged {
354 assert_eq!(
355 Some(&Error::perm()),
356 check_access(
357 &scontext,
358 &tcontext,
359 "keystore2_key",
360 $p_str
361 )
362 .err()
363 .unwrap()
364 .root_cause()
365 .downcast_ref::<Error>()
366 );
367 } else {
368 assert!(check_access(
369 &scontext,
370 &tcontext,
371 "keystore2_key",
372 $p_str
373 )
374 .is_ok());
375 }
376 Ok(())
377 }
378 };
379 }
380
381 check_key_perm!(manage_blob, true);
382 check_key_perm!(delete, false);
383 check_key_perm!(use_dev_id, true);
384 check_key_perm!(req_forced_op, true);
385 check_key_perm!(gen_unique_id, true);
386 check_key_perm!(grant, true);
387 check_key_perm!(get_info, false);
388 check_key_perm!(list, false);
389 check_key_perm!(rebind, false);
390 check_key_perm!(update, false);
391 check_key_perm!(use, false);
392
393 macro_rules! check_keystore_perm {
394 ($perm:ident) => {
395 #[test]
396 fn $perm() -> Result<()> {
397 let ks_context = Context::new("u:object_r:keystore:s0")?;
398 let priv_context = Context::new("u:r:system_server:s0")?;
399 let unpriv_context = Context::new("u:r:shell:s0")?;
400 assert!(check_access(
401 &priv_context,
402 &ks_context,
403 "keystore2",
404 stringify!($perm)
405 )
406 .is_ok());
407 assert_eq!(
408 Some(&Error::perm()),
409 check_access(&unpriv_context, &ks_context, "keystore2", stringify!($perm))
410 .err()
411 .unwrap()
412 .root_cause()
413 .downcast_ref::<Error>()
414 );
415 Ok(())
416 }
417 };
418 }
419
420 check_keystore_perm!(add_auth);
421 check_keystore_perm!(clear_ns);
422 check_keystore_perm!(get_state);
423 check_keystore_perm!(lock);
424 check_keystore_perm!(reset);
425 check_keystore_perm!(unlock);
426 }
427}