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