1 // Copyright 2021, 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 //! Wrapper to libselinux
16 
17 use anyhow::{anyhow, bail, Context, Result};
18 use std::ffi::{c_int, CStr, CString};
19 use std::fmt;
20 use std::io;
21 use std::ops::Deref;
22 use std::os::fd::AsRawFd;
23 use std::os::raw::c_char;
24 use std::ptr;
25 use std::sync;
26 
27 static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
28 
redirect_selinux_logs_to_logcat()29 fn redirect_selinux_logs_to_logcat() {
30     let cb =
31         selinux_bindgen::selinux_callback { func_log: Some(selinux_bindgen::selinux_log_callback) };
32     // SAFETY: `selinux_set_callback` assigns the static lifetime function pointer
33     // `selinux_log_callback` to a static lifetime variable.
34     unsafe {
35         selinux_bindgen::selinux_set_callback(selinux_bindgen::SELINUX_CB_LOG as c_int, cb);
36     }
37 }
38 
init_logger_once()39 fn init_logger_once() {
40     SELINUX_LOG_INIT.call_once(redirect_selinux_logs_to_logcat)
41 }
42 
43 // Partially copied from system/security/keystore2/selinux/src/lib.rs
44 /// SeContext represents an SELinux context string. It can take ownership of a raw
45 /// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
46 /// `freecon` to free the resources when dropped. In its second variant it stores
47 /// an `std::ffi::CString` that can be initialized from a Rust string slice.
48 #[derive(Debug)]
49 #[allow(dead_code)] // CString variant is used in tests
50 pub enum SeContext {
51     /// Wraps a raw context c-string as returned by libselinux.
52     Raw(*mut ::std::os::raw::c_char),
53     /// Stores a context string as `std::ffi::CString`.
54     CString(CString),
55 }
56 
57 impl PartialEq for SeContext {
eq(&self, other: &Self) -> bool58     fn eq(&self, other: &Self) -> bool {
59         // We dereference both and thereby delegate the comparison
60         // to `CStr`'s implementation of `PartialEq`.
61         **self == **other
62     }
63 }
64 
65 impl Eq for SeContext {}
66 
67 impl fmt::Display for SeContext {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result68     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69         write!(f, "{}", self.to_str().unwrap_or("Invalid context"))
70     }
71 }
72 
73 impl Drop for SeContext {
drop(&mut self)74     fn drop(&mut self) {
75         if let Self::Raw(p) = self {
76             // SAFETY: SeContext::Raw is created only with a pointer that is set by libselinux and
77             // has to be freed with freecon.
78             unsafe { selinux_bindgen::freecon(*p) };
79         }
80     }
81 }
82 
83 impl Deref for SeContext {
84     type Target = CStr;
85 
deref(&self) -> &Self::Target86     fn deref(&self) -> &Self::Target {
87         match self {
88             // SAFETY: the non-owned C string pointed by `p` is guaranteed to be valid (non-null
89             // and shorter than i32::MAX). It is freed when SeContext is dropped.
90             Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
91             Self::CString(cstr) => cstr,
92         }
93     }
94 }
95 
96 impl SeContext {
97     /// Initializes the `SeContext::CString` variant from a Rust string slice.
98     #[allow(dead_code)] // Used in tests
new(con: &str) -> Result<Self>99     pub fn new(con: &str) -> Result<Self> {
100         Ok(Self::CString(
101             CString::new(con)
102                 .with_context(|| format!("Failed to create SeContext with \"{}\"", con))?,
103         ))
104     }
105 
selinux_type(&self) -> Result<&str>106     pub fn selinux_type(&self) -> Result<&str> {
107         let context = self.deref().to_str().context("Label is not valid UTF8")?;
108 
109         // The syntax is user:role:type:sensitivity[:category,...],
110         // ignoring security level ranges, which don't occur on Android. See
111         // https://github.com/SELinuxProject/selinux-notebook/blob/main/src/security_context.md
112         // We only want the type.
113         let fields: Vec<_> = context.split(':').collect();
114         if fields.len() < 4 || fields.len() > 5 {
115             bail!("Syntactically invalid label {}", self);
116         }
117         Ok(fields[2])
118     }
119 }
120 
121 /// Takes ownership of context handle returned by `selinux_android_tee_service_context_handle`
122 /// and closes it via `selabel_close` when dropped.
123 struct TeeServiceSelinuxBackend {
124     handle: *mut selinux_bindgen::selabel_handle,
125 }
126 
127 impl TeeServiceSelinuxBackend {
128     const BACKEND_ID: i32 = selinux_bindgen::SELABEL_CTX_ANDROID_SERVICE as i32;
129 
130     /// Creates a new instance representing selinux context handle returned from
131     /// `selinux_android_tee_service_context_handle`.
new() -> Result<Self>132     fn new() -> Result<Self> {
133         // SAFETY: selinux_android_tee_service_context_handle is always safe to call. The returned
134         // handle is valid until `selabel_close` is called on it (see the safety comment on the drop
135         // trait).
136         let handle = unsafe { selinux_bindgen::selinux_android_tee_service_context_handle() };
137         if handle.is_null() {
138             Err(anyhow!("selinux_android_tee_service_context_handle returned a NULL context"))
139         } else {
140             Ok(TeeServiceSelinuxBackend { handle })
141         }
142     }
143 
lookup(&self, tee_service: &str) -> Result<SeContext>144     fn lookup(&self, tee_service: &str) -> Result<SeContext> {
145         let mut con: *mut c_char = ptr::null_mut();
146         let c_key = CString::new(tee_service).context("failed to convert to CString")?;
147         // SAFETY: the returned pointer `con` is valid until `freecon` is called on it.
148         match unsafe {
149             selinux_bindgen::selabel_lookup(self.handle, &mut con, c_key.as_ptr(), Self::BACKEND_ID)
150         } {
151             0 => {
152                 if !con.is_null() {
153                     Ok(SeContext::Raw(con))
154                 } else {
155                     Err(anyhow!("selabel_lookup returned a NULL context"))
156                 }
157             }
158             _ => Err(anyhow!(io::Error::last_os_error())).context("selabel_lookup failed"),
159         }
160     }
161 }
162 
163 impl Drop for TeeServiceSelinuxBackend {
drop(&mut self)164     fn drop(&mut self) {
165         // SAFETY: the TeeServiceSelinuxBackend is created only with a pointer is set by
166         // libselinux and has to be freed with `selabel_close`.
167         unsafe { selinux_bindgen::selabel_close(self.handle) };
168     }
169 }
170 
getfilecon<F: AsRawFd>(file: &F) -> Result<SeContext>171 pub fn getfilecon<F: AsRawFd>(file: &F) -> Result<SeContext> {
172     let fd = file.as_raw_fd();
173     let mut con: *mut c_char = ptr::null_mut();
174     // SAFETY: the returned pointer `con` is wrapped in SeContext::Raw which is freed with
175     // `freecon` when it is dropped.
176     match unsafe { selinux_bindgen::fgetfilecon(fd, &mut con) } {
177         1.. => {
178             if !con.is_null() {
179                 Ok(SeContext::Raw(con))
180             } else {
181                 Err(anyhow!("fgetfilecon returned a NULL context"))
182             }
183         }
184         _ => Err(anyhow!(io::Error::last_os_error())).context("fgetfilecon failed"),
185     }
186 }
187 
getprevcon() -> Result<SeContext>188 pub fn getprevcon() -> Result<SeContext> {
189     let mut con: *mut c_char = ptr::null_mut();
190     // SAFETY: the returned pointer `con` is wrapped in SeContext::Raw which is freed with
191     // `freecon` when it is dropped.
192     match unsafe { selinux_bindgen::getprevcon(&mut con) } {
193         0.. => {
194             if !con.is_null() {
195                 Ok(SeContext::Raw(con))
196             } else {
197                 Err(anyhow!("getprevcon returned a NULL context"))
198             }
199         }
200         _ => Err(anyhow!(io::Error::last_os_error())).context("getprevcon failed"),
201     }
202 }
203 
204 // Wrapper around selinux_check_access
check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()>205 fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> Result<()> {
206     let c_tclass = CString::new(tclass).context("failed to convert tclass to CString")?;
207     let c_perm = CString::new(perm).context("failed to convert perm to CString")?;
208 
209     // SAFETY: lifecycle of pointers passed to the selinux_check_access outlive the duration of the
210     // call.
211     match unsafe {
212         selinux_bindgen::selinux_check_access(
213             source.as_ptr(),
214             target.as_ptr(),
215             c_tclass.as_ptr(),
216             c_perm.as_ptr(),
217             ptr::null_mut(),
218         )
219     } {
220         0 => Ok(()),
221         _ => Err(anyhow!(io::Error::last_os_error())).with_context(|| {
222             format!(
223                 "check_access: Failed with sctx: {:?} tctx: {:?} tclass: {:?} perm {:?}",
224                 source, target, tclass, perm
225             )
226         }),
227     }
228 }
229 
check_tee_service_permission(caller_ctx: &SeContext, tee_services: &[String]) -> Result<()>230 pub fn check_tee_service_permission(caller_ctx: &SeContext, tee_services: &[String]) -> Result<()> {
231     init_logger_once();
232 
233     let backend = TeeServiceSelinuxBackend::new()?;
234 
235     for tee_service in tee_services {
236         let tee_service_ctx = backend.lookup(tee_service)?;
237         check_access(caller_ctx, &tee_service_ctx, "tee_service", "use")
238             .with_context(|| format!("permission denied for {:?}", tee_service))?;
239     }
240 
241     Ok(())
242 }
243 
244 #[cfg(test)]
245 mod tests {
246     use super::*;
247 
248     #[test]
249     #[ignore = "disabling test while investigating b/379087641"]
test_check_tee_service_permission_has_permission() -> Result<()>250     fn test_check_tee_service_permission_has_permission() -> Result<()> {
251         if cfg!(not(tee_services_allowlist)) {
252             // Skip test on release configurations without tee_services_allowlist feature enabled.
253             return Ok(());
254         }
255 
256         let caller_ctx = SeContext::new("u:r:shell:s0")?;
257         let tee_services = [String::from("test_pkvm_tee_service")];
258         check_tee_service_permission(&caller_ctx, &tee_services)
259     }
260 
261     #[test]
262     #[ignore = "disabling test while investigating b/379087641"]
test_check_tee_service_permission_invalid_tee_service() -> Result<()>263     fn test_check_tee_service_permission_invalid_tee_service() -> Result<()> {
264         if cfg!(not(tee_services_allowlist)) {
265             // Skip test on release configurations without tee_services_allowlist feature enabled.
266             return Ok(());
267         }
268 
269         let caller_ctx = SeContext::new("u:r:shell:s0")?;
270         let tee_services = [String::from("test_tee_service_does_not_exist")];
271         let ret = check_tee_service_permission(&caller_ctx, &tee_services);
272         assert!(ret.is_err());
273         Ok(())
274     }
275 }
276