1use crate::access_vector_cache::{FifoQueryCache, Locked, Query};
6use crate::policy::{AccessVector, AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE};
7use crate::security_server::SecurityServer;
8use crate::{ClassPermission, FsNodeClass, NullessByteStr, Permission, SecurityId};
9
10#[cfg(target_os = "fuchsia")]
11use fuchsia_inspect_contrib::profile_duration;
12
13use std::num::NonZeroU64;
14use std::sync::Weak;
15
16#[derive(Clone, Debug, PartialEq)]
18pub struct PermissionCheckResult {
19 pub permit: bool,
21
22 pub audit: bool,
27
28 pub todo_bug: Option<NonZeroU64>,
31}
32
33pub struct PermissionCheck<'a> {
37 security_server: &'a SecurityServer,
38 access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
39}
40
41impl<'a> PermissionCheck<'a> {
42 pub(crate) fn new(
43 security_server: &'a SecurityServer,
44 access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
45 ) -> Self {
46 Self { security_server, access_vector_cache }
47 }
48
49 pub fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
53 &self,
54 source_sid: SecurityId,
55 target_sid: SecurityId,
56 permission: P,
57 ) -> PermissionCheckResult {
58 has_permission(
59 self.security_server.is_enforcing(),
60 self.access_vector_cache,
61 self.security_server,
62 source_sid,
63 target_sid,
64 permission,
65 )
66 }
67
68 pub fn has_ioctl_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
81 &self,
82 source_sid: SecurityId,
83 target_sid: SecurityId,
84 permission: P,
85 ioctl: u16,
86 ) -> PermissionCheckResult {
87 has_ioctl_permission(
88 self.security_server.is_enforcing(),
89 self.access_vector_cache,
90 self.security_server,
91 source_sid,
92 target_sid,
93 permission,
94 ioctl,
95 )
96 }
97
98 pub fn security_server(&self) -> &SecurityServer {
101 self.security_server
102 }
103
104 pub fn compute_new_fs_node_sid(
109 &self,
110 source_sid: SecurityId,
111 target_sid: SecurityId,
112 fs_node_class: FsNodeClass,
113 fs_node_name: NullessByteStr<'_>,
114 ) -> Result<SecurityId, anyhow::Error> {
115 if !fs_node_name.as_bytes().is_empty() {
117 if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
118 source_sid,
119 target_sid,
120 fs_node_class,
121 fs_node_name,
122 ) {
123 return Ok(sid);
124 }
125 }
126 self.access_vector_cache.compute_new_fs_node_sid(source_sid, target_sid, fs_node_class)
127 }
128}
129
130fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
132 is_enforcing: bool,
133 query: &impl Query,
134 access_vector_computer: &impl AccessVectorComputer,
135 source_sid: SecurityId,
136 target_sid: SecurityId,
137 permission: P,
138) -> PermissionCheckResult {
139 #[cfg(target_os = "fuchsia")]
140 profile_duration!("libselinux.check_permission");
141 let target_class = permission.class();
142
143 let decision = query.compute_access_decision(source_sid, target_sid, target_class.into());
144
145 let mut result = if let Some(permission_access_vector) =
146 access_vector_computer.access_vector_from_permissions(&[permission])
147 {
148 let permit = permission_access_vector & decision.allow == permission_access_vector;
149 let audit = if permit {
150 permission_access_vector & decision.auditallow != AccessVector::NONE
151 } else {
152 permission_access_vector & decision.auditdeny != AccessVector::NONE
153 };
154 PermissionCheckResult { permit, audit, todo_bug: None }
155 } else {
156 PermissionCheckResult { permit: false, audit: true, todo_bug: None }
157 };
158
159 if !result.permit {
160 if !is_enforcing {
161 result.permit = true;
163 } else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
164 result.permit = true;
167 } else if decision.todo_bug.is_some() {
168 result.permit = true;
171 result.todo_bug = decision.todo_bug;
172 }
173 }
174
175 result
176}
177
178fn has_ioctl_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
179 is_enforcing: bool,
180 query: &impl Query,
181 access_vector_computer: &impl AccessVectorComputer,
182 source_sid: SecurityId,
183 target_sid: SecurityId,
184 permission: P,
185 ioctl: u16,
186) -> PermissionCheckResult {
187 let target_class = permission.class();
188
189 let permission_decision =
190 query.compute_access_decision(source_sid, target_sid, target_class.into());
191
192 let [ioctl_postfix, ioctl_prefix] = ioctl.to_le_bytes();
193 let xperm_decision = query.compute_ioctl_access_decision(
194 source_sid,
195 target_sid,
196 target_class.into(),
197 ioctl_prefix,
198 );
199
200 let permission_result = if let Some(permission_access_vector) =
201 access_vector_computer.access_vector_from_permissions(&[permission])
202 {
203 let permit =
204 permission_access_vector & permission_decision.allow == permission_access_vector;
205 let audit = if permit {
206 permission_access_vector & permission_decision.auditallow != AccessVector::NONE
207 } else {
208 permission_access_vector & permission_decision.auditdeny != AccessVector::NONE
209 };
210 println!("permission_access_vector is some; permit: {}, audit: {}", permit, audit);
211 PermissionCheckResult { permit, audit, todo_bug: None }
212 } else {
213 PermissionCheckResult { permit: false, audit: true, todo_bug: None }
214 };
215
216 let xperm_result = {
217 let permit = xperm_decision.allow.contains(ioctl_postfix);
218 let audit = if permit {
219 xperm_decision.auditallow.contains(ioctl_postfix)
220 } else {
221 xperm_decision.auditdeny.contains(ioctl_postfix)
222 };
223 println!("xperm result: permit: {}, audit: {}", permit, audit);
224 PermissionCheckResult { permit, audit, todo_bug: None }
225 };
226
227 let mut result = PermissionCheckResult {
228 permit: permission_result.permit && xperm_result.permit,
229 audit: permission_result.audit && xperm_result.audit,
230 todo_bug: None,
231 };
232
233 if !result.permit {
234 if !is_enforcing {
235 result.permit = true;
236 } else if permission_decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
237 result.permit = true;
238 } else if permission_decision.todo_bug.is_some() {
239 result.permit = true;
242 result.todo_bug = permission_decision.todo_bug;
243 }
244 }
245
246 result
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::access_vector_cache::DenyAll;
253 use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
254 use crate::policy::{AccessDecision, AccessVector, IoctlAccessDecision};
255 use crate::{
256 AbstractObjectClass, CommonFilePermission, CommonFsNodePermission, FileClass,
257 FilePermission, ProcessPermission,
258 };
259
260 use std::num::NonZeroU32;
261 use std::sync::atomic::{AtomicU32, Ordering};
262 use std::sync::LazyLock;
263
264 static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
266
267 fn unique_sid() -> SecurityId {
269 static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
270 SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
271 }
272
273 fn access_vector_from_permission<P: ClassPermission + Into<Permission> + 'static>(
274 permission: P,
275 ) -> AccessVector {
276 match permission.into() {
277 Permission::Process(ProcessPermission::Fork) => ACCESS_VECTOR_0001,
279 Permission::Process(ProcessPermission::Transition) => ACCESS_VECTOR_0010,
280 Permission::File(FilePermission::Common(CommonFilePermission::Common(
282 CommonFsNodePermission::Ioctl,
283 ))) => ACCESS_VECTOR_0001,
284 _ => AccessVector::NONE,
285 }
286 }
287
288 fn access_vector_from_permissions<
289 'a,
290 P: ClassPermission + Into<Permission> + Clone + 'static,
291 >(
292 permissions: &[P],
293 ) -> AccessVector {
294 let mut access_vector = AccessVector::NONE;
295 for permission in permissions {
296 access_vector |= access_vector_from_permission(permission.clone());
297 }
298 access_vector
299 }
300
301 #[derive(Default)]
302 pub struct DenyAllPermissions(DenyAll);
303
304 impl Query for DenyAllPermissions {
305 fn compute_access_decision(
306 &self,
307 source_sid: SecurityId,
308 target_sid: SecurityId,
309 target_class: AbstractObjectClass,
310 ) -> AccessDecision {
311 self.0.compute_access_decision(source_sid, target_sid, target_class)
312 }
313
314 fn compute_new_fs_node_sid(
315 &self,
316 _source_sid: SecurityId,
317 _target_sid: SecurityId,
318 _fs_node_class: FsNodeClass,
319 ) -> Result<SecurityId, anyhow::Error> {
320 unreachable!();
321 }
322
323 fn compute_new_fs_node_sid_with_name(
324 &self,
325 _source_sid: SecurityId,
326 _target_sid: SecurityId,
327 _fs_node_class: FsNodeClass,
328 _fs_node_name: NullessByteStr<'_>,
329 ) -> Option<SecurityId> {
330 unreachable!();
331 }
332
333 fn compute_ioctl_access_decision(
334 &self,
335 source_sid: SecurityId,
336 target_sid: SecurityId,
337 target_class: AbstractObjectClass,
338 ioctl_prefix: u8,
339 ) -> IoctlAccessDecision {
340 self.0.compute_ioctl_access_decision(source_sid, target_sid, target_class, ioctl_prefix)
341 }
342 }
343
344 impl AccessVectorComputer for DenyAllPermissions {
345 fn access_vector_from_permissions<
346 P: ClassPermission + Into<Permission> + Clone + 'static,
347 >(
348 &self,
349 permissions: &[P],
350 ) -> Option<AccessVector> {
351 Some(access_vector_from_permissions(permissions))
352 }
353 }
354
355 #[derive(Default)]
357 struct AllowAllPermissions;
358
359 impl Query for AllowAllPermissions {
360 fn compute_access_decision(
361 &self,
362 _source_sid: SecurityId,
363 _target_sid: SecurityId,
364 _target_class: AbstractObjectClass,
365 ) -> AccessDecision {
366 AccessDecision::allow(AccessVector::ALL)
367 }
368
369 fn compute_new_fs_node_sid(
370 &self,
371 _source_sid: SecurityId,
372 _target_sid: SecurityId,
373 _fs_node_class: FsNodeClass,
374 ) -> Result<SecurityId, anyhow::Error> {
375 unreachable!();
376 }
377
378 fn compute_new_fs_node_sid_with_name(
379 &self,
380 _source_sid: SecurityId,
381 _target_sid: SecurityId,
382 _fs_node_class: FsNodeClass,
383 _fs_node_name: NullessByteStr<'_>,
384 ) -> Option<SecurityId> {
385 unreachable!();
386 }
387
388 fn compute_ioctl_access_decision(
389 &self,
390 _source_sid: SecurityId,
391 _target_sid: SecurityId,
392 _target_class: AbstractObjectClass,
393 _ioctl_prefix: u8,
394 ) -> IoctlAccessDecision {
395 IoctlAccessDecision::ALLOW_ALL
396 }
397 }
398
399 impl AccessVectorComputer for AllowAllPermissions {
400 fn access_vector_from_permissions<
401 P: ClassPermission + Into<Permission> + Clone + 'static,
402 >(
403 &self,
404 permissions: &[P],
405 ) -> Option<AccessVector> {
406 Some(access_vector_from_permissions(permissions))
407 }
408 }
409
410 #[test]
411 fn has_permission_both() {
412 let deny_all: DenyAllPermissions = Default::default();
413 let allow_all: AllowAllPermissions = Default::default();
414
415 let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
418 for permission in &permissions {
419 assert_eq!(
421 PermissionCheckResult { permit: false, audit: true, todo_bug: None },
422 has_permission(
423 true,
424 &deny_all,
425 &deny_all,
426 *A_TEST_SID,
427 *A_TEST_SID,
428 permission.clone()
429 )
430 );
431 assert_eq!(
433 PermissionCheckResult { permit: true, audit: false, todo_bug: None },
434 has_permission(
435 true,
436 &allow_all,
437 &allow_all,
438 *A_TEST_SID,
439 *A_TEST_SID,
440 permission.clone()
441 )
442 );
443 }
444 }
445
446 #[test]
447 fn has_ioctl_permission_enforcing() {
448 let deny_all: DenyAllPermissions = Default::default();
449 let allow_all: AllowAllPermissions = Default::default();
450 let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
451
452 assert_eq!(
454 PermissionCheckResult { permit: false, audit: true, todo_bug: None },
455 has_ioctl_permission(
456 true,
457 &deny_all,
458 &deny_all,
459 *A_TEST_SID,
460 *A_TEST_SID,
461 permission.clone(),
462 0xabcd
463 )
464 );
465 assert_eq!(
467 PermissionCheckResult { permit: true, audit: false, todo_bug: None },
468 has_ioctl_permission(
469 true,
470 &allow_all,
471 &allow_all,
472 *A_TEST_SID,
473 *A_TEST_SID,
474 permission,
475 0xabcd
476 )
477 );
478 }
479
480 #[test]
481 fn has_ioctl_permission_not_enforcing() {
482 let deny_all: DenyAllPermissions = Default::default();
483 let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
484
485 assert_eq!(
488 PermissionCheckResult { permit: true, audit: true, todo_bug: None },
489 has_ioctl_permission(
490 false,
491 &deny_all,
492 &deny_all,
493 *A_TEST_SID,
494 *A_TEST_SID,
495 permission,
496 0xabcd
497 )
498 );
499 }
500}