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