1use crate::kernel::{ClassPermission, KernelAccessDecision, KernelClass, KernelPermission};
6use crate::permission_check::PermissionCheckResult;
7use crate::policy::{AccessVector, XpermsKind};
8use crate::{PolicySeqNo, SecurityId};
9use std::cell::Cell;
10use std::sync::atomic::{Ordering, fence};
11
12const SIMPLE_ALLOW: PermissionCheckResult =
14 PermissionCheckResult { granted: true, audit: false, permissive: false, todo_bug: None };
15
16const FD_USE_CACHE_SIZE: usize = 1 << 2;
21const ACCESS_CACHE_SIZE: usize = 1 << 3;
22const XPERM_CACHE_SIZE: usize = 1 << 1;
23
24#[derive(Clone, Copy, Debug)]
27struct LruState<const ENTRIES: usize>(u32);
28
29impl<const ENTRIES: usize> LruState<ENTRIES> {
30 fn new() -> Self {
31 const {
32 assert!(ENTRIES > 0 && ENTRIES <= 8);
33 }
34 let mask = if ENTRIES >= 8 { u32::MAX } else { (1_u32 << (ENTRIES * 4)) - 1 };
35 Self(0x76543210 & mask)
36 }
37
38 fn evict(&mut self) -> usize {
40 let shift = (ENTRIES - 1) * 4;
42 let evicted = (self.0 >> shift) & 0xF;
43 self.0 = (self.0 << 4) | evicted;
45 evicted as usize
46 }
47
48 fn touch_mru_idx(&mut self, hit_idx: usize) {
50 if hit_idx >= ENTRIES || hit_idx == 0 {
51 return;
52 }
53 let pos = hit_idx * 4;
54 let val = (self.0 >> pos) & 0xF; let lower_mask = (1_u32 << pos) - 1;
59 let lower_part = self.0 & lower_mask;
60 let shifted_lower = lower_part << 4;
61
62 let upper_mask = (!0_u32 << 4) << pos;
66 let upper_part = self.0 & upper_mask;
67
68 self.0 = upper_part | shifted_lower | val;
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75struct SidPair(u64);
76
77impl SidPair {
78 fn new(source: SecurityId, target: SecurityId) -> Self {
79 Self((source.0.get() as u64) << 32 | (target.0.get() as u64))
80 }
81 const NONE: Self = Self(0);
82}
83
84impl Default for SidPair {
85 fn default() -> Self {
86 Self::NONE
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
91struct XpermCacheKey {
92 sids: SidPair,
93 details: u64,
95}
96
97impl XpermCacheKey {
98 fn new(
99 source_sid: SecurityId,
100 target_sid: SecurityId,
101 kind: XpermsKind,
102 permission: &KernelPermission,
103 xperm: u16,
104 ) -> Self {
105 let kind_num = match kind {
106 XpermsKind::Ioctl => 0u64,
107 XpermsKind::Nlmsg => 1u64,
108 };
109 let class_num = permission.class() as u64;
110 let id_num = permission.id() as u64;
111 let xperm_num = xperm as u64;
112 let details = (kind_num << 40) | (class_num << 32) | (id_num << 16) | xperm_num;
113 Self { sids: SidPair::new(source_sid, target_sid), details }
114 }
115}
116
117#[derive(Debug)]
119pub struct PerThreadCache {
120 policy_seqno: Cell<PolicySeqNo>,
122 fd_use_cache: [Cell<SidPair>; FD_USE_CACHE_SIZE],
124 fd_use_lru: Cell<LruState<FD_USE_CACHE_SIZE>>,
125 access_cache_sid_idx: [Cell<SidPair>; ACCESS_CACHE_SIZE],
128 access_cache_class_idx: [Cell<KernelClass>; ACCESS_CACHE_SIZE],
129 access_cache_result: [Cell<(AccessVector, AccessVector)>; ACCESS_CACHE_SIZE],
130 access_lru: Cell<LruState<ACCESS_CACHE_SIZE>>,
131 xperm_cache: [Cell<XpermCacheKey>; XPERM_CACHE_SIZE],
133 xperm_lru: Cell<LruState<XPERM_CACHE_SIZE>>,
134}
135
136impl Default for PerThreadCache {
137 fn default() -> Self {
138 Self {
139 policy_seqno: Cell::new(PolicySeqNo::INITIAL),
140 fd_use_cache: std::array::from_fn(|_| Cell::new(SidPair::NONE)),
141 fd_use_lru: Cell::new(LruState::new()),
142 access_cache_sid_idx: std::array::from_fn(|_| Cell::new(SidPair::NONE)),
143 access_cache_class_idx: std::array::from_fn(|_| Cell::new(KernelClass::File)),
144 access_cache_result: std::array::from_fn(|_| {
145 Cell::new((AccessVector::NONE, AccessVector::NONE))
146 }),
147 access_lru: Cell::new(LruState::new()),
148 xperm_cache: std::array::from_fn(|_| Cell::new(XpermCacheKey::default())),
149 xperm_lru: Cell::new(LruState::new()),
150 }
151 }
152}
153
154impl PerThreadCache {
155 #[cold]
156 fn reset(&self) {
157 self.fd_use_cache.iter().for_each(|c| c.set(SidPair::NONE));
158 self.fd_use_lru.set(LruState::new());
159 self.access_cache_sid_idx.iter().for_each(|c| c.set(SidPair::NONE));
160 self.access_lru.set(LruState::new());
161 self.xperm_cache.iter().for_each(|c| c.set(XpermCacheKey::default()));
162 self.xperm_lru.set(LruState::new());
163 }
164
165 fn check_policy_version(&self, policy_seqno: PolicySeqNo) {
168 if self.policy_seqno.get() != policy_seqno {
169 assert!(policy_seqno > self.policy_seqno.get());
170 fence(Ordering::Acquire);
174 self.reset();
175 self.policy_seqno.set(policy_seqno);
176 }
177 }
178
179 #[inline]
181 pub(crate) fn lookup_fd_use<F>(
182 &self,
183 policy_seqno: PolicySeqNo,
184 source_sid: SecurityId,
185 target_sid: SecurityId,
186 compute: F,
187 ) -> PermissionCheckResult
188 where
189 F: FnOnce() -> PermissionCheckResult,
190 {
191 self.check_policy_version(policy_seqno);
192
193 let key = SidPair::new(source_sid, target_sid);
194 let mut lru = self.fd_use_lru.get();
195 let mut sequence = lru.0;
196 for hit_idx in 0..FD_USE_CACHE_SIZE {
197 let i = (sequence & 0xF) as usize % FD_USE_CACHE_SIZE;
198 if self.fd_use_cache[i].get() == key {
199 if hit_idx != 0 {
200 lru.touch_mru_idx(hit_idx);
201 self.fd_use_lru.set(lru);
202 }
203 return SIMPLE_ALLOW;
204 }
205 sequence >>= 4;
206 }
207 self.lookup_fd_use_miss(key, lru, compute)
208 }
209
210 #[inline(never)]
212 fn lookup_fd_use_miss<F>(
213 &self,
214 key: SidPair,
215 mut lru: LruState<FD_USE_CACHE_SIZE>,
216 compute: F,
217 ) -> PermissionCheckResult
218 where
219 F: FnOnce() -> PermissionCheckResult,
220 {
221 let result = compute();
222 if result == SIMPLE_ALLOW {
225 let evicted = lru.evict();
226 self.fd_use_lru.set(lru);
227 self.fd_use_cache[evicted % FD_USE_CACHE_SIZE].set(key);
228 }
229 result
230 }
231
232 #[inline]
234 pub(crate) fn check_xperm<F>(
235 &self,
236 policy_seqno: PolicySeqNo,
237 kind: XpermsKind,
238 source_sid: SecurityId,
239 target_sid: SecurityId,
240 permission: KernelPermission,
241 xperm: u16,
242 compute: F,
243 ) -> PermissionCheckResult
244 where
245 F: FnOnce() -> PermissionCheckResult,
246 {
247 self.check_policy_version(policy_seqno);
248 let key = XpermCacheKey::new(source_sid, target_sid, kind, &permission, xperm);
249 let mut lru = self.xperm_lru.get();
250 let mut sequence = lru.0;
251 for hit_idx in 0..XPERM_CACHE_SIZE {
252 let i = (sequence & 0xF) as usize % XPERM_CACHE_SIZE;
253 if self.xperm_cache[i].get() == key {
254 if hit_idx != 0 {
255 lru.touch_mru_idx(hit_idx);
256 self.xperm_lru.set(lru);
257 }
258 return SIMPLE_ALLOW;
259 }
260 sequence >>= 4;
261 }
262 self.check_xperm_miss(key, lru, compute)
263 }
264
265 #[inline(never)]
267 fn check_xperm_miss<F>(
268 &self,
269 key: XpermCacheKey,
270 mut lru: LruState<XPERM_CACHE_SIZE>,
271 compute: F,
272 ) -> PermissionCheckResult
273 where
274 F: FnOnce() -> PermissionCheckResult,
275 {
276 let result = compute();
277 if result == SIMPLE_ALLOW {
280 let evicted = lru.evict();
281 self.xperm_lru.set(lru);
282 self.xperm_cache[evicted % XPERM_CACHE_SIZE].set(key);
283 }
284 result
285 }
286
287 #[inline]
291 pub(crate) fn lookup_access_decision<F>(
292 &self,
293 policy_seqno: PolicySeqNo,
294 source_sid: SecurityId,
295 target_sid: SecurityId,
296 class: KernelClass,
297 compute: F,
298 ) -> KernelAccessDecision
299 where
300 F: FnOnce() -> KernelAccessDecision,
301 {
302 self.check_policy_version(policy_seqno);
303 let key = SidPair::new(source_sid, target_sid);
304 let mut lru = self.access_lru.get();
305 let mut sequence = lru.0;
306 for hit_idx in 0..ACCESS_CACHE_SIZE {
307 let i = (sequence & 0xF) as usize % ACCESS_CACHE_SIZE;
308 if key == self.access_cache_sid_idx[i].get()
309 && class == self.access_cache_class_idx[i].get()
310 {
311 if hit_idx != 0 {
312 lru.touch_mru_idx(hit_idx);
313 self.access_lru.set(lru);
314 }
315 let (allow, audit) = self.access_cache_result[i].get();
316 return KernelAccessDecision { allow, audit, flags: 0, todo_bug: None };
317 }
318 sequence >>= 4;
319 }
320 self.lookup_access_decision_miss(key, class, lru, compute)
321 }
322
323 #[inline(never)]
325 fn lookup_access_decision_miss<F>(
326 &self,
327 key: SidPair,
328 class: KernelClass,
329 mut lru: LruState<ACCESS_CACHE_SIZE>,
330 compute: F,
331 ) -> KernelAccessDecision
332 where
333 F: FnOnce() -> KernelAccessDecision,
334 {
335 let result = compute();
336
337 if result.todo_bug.is_none() && result.flags == 0 {
340 let evicted = lru.evict();
341 self.access_lru.set(lru);
342 let idx = evicted % ACCESS_CACHE_SIZE;
343 self.access_cache_sid_idx[idx].set(key);
344 self.access_cache_class_idx[idx].set(class);
345 self.access_cache_result[idx].set((result.allow, result.audit));
346 }
347
348 result
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::FilePermission;
356
357 #[test]
358 fn test_lru_state() {
359 let mut lru = LruState::<4>::new();
360 assert_eq!(lru.0, 0x3210);
361
362 lru.touch_mru_idx(2);
364 assert_eq!(lru.0, 0x3102);
365
366 assert_eq!(lru.evict(), 3);
368 assert_eq!(lru.0 & 0xFFFF, 0x1023);
369
370 lru.touch_mru_idx(2);
372 assert_eq!(lru.0 & 0xFFFF, 0x1230);
373
374 assert_eq!(lru.evict(), 1);
376 assert_eq!(lru.0 & 0xFFFF, 0x2301);
377 }
378
379 #[test]
380 fn test_touch_mru_idx_max_entries() {
381 let mut lru = LruState::<8>::new();
382 lru.touch_mru_idx(7);
384 assert_eq!(lru.0, 0x65432107);
388 }
389
390 #[test]
391 fn test_cache_lookup_hit() {
392 let cache = PerThreadCache::default();
393 let sid1 = SecurityId(1.try_into().unwrap());
394 let sid2 = SecurityId(2.try_into().unwrap());
395
396 let mut compute_called = false;
398 let result = cache.lookup_fd_use(PolicySeqNo::INITIAL, sid1, sid2, || {
399 compute_called = true;
400 PermissionCheckResult { granted: true, audit: false, permissive: false, todo_bug: None }
401 });
402 assert!(compute_called);
403 assert!(result.granted);
404
405 compute_called = false;
407 let result2 = cache.lookup_fd_use(PolicySeqNo::INITIAL, sid1, sid2, || {
408 compute_called = true;
409 PermissionCheckResult {
410 granted: false,
411 audit: false,
412 permissive: false,
413 todo_bug: None,
414 }
415 });
416 assert!(!compute_called);
417 assert!(result2.granted);
418 }
419
420 #[test]
421 fn test_fd_use_cache_invalidation_on_policy_change() {
422 let cache = PerThreadCache::default();
423 let sid1 = SecurityId(1.try_into().unwrap());
424 let sid2 = SecurityId(2.try_into().unwrap());
425
426 cache.lookup_fd_use(PolicySeqNo::INITIAL, sid1, sid2, || PermissionCheckResult {
428 granted: true,
429 audit: false,
430 permissive: false,
431 todo_bug: None,
432 });
433
434 let mut compute_called = false;
436 let result = cache.lookup_fd_use(PolicySeqNo::OTHER, sid1, sid2, || {
437 compute_called = true;
438 PermissionCheckResult {
439 granted: false,
440 audit: false,
441 permissive: false,
442 todo_bug: None,
443 }
444 });
445 assert!(compute_called);
446 assert!(!result.granted);
447 }
448
449 #[test]
450 fn test_access_cache_lookup() {
451 let cache = PerThreadCache::default();
452 let sid1 = SecurityId(1.try_into().unwrap());
453 let sid2 = SecurityId(2.try_into().unwrap());
454 let class = KernelClass::File;
455
456 let mut compute_called = false;
457 let result = cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
458 compute_called = true;
459 KernelAccessDecision {
460 allow: AccessVector::from(1),
461 audit: AccessVector::NONE,
462 flags: 0,
463 todo_bug: None,
464 }
465 });
466 assert!(compute_called);
467 assert_eq!(result.allow, AccessVector::from(1));
468
469 compute_called = false;
470 let result2 = cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
471 compute_called = true;
472 KernelAccessDecision {
473 allow: AccessVector::NONE,
474 audit: AccessVector::NONE,
475 flags: 0,
476 todo_bug: None,
477 }
478 });
479 assert!(!compute_called);
480 assert_eq!(result2.allow, AccessVector::from(1));
481 }
482
483 #[test]
484 fn test_access_cache_todo_uncached() {
485 let cache = PerThreadCache::default();
486 let sid1 = SecurityId(1.try_into().unwrap());
487 let sid2 = SecurityId(2.try_into().unwrap());
488 let class = KernelClass::File;
489
490 cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
491 KernelAccessDecision {
492 allow: AccessVector::from(1),
493 audit: AccessVector::NONE,
494 flags: 0,
495 todo_bug: Some(123.try_into().unwrap()),
496 }
497 });
498
499 let mut compute_called = false;
500 cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
501 compute_called = true;
502 KernelAccessDecision {
503 allow: AccessVector::NONE,
504 audit: AccessVector::NONE,
505 flags: 0,
506 todo_bug: None,
507 }
508 });
509 assert!(compute_called);
510 }
511
512 #[test]
513 fn test_access_cache_permissive_uncached() {
514 let cache = PerThreadCache::default();
515 let sid1 = SecurityId(1.try_into().unwrap());
516 let sid2 = SecurityId(2.try_into().unwrap());
517 let class = KernelClass::File;
518
519 cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
520 KernelAccessDecision {
521 allow: AccessVector::from(1),
522 audit: AccessVector::NONE,
523 flags: 1,
524 todo_bug: None,
525 }
526 });
527
528 let mut compute_called = false;
529 cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
530 compute_called = true;
531 KernelAccessDecision {
532 allow: AccessVector::NONE,
533 audit: AccessVector::NONE,
534 flags: 0,
535 todo_bug: None,
536 }
537 });
538 assert!(compute_called);
539 }
540
541 #[test]
542 fn test_xperm_cache_lookup() {
543 let cache = PerThreadCache::default();
544 let sid1 = SecurityId(1.try_into().unwrap());
545 let sid2 = SecurityId(2.try_into().unwrap());
546 let permission = KernelPermission::File(FilePermission::Ioctl);
547
548 let mut compute_called = false;
549 let result = cache.check_xperm(
550 PolicySeqNo::INITIAL,
551 XpermsKind::Ioctl,
552 sid1,
553 sid2,
554 permission.clone(),
555 1,
556 || {
557 compute_called = true;
558 PermissionCheckResult {
559 granted: true,
560 audit: false,
561 permissive: false,
562 todo_bug: None,
563 }
564 },
565 );
566 assert!(compute_called);
567 assert!(result.granted);
568
569 compute_called = false;
570 let result2 = cache.check_xperm(
571 PolicySeqNo::INITIAL,
572 XpermsKind::Ioctl,
573 sid1,
574 sid2,
575 permission,
576 1,
577 || {
578 compute_called = true;
579 PermissionCheckResult {
580 granted: false,
581 audit: false,
582 permissive: false,
583 todo_bug: None,
584 }
585 },
586 );
587 assert!(!compute_called);
588 assert!(result2.granted);
589 }
590
591 #[test]
592 fn test_access_cache_invalidation_on_policy_change() {
593 let cache = PerThreadCache::default();
594 let sid1 = SecurityId(1.try_into().unwrap());
595 let sid2 = SecurityId(2.try_into().unwrap());
596 let class = KernelClass::File;
597
598 cache.lookup_access_decision(PolicySeqNo::INITIAL, sid1, sid2, class, || {
599 KernelAccessDecision {
600 allow: AccessVector::from(1),
601 audit: AccessVector::NONE,
602 flags: 0,
603 todo_bug: None,
604 }
605 });
606
607 let mut compute_called = false;
608 let result = cache.lookup_access_decision(PolicySeqNo::OTHER, sid1, sid2, class, || {
609 compute_called = true;
610 KernelAccessDecision {
611 allow: AccessVector::NONE,
612 audit: AccessVector::NONE,
613 flags: 0,
614 todo_bug: None,
615 }
616 });
617 assert!(compute_called);
618 assert_eq!(result.allow, AccessVector::NONE);
619 }
620
621 #[test]
622 fn test_xperm_cache_invalidation_on_policy_change() {
623 let cache = PerThreadCache::default();
624 let sid1 = SecurityId(1.try_into().unwrap());
625 let sid2 = SecurityId(2.try_into().unwrap());
626 let permission = KernelPermission::File(FilePermission::Ioctl);
627
628 cache.check_xperm(
629 PolicySeqNo::INITIAL,
630 XpermsKind::Ioctl,
631 sid1,
632 sid2,
633 permission.clone(),
634 1,
635 || PermissionCheckResult {
636 granted: true,
637 audit: false,
638 permissive: false,
639 todo_bug: None,
640 },
641 );
642
643 let mut compute_called = false;
644 let result = cache.check_xperm(
645 PolicySeqNo::OTHER,
646 XpermsKind::Ioctl,
647 sid1,
648 sid2,
649 permission,
650 1,
651 || {
652 compute_called = true;
653 PermissionCheckResult {
654 granted: false,
655 audit: false,
656 permissive: false,
657 todo_bug: None,
658 }
659 },
660 );
661 assert!(compute_called);
662 assert!(!result.granted);
663 }
664}