1use std::collections::{BTreeMap, BTreeSet};
13use std::ops::Range;
14
15pub type ResourceId = u32;
17
18pub const WIDTH32: u32 = 4;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum SessionMode {
25 ReadOnly,
27 Mutating,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
34pub enum AccessClass {
35 ReadOnce,
37 Snapshot,
39 Poll,
41 Write,
43 Sequence,
45 Protocol,
47 Interrupt,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
53pub enum ResourceKind {
54 Mmio,
55 Gpio,
56 I2c,
57 Spi,
58 Interrupt,
59 Clock,
60 Reset,
61 Serial,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct MmioResource {
68 pub name: String,
70 pub kind: ResourceKind,
72 pub logical_size: u64,
74 pub mapped_size: u64,
77}
78
79impl MmioResource {
80 pub fn mmio(name: impl Into<String>, logical_size: u64, mapped_size: u64) -> Self {
81 Self { name: name.into(), kind: ResourceKind::Mmio, logical_size, mapped_size }
82 }
83
84 pub fn gpio(name: impl Into<String>) -> Self {
85 Self { name: name.into(), kind: ResourceKind::Gpio, logical_size: 1, mapped_size: 1 }
86 }
87
88 pub fn i2c(name: impl Into<String>) -> Self {
89 Self { name: name.into(), kind: ResourceKind::I2c, logical_size: 0, mapped_size: 0 }
90 }
91
92 pub fn spi(name: impl Into<String>) -> Self {
93 Self { name: name.into(), kind: ResourceKind::Spi, logical_size: 0, mapped_size: 0 }
94 }
95
96 pub fn interrupt(name: impl Into<String>) -> Self {
97 Self { name: name.into(), kind: ResourceKind::Interrupt, logical_size: 0, mapped_size: 0 }
98 }
99
100 pub fn clock(name: impl Into<String>) -> Self {
101 Self { name: name.into(), kind: ResourceKind::Clock, logical_size: 0, mapped_size: 0 }
102 }
103
104 pub fn reset(name: impl Into<String>) -> Self {
105 Self { name: name.into(), kind: ResourceKind::Reset, logical_size: 0, mapped_size: 0 }
106 }
107
108 pub fn serial(name: impl Into<String>) -> Self {
109 Self { name: name.into(), kind: ResourceKind::Serial, logical_size: 0, mapped_size: 0 }
110 }
111}
112
113#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
119pub struct WritableRegister {
120 pub offset: u64,
122 pub width: u32,
124 pub allow_mask: u32,
126 pub allow_rmw: bool,
128 pub require_precondition: bool,
130 pub precondition_mask: u32,
132 pub readback: bool,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138pub struct WritePrecondition {
139 pub expected: u32,
141 pub mask: u32,
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
147pub struct ProtocolCeiling {
148 pub allowed_methods: BTreeSet<String>,
150 pub max_transfer_size: u32,
152 pub allow_mutating: bool,
154}
155
156impl ProtocolCeiling {
157 pub fn default_for(kind: ResourceKind) -> Self {
158 let mut allowed_methods = BTreeSet::new();
159 match kind {
160 ResourceKind::Gpio => {
161 allowed_methods.insert("read".to_string());
162 allowed_methods.insert("write".to_string());
163 Self { allowed_methods, max_transfer_size: 1, allow_mutating: true }
164 }
165 ResourceKind::I2c => {
166 allowed_methods.insert("transfer".to_string());
167 Self { allowed_methods, max_transfer_size: 8192, allow_mutating: true }
168 }
169 ResourceKind::Spi => {
170 allowed_methods.insert("transmit".to_string());
171 Self { allowed_methods, max_transfer_size: 8192, allow_mutating: true }
172 }
173 ResourceKind::Interrupt => {
174 allowed_methods.insert("wait".to_string());
175 Self { allowed_methods, max_transfer_size: 0, allow_mutating: false }
176 }
177 ResourceKind::Clock => {
178 allowed_methods.insert("enable".to_string());
179 allowed_methods.insert("disable".to_string());
180 allowed_methods.insert("is_enabled".to_string());
181 allowed_methods.insert("set_rate".to_string());
182 allowed_methods.insert("query_rate".to_string());
183 allowed_methods.insert("get_rate".to_string());
184 Self { allowed_methods, max_transfer_size: 0, allow_mutating: true }
185 }
186 ResourceKind::Reset => {
187 allowed_methods.insert("assert".to_string());
188 allowed_methods.insert("deassert".to_string());
189 allowed_methods.insert("toggle".to_string());
190 allowed_methods.insert("status".to_string());
191 Self { allowed_methods, max_transfer_size: 0, allow_mutating: true }
192 }
193 ResourceKind::Serial => {
194 allowed_methods.insert("read".to_string());
195 allowed_methods.insert("write".to_string());
196 Self { allowed_methods, max_transfer_size: 8192, allow_mutating: true }
197 }
198 ResourceKind::Mmio => {
199 Self { allowed_methods, max_transfer_size: 0, allow_mutating: false }
200 }
201 }
202 }
203}
204
205#[derive(Clone, Debug, Default)]
208pub struct ResourceCeiling {
209 pub hard_denied: Vec<Range<u64>>,
211 pub allow_unknown_reads: bool,
214 pub allow_poll: bool,
217 pub writable_registers: Vec<WritableRegister>,
219 pub protocol: Option<ProtocolCeiling>,
221 pub allow_interrupt: bool,
223}
224
225impl ResourceCeiling {
226 pub fn get_writable_register(&self, offset: u64) -> Option<&WritableRegister> {
228 self.writable_registers.iter().find(|r| r.offset == offset)
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
235pub struct AccessRule {
236 pub resource: ResourceId,
238 pub offset: u64,
240 pub width: u32,
242 pub class: AccessClass,
244}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub enum Denial {
250 UnknownResource,
252 UnsupportedWidth,
254 UnsupportedAccessClass,
256 Misaligned,
258 OffsetOverflow,
260 OutOfLogicalBounds,
262 OutOfMappedBounds,
264 NotPermittedByCeiling,
266 HardDenied,
269 UnknownReadsNotPermitted,
272 PollNotPermitted,
274 NotInAllowlist,
276 LimitExceeded,
279 StaleIdentity,
282 MutationLeaseContention,
284 NotAccepting,
286 UnsupportedExpectation,
290 PreconditionFailed,
292 MissingPrecondition,
294 Timeout,
296 ReadOnlySession,
298 WriteNotPermitted,
300 UnsupportedMethod,
302 TransferTooLarge,
304}
305
306#[derive(Debug)]
312pub struct AccessPolicy {
313 mode: SessionMode,
314 resources: BTreeMap<ResourceId, MmioResource>,
315 ceiling: BTreeMap<ResourceId, ResourceCeiling>,
316 allowlist: BTreeSet<AccessRule>,
317}
318
319impl AccessPolicy {
320 pub fn new(
324 mode: SessionMode,
325 resources: BTreeMap<ResourceId, MmioResource>,
326 ceiling: BTreeMap<ResourceId, ResourceCeiling>,
327 allowlist: impl IntoIterator<Item = AccessRule>,
328 ) -> Result<Self, (AccessRule, Denial)> {
329 let allowlist: BTreeSet<AccessRule> = allowlist.into_iter().collect();
330 let policy = Self { mode, resources, ceiling, allowlist };
331 for rule in &policy.allowlist {
332 policy.validate_rule(rule).map_err(|denial| (*rule, denial))?;
333 }
334 Ok(policy)
335 }
336
337 pub fn mode(&self) -> SessionMode {
339 self.mode
340 }
341
342 pub fn resource_ids(&self) -> impl Iterator<Item = ResourceId> {
344 self.resources.keys().copied()
345 }
346
347 pub fn resource(&self, id: ResourceId) -> Option<&MmioResource> {
349 self.resources.get(&id)
350 }
351
352 pub fn check_read32(
358 &self,
359 resource: ResourceId,
360 offset: u64,
361 class: AccessClass,
362 ) -> Result<(), Denial> {
363 if !matches!(class, AccessClass::ReadOnce | AccessClass::Snapshot | AccessClass::Sequence) {
364 return Err(Denial::UnsupportedAccessClass);
365 }
366 self.validate_read(resource, offset, WIDTH32)?;
367 let rule = AccessRule { resource, offset, width: WIDTH32, class };
368 let rule_seq =
369 AccessRule { resource, offset, width: WIDTH32, class: AccessClass::Sequence };
370 if !self.allowlist.contains(&rule) && !self.allowlist.contains(&rule_seq) {
371 return Err(Denial::NotInAllowlist);
372 }
373 Ok(())
374 }
375
376 pub fn check_write32(
378 &self,
379 resource: ResourceId,
380 offset: u64,
381 write_mask: u32,
382 precondition: Option<&WritePrecondition>,
383 ) -> Result<&WritableRegister, Denial> {
384 if self.mode != SessionMode::Mutating {
385 return Err(Denial::ReadOnlySession);
386 }
387 if write_mask == 0 {
388 return Err(Denial::WriteNotPermitted);
389 }
390 self.validate_write_rule(resource, offset, WIDTH32)?;
391 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
392 let reg = ceiling.get_writable_register(offset).ok_or(Denial::WriteNotPermitted)?;
393 if (write_mask & !reg.allow_mask) != 0 {
394 return Err(Denial::WriteNotPermitted);
395 }
396 if write_mask != 0xFFFF_FFFF && !reg.allow_rmw {
397 return Err(Denial::WriteNotPermitted);
398 }
399 if reg.require_precondition && precondition.is_none() {
400 return Err(Denial::MissingPrecondition);
401 }
402 if let Some(pre) = precondition {
403 if !reg.allow_rmw {
404 return Err(Denial::WriteNotPermitted);
405 }
406 if pre.mask == 0 || (pre.mask & !reg.precondition_mask) != 0 {
407 return Err(Denial::WriteNotPermitted);
408 }
409 }
410 let rule_write = AccessRule { resource, offset, width: WIDTH32, class: AccessClass::Write };
411 let rule_seq =
412 AccessRule { resource, offset, width: WIDTH32, class: AccessClass::Sequence };
413 if !self.allowlist.contains(&rule_write) && !self.allowlist.contains(&rule_seq) {
414 return Err(Denial::NotInAllowlist);
415 }
416 Ok(reg)
417 }
418
419 pub fn check_poll32(
421 &self,
422 resource: ResourceId,
423 offset: u64,
424 mask: u32,
425 interval_ns: i64,
426 timeout_ns: i64,
427 ) -> Result<(), Denial> {
428 if mask == 0 {
429 return Err(Denial::Misaligned);
430 }
431 if interval_ns < 0 || timeout_ns < 0 {
432 return Err(Denial::LimitExceeded);
433 }
434 self.validate_read(resource, offset, WIDTH32)?;
435 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
436 if !ceiling.allow_poll {
437 return Err(Denial::PollNotPermitted);
438 }
439 let rule_poll = AccessRule { resource, offset, width: WIDTH32, class: AccessClass::Poll };
440 let rule_seq =
441 AccessRule { resource, offset, width: WIDTH32, class: AccessClass::Sequence };
442 if !self.allowlist.contains(&rule_poll) && !self.allowlist.contains(&rule_seq) {
443 return Err(Denial::NotInAllowlist);
444 }
445 Ok(())
446 }
447
448 fn validate_rule(&self, rule: &AccessRule) -> Result<(), Denial> {
450 match rule.class {
451 AccessClass::ReadOnce | AccessClass::Snapshot => {
452 self.validate_read(rule.resource, rule.offset, rule.width)
453 }
454 AccessClass::Poll => {
455 self.validate_read(rule.resource, rule.offset, rule.width)?;
456 let ceiling =
457 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
458 if !ceiling.allow_poll {
459 return Err(Denial::PollNotPermitted);
460 }
461 Ok(())
462 }
463 AccessClass::Write => {
464 if self.mode != SessionMode::Mutating {
465 return Err(Denial::ReadOnlySession);
466 }
467 self.validate_write_rule(rule.resource, rule.offset, rule.width)?;
468 let ceiling =
469 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
470 if ceiling.get_writable_register(rule.offset).is_none() {
471 return Err(Denial::WriteNotPermitted);
472 }
473 Ok(())
474 }
475 AccessClass::Sequence => {
476 let desc = self.resources.get(&rule.resource).ok_or(Denial::UnknownResource)?;
477 if desc.kind != ResourceKind::Mmio {
478 let ceiling =
479 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
480 if ceiling.protocol.is_none() {
481 return Err(Denial::NotPermittedByCeiling);
482 }
483 return Ok(());
484 }
485 if rule.width != WIDTH32 {
486 return Err(Denial::UnsupportedWidth);
487 }
488 if rule.offset % u64::from(rule.width) != 0 {
489 return Err(Denial::Misaligned);
490 }
491 let end =
492 rule.offset.checked_add(u64::from(rule.width)).ok_or(Denial::OffsetOverflow)?;
493 if end > desc.logical_size {
494 return Err(Denial::OutOfLogicalBounds);
495 }
496 if end > desc.mapped_size {
497 return Err(Denial::OutOfMappedBounds);
498 }
499 let ceiling =
500 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
501 if ceiling
502 .hard_denied
503 .iter()
504 .any(|range| range.start < end && rule.offset < range.end)
505 {
506 return Err(Denial::HardDenied);
507 }
508 Ok(())
509 }
510 AccessClass::Protocol => {
511 let desc = self.resources.get(&rule.resource).ok_or(Denial::UnknownResource)?;
512 if desc.kind == ResourceKind::Mmio {
513 return Err(Denial::UnsupportedAccessClass);
514 }
515 let ceiling =
516 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
517 if ceiling.protocol.is_none() {
518 return Err(Denial::NotPermittedByCeiling);
519 }
520 Ok(())
521 }
522 AccessClass::Interrupt => {
523 let desc = self.resources.get(&rule.resource).ok_or(Denial::UnknownResource)?;
524 if desc.kind != ResourceKind::Interrupt {
525 return Err(Denial::UnsupportedAccessClass);
526 }
527 let ceiling =
528 self.ceiling.get(&rule.resource).ok_or(Denial::NotPermittedByCeiling)?;
529 if !ceiling.allow_interrupt {
530 return Err(Denial::NotPermittedByCeiling);
531 }
532 Ok(())
533 }
534 }
535 }
536
537 pub fn check_interrupt(&self, resource: ResourceId) -> Result<(), Denial> {
539 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
540 if desc.kind != ResourceKind::Interrupt {
541 return Err(Denial::UnknownResource);
542 }
543 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
544 if !ceiling.allow_interrupt {
545 return Err(Denial::NotPermittedByCeiling);
546 }
547 let rule = AccessRule { resource, offset: 0, width: 0, class: AccessClass::Interrupt };
548 if !self.allowlist.contains(&rule) {
549 return Err(Denial::NotInAllowlist);
550 }
551 Ok(())
552 }
553
554 pub fn check_gpio_read(&self, resource: ResourceId) -> Result<(), Denial> {
556 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
557 if desc.kind != ResourceKind::Gpio {
558 return Err(Denial::UnknownResource);
559 }
560 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
561 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
562 if !proto.allowed_methods.contains("read") {
563 return Err(Denial::UnsupportedMethod);
564 }
565 if !self.is_protocol_allowed(resource, false) {
566 return Err(Denial::NotInAllowlist);
567 }
568 Ok(())
569 }
570
571 pub fn check_gpio_write(&self, resource: ResourceId) -> Result<(), Denial> {
573 if self.mode != SessionMode::Mutating {
574 return Err(Denial::ReadOnlySession);
575 }
576 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
577 if desc.kind != ResourceKind::Gpio {
578 return Err(Denial::UnknownResource);
579 }
580 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
581 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
582 if !proto.allow_mutating || !proto.allowed_methods.contains("write") {
583 return Err(Denial::WriteNotPermitted);
584 }
585 if !self.is_protocol_allowed(resource, true) {
586 return Err(Denial::NotInAllowlist);
587 }
588 Ok(())
589 }
590
591 pub fn check_i2c_transfer(
593 &self,
594 resource: ResourceId,
595 write_len: usize,
596 read_len: usize,
597 ) -> Result<(), Denial> {
598 let is_mutating = write_len > 0;
599 if is_mutating && self.mode != SessionMode::Mutating {
600 return Err(Denial::ReadOnlySession);
601 }
602 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
603 if desc.kind != ResourceKind::I2c {
604 return Err(Denial::UnknownResource);
605 }
606 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
607 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
608 if !proto.allowed_methods.contains("transfer") {
609 return Err(Denial::UnsupportedMethod);
610 }
611 if is_mutating && !proto.allow_mutating {
612 return Err(Denial::WriteNotPermitted);
613 }
614 let total_len = write_len.saturating_add(read_len);
615 if total_len as u32 > proto.max_transfer_size {
616 return Err(Denial::TransferTooLarge);
617 }
618 if !self.is_protocol_allowed(resource, is_mutating) {
619 return Err(Denial::NotInAllowlist);
620 }
621 Ok(())
622 }
623
624 pub fn check_spi_transmit(&self, resource: ResourceId, tx_len: usize) -> Result<(), Denial> {
626 if self.mode != SessionMode::Mutating {
627 return Err(Denial::ReadOnlySession);
628 }
629 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
630 if desc.kind != ResourceKind::Spi {
631 return Err(Denial::UnknownResource);
632 }
633 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
634 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
635 if !proto.allowed_methods.contains("transmit") {
636 return Err(Denial::UnsupportedMethod);
637 }
638 if !proto.allow_mutating {
639 return Err(Denial::WriteNotPermitted);
640 }
641 if tx_len as u32 > proto.max_transfer_size {
642 return Err(Denial::TransferTooLarge);
643 }
644 if !self.is_protocol_allowed(resource, true) {
645 return Err(Denial::NotInAllowlist);
646 }
647 Ok(())
648 }
649
650 pub fn check_clock_enable(&self, resource: ResourceId) -> Result<(), Denial> {
652 self.check_clock_op(resource, "enable", true)
653 }
654
655 pub fn check_clock_disable(&self, resource: ResourceId) -> Result<(), Denial> {
657 self.check_clock_op(resource, "disable", true)
658 }
659
660 pub fn check_clock_is_enabled(&self, resource: ResourceId) -> Result<(), Denial> {
662 self.check_clock_op(resource, "is_enabled", false)
663 }
664
665 pub fn check_clock_set_rate(&self, resource: ResourceId) -> Result<(), Denial> {
667 self.check_clock_op(resource, "set_rate", true)
668 }
669
670 pub fn check_clock_query_rate(&self, resource: ResourceId) -> Result<(), Denial> {
672 self.check_clock_op(resource, "query_rate", false)
673 }
674
675 pub fn check_clock_get_rate(&self, resource: ResourceId) -> Result<(), Denial> {
677 self.check_clock_op(resource, "get_rate", false)
678 }
679
680 fn check_clock_op(
681 &self,
682 resource: ResourceId,
683 method: &str,
684 is_mutating: bool,
685 ) -> Result<(), Denial> {
686 if is_mutating && self.mode != SessionMode::Mutating {
687 return Err(Denial::ReadOnlySession);
688 }
689 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
690 if desc.kind != ResourceKind::Clock {
691 return Err(Denial::UnknownResource);
692 }
693 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
694 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
695 if !proto.allowed_methods.contains(method) {
696 return Err(Denial::UnsupportedMethod);
697 }
698 if is_mutating && !proto.allow_mutating {
699 return Err(Denial::WriteNotPermitted);
700 }
701 if !self.is_protocol_allowed(resource, is_mutating) {
702 return Err(Denial::NotInAllowlist);
703 }
704 Ok(())
705 }
706
707 pub fn check_reset_assert(&self, resource: ResourceId) -> Result<(), Denial> {
709 self.check_reset_op(resource, "assert", true)
710 }
711
712 pub fn check_reset_deassert(&self, resource: ResourceId) -> Result<(), Denial> {
714 self.check_reset_op(resource, "deassert", true)
715 }
716
717 pub fn check_reset_toggle(&self, resource: ResourceId) -> Result<(), Denial> {
719 self.check_reset_op(resource, "toggle", true)
720 }
721
722 pub fn check_reset_status(&self, resource: ResourceId) -> Result<(), Denial> {
724 self.check_reset_op(resource, "status", false)
725 }
726
727 fn check_reset_op(
728 &self,
729 resource: ResourceId,
730 method: &str,
731 is_mutating: bool,
732 ) -> Result<(), Denial> {
733 if is_mutating && self.mode != SessionMode::Mutating {
734 return Err(Denial::ReadOnlySession);
735 }
736 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
737 if desc.kind != ResourceKind::Reset {
738 return Err(Denial::UnknownResource);
739 }
740 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
741 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
742 if !proto.allowed_methods.contains(method) {
743 return Err(Denial::UnsupportedMethod);
744 }
745 if is_mutating && !proto.allow_mutating {
746 return Err(Denial::WriteNotPermitted);
747 }
748 if !self.is_protocol_allowed(resource, is_mutating) {
749 return Err(Denial::NotInAllowlist);
750 }
751 Ok(())
752 }
753
754 pub fn check_serial_read(&self, resource: ResourceId) -> Result<(), Denial> {
756 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
757 if desc.kind != ResourceKind::Serial {
758 return Err(Denial::UnknownResource);
759 }
760 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
761 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
762 if !proto.allowed_methods.contains("read") {
763 return Err(Denial::UnsupportedMethod);
764 }
765 if !self.is_protocol_allowed(resource, false) {
766 return Err(Denial::NotInAllowlist);
767 }
768 Ok(())
769 }
770
771 pub fn check_serial_write(&self, resource: ResourceId, tx_len: usize) -> Result<(), Denial> {
773 if self.mode != SessionMode::Mutating {
774 return Err(Denial::ReadOnlySession);
775 }
776 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
777 if desc.kind != ResourceKind::Serial {
778 return Err(Denial::UnknownResource);
779 }
780 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
781 let proto = ceiling.protocol.as_ref().ok_or(Denial::NotPermittedByCeiling)?;
782 if !proto.allowed_methods.contains("write") {
783 return Err(Denial::UnsupportedMethod);
784 }
785 if !proto.allow_mutating {
786 return Err(Denial::WriteNotPermitted);
787 }
788 if tx_len as u32 > proto.max_transfer_size {
789 return Err(Denial::TransferTooLarge);
790 }
791 if !self.is_protocol_allowed(resource, true) {
792 return Err(Denial::NotInAllowlist);
793 }
794 Ok(())
795 }
796
797 fn is_protocol_allowed(&self, resource: ResourceId, is_mutating: bool) -> bool {
798 self.allowlist.iter().any(|r| {
799 r.resource == resource
800 && (r.class == AccessClass::Protocol
801 || r.class == AccessClass::Sequence
802 || (is_mutating && r.class == AccessClass::Write)
803 || (!is_mutating
804 && (r.class == AccessClass::ReadOnce || r.class == AccessClass::Snapshot)))
805 })
806 }
807
808 fn validate_read(&self, resource: ResourceId, offset: u64, width: u32) -> Result<(), Denial> {
811 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
812 if width != WIDTH32 {
813 return Err(Denial::UnsupportedWidth);
814 }
815 if offset % u64::from(width) != 0 {
816 return Err(Denial::Misaligned);
817 }
818 let end = offset.checked_add(u64::from(width)).ok_or(Denial::OffsetOverflow)?;
819 if end > desc.logical_size {
820 return Err(Denial::OutOfLogicalBounds);
821 }
822 if end > desc.mapped_size {
823 return Err(Denial::OutOfMappedBounds);
824 }
825 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
826 if ceiling.hard_denied.iter().any(|range| range.start < end && offset < range.end) {
827 return Err(Denial::HardDenied);
828 }
829 if !ceiling.allow_unknown_reads {
830 return Err(Denial::UnknownReadsNotPermitted);
831 }
832 Ok(())
833 }
834
835 fn validate_write_rule(
837 &self,
838 resource: ResourceId,
839 offset: u64,
840 width: u32,
841 ) -> Result<(), Denial> {
842 let desc = self.resources.get(&resource).ok_or(Denial::UnknownResource)?;
843 if width != WIDTH32 {
844 return Err(Denial::UnsupportedWidth);
845 }
846 if offset % u64::from(width) != 0 {
847 return Err(Denial::Misaligned);
848 }
849 let end = offset.checked_add(u64::from(width)).ok_or(Denial::OffsetOverflow)?;
850 if end > desc.logical_size {
851 return Err(Denial::OutOfLogicalBounds);
852 }
853 if end > desc.mapped_size {
854 return Err(Denial::OutOfMappedBounds);
855 }
856 let ceiling = self.ceiling.get(&resource).ok_or(Denial::NotPermittedByCeiling)?;
857 if ceiling.hard_denied.iter().any(|range| range.start < end && offset < range.end) {
858 return Err(Denial::HardDenied);
859 }
860 let reg = ceiling.get_writable_register(offset).ok_or(Denial::WriteNotPermitted)?;
861 if reg.width != width {
862 return Err(Denial::UnsupportedWidth);
863 }
864 Ok(())
865 }
866}
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871
872 const CTRL: ResourceId = 1;
873 const LOCKED: ResourceId = 2;
874 const NO_CEILING: ResourceId = 3;
875 const SHORT_MAP: ResourceId = 4;
876
877 fn resources() -> BTreeMap<ResourceId, MmioResource> {
878 BTreeMap::from([
879 (
880 CTRL,
881 MmioResource {
882 name: "ctrl".to_string(),
883 kind: ResourceKind::Mmio,
884 logical_size: 0x100,
885 mapped_size: 0x1000,
886 },
887 ),
888 (
889 LOCKED,
890 MmioResource {
891 name: "locked".to_string(),
892 kind: ResourceKind::Mmio,
893 logical_size: 0x100,
894 mapped_size: 0x100,
895 },
896 ),
897 (
898 NO_CEILING,
899 MmioResource {
900 name: "no-ceiling".to_string(),
901 kind: ResourceKind::Mmio,
902 logical_size: 0x100,
903 mapped_size: 0x100,
904 },
905 ),
906 (
907 SHORT_MAP,
908 MmioResource {
909 name: "short-map".to_string(),
910 kind: ResourceKind::Mmio,
911 logical_size: 0x2000,
912 mapped_size: 0x10,
913 },
914 ),
915 ])
916 }
917
918 fn ceiling() -> BTreeMap<ResourceId, ResourceCeiling> {
919 BTreeMap::from([
920 (
921 CTRL,
922 ResourceCeiling {
923 hard_denied: vec![0x40..0x44],
924 allow_unknown_reads: true,
925 allow_poll: true,
926 writable_registers: vec![
927 WritableRegister {
928 offset: 0x20,
929 width: WIDTH32,
930 allow_mask: 0x0000FFFF,
931 allow_rmw: true,
932 require_precondition: false,
933 precondition_mask: 0,
934 readback: false,
935 },
936 WritableRegister {
937 offset: 0x24,
938 width: WIDTH32,
939 allow_mask: 0xFFFFFFFF,
940 allow_rmw: true,
941 require_precondition: true,
942 precondition_mask: 0x00000001,
943 readback: true,
944 },
945 ],
946 protocol: None,
947 allow_interrupt: false,
948 },
949 ),
950 (
951 LOCKED,
952 ResourceCeiling {
953 hard_denied: vec![],
954 allow_unknown_reads: false,
955 allow_poll: false,
956 writable_registers: vec![],
957 protocol: None,
958 allow_interrupt: false,
959 },
960 ),
961 (
962 SHORT_MAP,
963 ResourceCeiling {
964 hard_denied: vec![],
965 allow_unknown_reads: true,
966 allow_poll: false,
967 writable_registers: vec![],
968 protocol: None,
969 allow_interrupt: false,
970 },
971 ),
972 ])
973 }
974
975 fn read_rule(resource: ResourceId, offset: u64) -> AccessRule {
976 AccessRule { resource, offset, width: WIDTH32, class: AccessClass::ReadOnce }
977 }
978
979 fn policy(rules: &[AccessRule]) -> AccessPolicy {
980 AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), rules.iter().copied())
981 .unwrap()
982 }
983
984 #[test]
985 fn exact_read_allowed() {
986 let policy = policy(&[read_rule(CTRL, 0x3c)]);
987 assert_eq!(policy.check_read32(CTRL, 0x3c, AccessClass::ReadOnce), Ok(()));
988 }
989
990 #[test]
991 fn nearby_offset_not_authorized() {
992 let policy = policy(&[read_rule(CTRL, 0x3c)]);
993 assert_eq!(
994 policy.check_read32(CTRL, 0x38, AccessClass::ReadOnce),
995 Err(Denial::NotInAllowlist)
996 );
997 }
998
999 #[test]
1000 fn access_classes_are_distinct() {
1001 let policy = policy(&[read_rule(CTRL, 0x3c)]);
1002 assert_eq!(
1003 policy.check_read32(CTRL, 0x3c, AccessClass::Snapshot),
1004 Err(Denial::NotInAllowlist)
1005 );
1006 assert_eq!(
1007 policy.check_read32(CTRL, 0x3c, AccessClass::Poll),
1008 Err(Denial::UnsupportedAccessClass)
1009 );
1010 assert_eq!(
1011 policy.check_read32(CTRL, 0x3c, AccessClass::Write),
1012 Err(Denial::UnsupportedAccessClass)
1013 );
1014 }
1015
1016 #[test]
1017 fn unknown_resource_denied() {
1018 let policy = policy(&[]);
1019 assert_eq!(
1020 policy.check_read32(99, 0x0, AccessClass::ReadOnce),
1021 Err(Denial::UnknownResource)
1022 );
1023 }
1024
1025 #[test]
1026 fn misaligned_denied() {
1027 let policy = policy(&[read_rule(CTRL, 0x3c)]);
1028 assert_eq!(policy.check_read32(CTRL, 0x3e, AccessClass::ReadOnce), Err(Denial::Misaligned));
1029 }
1030
1031 #[test]
1032 fn offset_overflow_denied() {
1033 let policy = policy(&[]);
1034 assert_eq!(
1037 policy.check_read32(CTRL, u64::MAX - 3, AccessClass::ReadOnce),
1038 Err(Denial::OffsetOverflow)
1039 );
1040 }
1041
1042 #[test]
1043 fn logical_bounds_enforced() {
1044 let policy = policy(&[read_rule(CTRL, 0xfc)]);
1045 assert_eq!(policy.check_read32(CTRL, 0xfc, AccessClass::ReadOnce), Ok(()));
1046 assert_eq!(
1047 policy.check_read32(CTRL, 0x100, AccessClass::ReadOnce),
1048 Err(Denial::OutOfLogicalBounds)
1049 );
1050 }
1051
1052 #[test]
1053 fn mapped_bounds_enforced() {
1054 let policy = policy(&[read_rule(SHORT_MAP, 0xc)]);
1055 assert_eq!(policy.check_read32(SHORT_MAP, 0xc, AccessClass::ReadOnce), Ok(()));
1056 assert_eq!(
1057 policy.check_read32(SHORT_MAP, 0x10, AccessClass::ReadOnce),
1058 Err(Denial::OutOfMappedBounds)
1059 );
1060 }
1061
1062 #[test]
1063 fn hard_denied_wins_over_allowlist() {
1064 let denied = AccessPolicy::new(
1066 SessionMode::ReadOnly,
1067 resources(),
1068 ceiling(),
1069 [read_rule(CTRL, 0x40)],
1070 );
1071 assert_eq!(denied.unwrap_err(), (read_rule(CTRL, 0x40), Denial::HardDenied));
1072 let policy = policy(&[]);
1074 assert_eq!(policy.check_read32(CTRL, 0x40, AccessClass::ReadOnce), Err(Denial::HardDenied));
1075 }
1076
1077 #[test]
1078 fn read_adjacent_to_hard_denied_range_allowed() {
1079 let policy = policy(&[read_rule(CTRL, 0x44)]);
1080 assert_eq!(policy.check_read32(CTRL, 0x44, AccessClass::ReadOnce), Ok(()));
1081 }
1082
1083 #[test]
1084 fn unknown_reads_disabled_by_ceiling() {
1085 let denied = AccessPolicy::new(
1086 SessionMode::ReadOnly,
1087 resources(),
1088 ceiling(),
1089 [read_rule(LOCKED, 0x0)],
1090 );
1091 assert_eq!(denied.unwrap_err(), (read_rule(LOCKED, 0x0), Denial::UnknownReadsNotPermitted));
1092 }
1093
1094 #[test]
1095 fn missing_ceiling_entry_permits_nothing() {
1096 let policy = policy(&[]);
1097 assert_eq!(
1098 policy.check_read32(NO_CEILING, 0x0, AccessClass::ReadOnce),
1099 Err(Denial::NotPermittedByCeiling)
1100 );
1101 }
1102
1103 #[test]
1104 fn allowlist_prevalidation_checks_poll_permission() {
1105 let rule_poll_allowed =
1107 AccessRule { resource: CTRL, offset: 0x0, width: WIDTH32, class: AccessClass::Poll };
1108 assert!(
1109 AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), [rule_poll_allowed])
1110 .is_ok()
1111 );
1112
1113 let rule_poll_denied = AccessRule {
1115 resource: SHORT_MAP,
1116 offset: 0x0,
1117 width: WIDTH32,
1118 class: AccessClass::Poll,
1119 };
1120 let denied =
1121 AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), [rule_poll_denied]);
1122 assert_eq!(denied.unwrap_err(), (rule_poll_denied, Denial::PollNotPermitted));
1123 }
1124
1125 #[test]
1126 fn allowlist_prevalidation_rejects_write_in_read_only_session() {
1127 let rule =
1128 AccessRule { resource: CTRL, offset: 0x20, width: WIDTH32, class: AccessClass::Write };
1129 let denied = AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), [rule]);
1130 assert_eq!(denied.unwrap_err(), (rule, Denial::ReadOnlySession));
1131 }
1132
1133 #[test]
1134 fn allowlist_prevalidation_accepts_valid_write_in_mutating_session() {
1135 let rule =
1136 AccessRule { resource: CTRL, offset: 0x20, width: WIDTH32, class: AccessClass::Write };
1137 let policy = AccessPolicy::new(SessionMode::Mutating, resources(), ceiling(), [rule]);
1138 assert!(policy.is_ok());
1139 }
1140
1141 #[test]
1142 fn write_checks_enforce_session_mode_mask_and_preconditions() {
1143 let write_rule =
1144 AccessRule { resource: CTRL, offset: 0x20, width: WIDTH32, class: AccessClass::Write };
1145 let write_rule_pre =
1146 AccessRule { resource: CTRL, offset: 0x24, width: WIDTH32, class: AccessClass::Write };
1147 let policy = AccessPolicy::new(
1148 SessionMode::Mutating,
1149 resources(),
1150 ceiling(),
1151 [write_rule, write_rule_pre],
1152 )
1153 .unwrap();
1154
1155 let reg = policy.check_write32(CTRL, 0x20, 0x000000FF, None).unwrap();
1157 assert_eq!(reg.offset, 0x20);
1158
1159 assert_eq!(
1161 policy.check_write32(CTRL, 0x20, 0x00010000, None),
1162 Err(Denial::WriteNotPermitted)
1163 );
1164
1165 assert_eq!(policy.check_write32(CTRL, 0x20, 0, None), Err(Denial::WriteNotPermitted));
1167
1168 assert_eq!(
1170 policy.check_write32(CTRL, 0x24, 0xFFFFFFFF, None),
1171 Err(Denial::MissingPrecondition)
1172 );
1173
1174 let bad_pre = WritePrecondition { expected: 1, mask: 0x2 };
1176 assert_eq!(
1177 policy.check_write32(CTRL, 0x24, 0xFFFFFFFF, Some(&bad_pre)),
1178 Err(Denial::WriteNotPermitted)
1179 );
1180
1181 let good_pre = WritePrecondition { expected: 1, mask: 0x1 };
1183 assert!(policy.check_write32(CTRL, 0x24, 0xFFFFFFFF, Some(&good_pre)).is_ok());
1184 }
1185
1186 #[test]
1187 fn allowlist_prevalidation_rejects_bad_width() {
1188 let rule =
1189 AccessRule { resource: CTRL, offset: 0x0, width: 8, class: AccessClass::ReadOnce };
1190 let denied = AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), [rule]);
1191 assert_eq!(denied.unwrap_err(), (rule, Denial::UnsupportedWidth));
1192 }
1193
1194 #[test]
1195 fn allowlist_prevalidation_rejects_out_of_bounds() {
1196 let rule = read_rule(CTRL, 0x200);
1197 let denied = AccessPolicy::new(SessionMode::ReadOnly, resources(), ceiling(), [rule]);
1198 assert_eq!(denied.unwrap_err(), (rule, Denial::OutOfLogicalBounds));
1199 }
1200
1201 #[test]
1202 fn protocol_policy_gpio_and_i2c_and_spi() {
1203 const GPIO_ID: ResourceId = 10;
1204 const I2C_ID: ResourceId = 11;
1205 const SPI_ID: ResourceId = 12;
1206
1207 let mut res = BTreeMap::new();
1208 res.insert(GPIO_ID, MmioResource::gpio("gpio-a"));
1209 res.insert(I2C_ID, MmioResource::i2c("i2c-sensor"));
1210 res.insert(SPI_ID, MmioResource::spi("spi-flash"));
1211
1212 let mut ceil = BTreeMap::new();
1213 let mut gpio_ceil = ResourceCeiling::default();
1214 gpio_ceil.protocol = Some(ProtocolCeiling::default_for(ResourceKind::Gpio));
1215 ceil.insert(GPIO_ID, gpio_ceil);
1216
1217 let mut i2c_ceil = ResourceCeiling::default();
1218 i2c_ceil.protocol = Some(ProtocolCeiling::default_for(ResourceKind::I2c));
1219 ceil.insert(I2C_ID, i2c_ceil);
1220
1221 let mut spi_ceil = ResourceCeiling::default();
1222 spi_ceil.protocol = Some(ProtocolCeiling::default_for(ResourceKind::Spi));
1223 ceil.insert(SPI_ID, spi_ceil);
1224
1225 let proto_rule =
1226 |r| AccessRule { resource: r, offset: 0, width: 0, class: AccessClass::Protocol };
1227
1228 let ro_policy = AccessPolicy::new(
1230 SessionMode::ReadOnly,
1231 res.clone(),
1232 ceil.clone(),
1233 [proto_rule(GPIO_ID), proto_rule(I2C_ID), proto_rule(SPI_ID)],
1234 )
1235 .expect("policy creation ok");
1236
1237 assert!(ro_policy.check_gpio_read(GPIO_ID).is_ok());
1239 assert_eq!(ro_policy.check_gpio_write(GPIO_ID), Err(Denial::ReadOnlySession));
1241
1242 assert!(ro_policy.check_i2c_transfer(I2C_ID, 0, 4).is_ok());
1244 assert_eq!(ro_policy.check_i2c_transfer(I2C_ID, 2, 4), Err(Denial::ReadOnlySession));
1246
1247 assert_eq!(ro_policy.check_spi_transmit(SPI_ID, 4), Err(Denial::ReadOnlySession));
1249
1250 let mut_policy = AccessPolicy::new(
1252 SessionMode::Mutating,
1253 res,
1254 ceil,
1255 [proto_rule(GPIO_ID), proto_rule(I2C_ID), proto_rule(SPI_ID)],
1256 )
1257 .expect("policy creation ok");
1258
1259 assert!(mut_policy.check_gpio_write(GPIO_ID).is_ok());
1261 assert!(mut_policy.check_i2c_transfer(I2C_ID, 2, 4).is_ok());
1262 assert!(mut_policy.check_spi_transmit(SPI_ID, 4).is_ok());
1263
1264 assert_eq!(
1266 mut_policy.check_i2c_transfer(I2C_ID, 5000, 5000),
1267 Err(Denial::TransferTooLarge)
1268 );
1269 assert_eq!(mut_policy.check_spi_transmit(SPI_ID, 10000), Err(Denial::TransferTooLarge));
1270 }
1271}