Skip to main content

selinux/new_policy/
rules.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::num::NonZeroU32;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use hashbrown::HashTable;
9use hashbrown::hash_table::Entry;
10use rapidhash::RapidBuildHasher;
11
12use super::error::{ParseError, SerializeError, ValidateError};
13use super::parser::PolicyCursor;
14use super::traits::{Parse, Serialize, Validate};
15use super::{AccessVector, ClassId, NewPolicy, TypeId, U24Index};
16
17pub use selinux_policy_derive::{Parse, Serialize};
18
19// Constants
20/// Flag bit for standard `allow` rules.
21pub const AV_ALLOW_RULE_FLAG: u16 = 0x1;
22/// Flag bit for `auditallow` rules.
23pub const AV_AUDITALLOW_RULE_FLAG: u16 = 0x2;
24/// Flag bit for `dontaudit` rules.
25pub const AV_DONTAUDIT_RULE_FLAG: u16 = 0x4;
26
27/// Flag bit for `type_transition` rules.
28pub const AV_TYPE_TRANSITION_RULE_FLAG: u16 = 0x10;
29/// Flag bit for `type_member` rules.
30pub const AV_TYPE_MEMBER_RULE_FLAG: u16 = 0x20;
31/// Flag bit for `type_change` rules.
32pub const AV_TYPE_CHANGE_RULE_FLAG: u16 = 0x40;
33
34/// Flag bit for `allowxperm` extended permissions rules.
35pub const AV_ALLOWXPERM_RULE_FLAG: u16 = 0x100;
36/// Flag bit for `auditallowxperm` extended permissions rules.
37pub const AV_AUDITALLOWXPERM_RULE_FLAG: u16 = 0x200;
38/// Flag bit for `dontauditxperm` extended permissions rules.
39pub const AV_DONTAUDITXPERM_RULE_FLAG: u16 = 0x400;
40
41/// Mask for high bit in rule type flags indicating whether rule is enabled.
42pub const AV_ENABLED_RULE_FLAG: u16 = 0x8000;
43
44/// [`AccessDecision::flags`] value indicating that policy marks source domain permissive.
45pub const SELINUX_AVD_FLAGS_PERMISSIVE: u32 = 1;
46
47/// Extended permissions type for ioctl driver prefix and 8-bit postfix sets.
48pub const XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES: u8 = 1;
49/// Extended permissions type for ioctl 8-bit driver prefixes.
50pub const XPERMS_TYPE_IOCTL_PREFIXES: u8 = 2;
51/// Extended permissions type for netlink message types.
52pub const XPERMS_TYPE_NLMSG: u8 = 3;
53
54/// Number of 64-bit words in 256-bit [`XpermsBitmap`].
55pub const XPERMS_BITMAP_BLOCKS: usize = 4;
56
57/// 256-bit bitmap used for extended permissions (such as ioctls and netlink messages).
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub struct XpermsBitmap([u64; XPERMS_BITMAP_BLOCKS]);
60
61impl Parse for XpermsBitmap {
62    fn parse(cursor: &mut PolicyCursor<'_>) -> Result<Self, ParseError> {
63        let mut words = [0u64; XPERMS_BITMAP_BLOCKS];
64        for word in words.iter_mut() {
65            let low = cursor.parse::<u32>()? as u64;
66            let high = cursor.parse::<u32>()? as u64;
67            *word = low | (high << 32);
68        }
69        Ok(Self(words))
70    }
71}
72
73impl Serialize for XpermsBitmap {
74    fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
75        for &word in self.0.iter() {
76            (word as u32).serialize(writer)?;
77            ((word >> 32) as u32).serialize(writer)?;
78        }
79        Ok(())
80    }
81}
82
83impl XpermsBitmap {
84    pub const BITMAP_BLOCKS: usize = XPERMS_BITMAP_BLOCKS;
85    /// Bitmap with all 256 bits set to 1.
86    pub const ALL: Self = Self([u64::MAX; Self::BITMAP_BLOCKS]);
87    /// Empty bitmap with all bits set to 0.
88    pub const NONE: Self = Self([0u64; Self::BITMAP_BLOCKS]);
89
90    /// Constructs a new [`XpermsBitmap`] from an array of four 64-bit words.
91    pub fn new(elements: [u64; Self::BITMAP_BLOCKS]) -> Self {
92        Self(elements)
93    }
94
95    /// Returns `true` if the bit corresponding to `value` is set in this bitmap.
96    pub fn contains(&self, value: u8) -> bool {
97        let block_index = (value as usize) / (u64::BITS as usize);
98        let bit_index = (value as usize) % (u64::BITS as usize);
99        self.0[block_index] & (1u64 << bit_index) != 0
100    }
101
102    /// Constructs an [`XpermsBitmap`] by loading words from an array of atomic 64-bit integers using relaxed ordering.
103    pub fn from_atomics(atomics: &[AtomicU64; Self::BITMAP_BLOCKS]) -> Self {
104        let mut words = [0u64; Self::BITMAP_BLOCKS];
105        for (i, word) in words.iter_mut().enumerate() {
106            *word = atomics[i].load(Ordering::Relaxed);
107        }
108        Self(words)
109    }
110
111    /// Stores this bitmap into an array of atomic 64-bit integers using relaxed ordering.
112    pub fn to_atomics(&self, atomics: &[AtomicU64; Self::BITMAP_BLOCKS]) {
113        for (i, word) in self.0.iter().enumerate() {
114            atomics[i].store(*word, Ordering::Relaxed);
115        }
116    }
117}
118
119impl std::ops::BitAnd for XpermsBitmap {
120    type Output = Self;
121    fn bitand(self, rhs: Self) -> Self::Output {
122        Self(std::array::from_fn(|i| self.0[i] & rhs.0[i]))
123    }
124}
125
126impl std::ops::BitAndAssign for XpermsBitmap {
127    fn bitand_assign(&mut self, rhs: Self) {
128        for i in 0..4 {
129            self.0[i] &= rhs.0[i];
130        }
131    }
132}
133
134impl std::ops::BitOr for XpermsBitmap {
135    type Output = Self;
136    fn bitor(self, rhs: Self) -> Self::Output {
137        Self(std::array::from_fn(|i| self.0[i] | rhs.0[i]))
138    }
139}
140
141impl std::ops::BitOrAssign for XpermsBitmap {
142    fn bitor_assign(&mut self, rhs: Self) {
143        for i in 0..4 {
144            self.0[i] |= rhs.0[i];
145        }
146    }
147}
148
149impl std::ops::Sub for XpermsBitmap {
150    type Output = Self;
151    fn sub(self, rhs: Self) -> Self {
152        Self(std::array::from_fn(|i| self.0[i] & !rhs.0[i]))
153    }
154}
155
156impl std::ops::SubAssign for XpermsBitmap {
157    fn sub_assign(&mut self, rhs: Self) {
158        for i in 0..4 {
159            self.0[i] &= !rhs.0[i];
160        }
161    }
162}
163
164impl std::ops::Not for XpermsBitmap {
165    type Output = Self;
166    fn not(self) -> Self::Output {
167        Self(self.0.map(|word| !word))
168    }
169}
170
171impl Validate for XpermsBitmap {
172    fn validate(&self, _policy: &NewPolicy) -> Result<(), ValidateError> {
173        Ok(())
174    }
175}
176
177/// Extended permissions specification (e.g., ioctl commands or netlink message types) associated with an access vector rule.
178#[derive(Clone, Debug, PartialEq, Eq, Parse, Serialize)]
179pub struct ExtendedPermissions {
180    xperms_type: u8,
181    xperms_optional_prefix: u8,
182    xperms_bitmap: XpermsBitmap,
183}
184
185impl Validate for ExtendedPermissions {
186    fn validate(&self, _policy: &NewPolicy) -> Result<(), ValidateError> {
187        match self.xperms_type {
188            XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES
189            | XPERMS_TYPE_IOCTL_PREFIXES
190            | XPERMS_TYPE_NLMSG => Ok(()),
191            v => Err(ValidateError::InvalidExtendedPermissionsType { value: v }),
192        }
193    }
194}
195
196impl ExtendedPermissions {
197    /// Returns the raw extended permissions type identifier (e.g. ioctl or netlink message format).
198    pub fn xperms_type(&self) -> u8 {
199        self.xperms_type
200    }
201
202    /// Returns the optional 8-bit prefix specified by this extended permissions block, if any.
203    pub fn xperms_optional_prefix(&self) -> u8 {
204        self.xperms_optional_prefix
205    }
206
207    /// Returns a reference to the underlying [`XpermsBitmap`].
208    pub fn xperms_bitmap(&self) -> &XpermsBitmap {
209        &self.xperms_bitmap
210    }
211
212    /// Returns the total number of individual permissions specified by this bitmap.
213    #[cfg(test)]
214    pub fn count(&self) -> u64 {
215        let count = self
216            .xperms_bitmap
217            .0
218            .iter()
219            .fold(0, |count, block| count as u64 + block.count_ones() as u64);
220        match self.xperms_type {
221            XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES | XPERMS_TYPE_NLMSG => count,
222            XPERMS_TYPE_IOCTL_PREFIXES => count * 0x100,
223            _ => unreachable!("invalid xperms_type in validated ExtendedPermissions"),
224        }
225    }
226
227    /// Returns `true` if the specified extended permission `xperm` is included in this rule.
228    #[cfg(test)]
229    pub fn contains(&self, xperm: u16) -> bool {
230        let [postfix, prefix] = xperm.to_le_bytes();
231        if (self.xperms_type == XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES
232            || self.xperms_type == XPERMS_TYPE_NLMSG)
233            && self.xperms_optional_prefix != prefix
234        {
235            return false;
236        }
237        let value = match self.xperms_type {
238            XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES | XPERMS_TYPE_NLMSG => postfix,
239            XPERMS_TYPE_IOCTL_PREFIXES => prefix,
240            _ => unreachable!("invalid xperms_type in validated ExtendedPermissions"),
241        };
242        self.xperms_bitmap.contains(value)
243    }
244}
245
246/// Compact enum identifying the type and target array for a rule in sequential policy order.
247#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
248pub enum RuleKind {
249    Allow,
250    AuditAllow,
251    DontAudit,
252    TypeTransition,
253    TypeMember,
254    TypeChange,
255    AllowXperm,
256    AuditAllowXperm,
257    DontAuditXperm,
258}
259
260impl TryFrom<u16> for RuleKind {
261    type Error = ParseError;
262
263    fn try_from(rule_type: u16) -> Result<Self, Self::Error> {
264        let base_kind = rule_type & !AV_ENABLED_RULE_FLAG;
265        match base_kind {
266            AV_ALLOW_RULE_FLAG => Ok(Self::Allow),
267            AV_AUDITALLOW_RULE_FLAG => Ok(Self::AuditAllow),
268            AV_DONTAUDIT_RULE_FLAG => Ok(Self::DontAudit),
269            AV_TYPE_TRANSITION_RULE_FLAG => Ok(Self::TypeTransition),
270            AV_TYPE_MEMBER_RULE_FLAG => Ok(Self::TypeMember),
271            AV_TYPE_CHANGE_RULE_FLAG => Ok(Self::TypeChange),
272            AV_ALLOWXPERM_RULE_FLAG => Ok(Self::AllowXperm),
273            AV_AUDITALLOWXPERM_RULE_FLAG => Ok(Self::AuditAllowXperm),
274            AV_DONTAUDITXPERM_RULE_FLAG => Ok(Self::DontAuditXperm),
275            _ => {
276                Err(ParseError::InvalidEnumValue { enum_name: "RuleKind", value: rule_type as u64 })
277            }
278        }
279    }
280}
281
282impl From<RuleKind> for u16 {
283    fn from(kind: RuleKind) -> Self {
284        match kind {
285            RuleKind::Allow => AV_ALLOW_RULE_FLAG,
286            RuleKind::AuditAllow => AV_AUDITALLOW_RULE_FLAG,
287            RuleKind::DontAudit => AV_DONTAUDIT_RULE_FLAG,
288            RuleKind::TypeTransition => AV_TYPE_TRANSITION_RULE_FLAG,
289            RuleKind::TypeMember => AV_TYPE_MEMBER_RULE_FLAG,
290            RuleKind::TypeChange => AV_TYPE_CHANGE_RULE_FLAG,
291            RuleKind::AllowXperm => AV_ALLOWXPERM_RULE_FLAG,
292            RuleKind::AuditAllowXperm => AV_AUDITALLOWXPERM_RULE_FLAG,
293            RuleKind::DontAuditXperm => AV_DONTAUDITXPERM_RULE_FLAG,
294        }
295    }
296}
297
298/// Standard access vector rule (allow, auditallow, dontaudit).
299#[derive(Clone, Debug, PartialEq, Eq)]
300pub struct AccessRule {
301    key: RuleKey,
302    access_vector: AccessVector,
303}
304
305/// Type transition, change, or member rule.
306#[derive(Clone, Debug, PartialEq, Eq)]
307pub struct TypeTransitionRule {
308    key: RuleKey,
309    new_type: TypeId,
310}
311
312/// Extended permissions rule (allowxperm, auditallowxperm, dontauditxperm).
313#[derive(Clone, Debug, PartialEq, Eq)]
314pub struct XpermRule {
315    key: RuleKey,
316    kind: RuleKind,
317    extended_permissions: ExtendedPermissions,
318}
319
320/// Lookup key for indexing and matching access vector rules by source domain, target domain, and class.
321#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
322pub struct RuleKey {
323    source_type: TypeId,
324    target_type: TypeId,
325    class: ClassId,
326}
327
328impl RuleKey {
329    /// Constructs a [`RuleKey`] for the specified source domain, target domain, and security class.
330    pub fn new(source_type: TypeId, target_type: TypeId, class: ClassId) -> Self {
331        Self { source_type, target_type, class }
332    }
333
334    /// Hashes this [`RuleKey`] using `hasher`.
335    pub fn hash(&self, hasher: &RapidBuildHasher) -> u64 {
336        use std::hash::{BuildHasher, Hash, Hasher};
337        let mut state = hasher.build_hasher();
338        Hash::hash(self, &mut state);
339        state.finish()
340    }
341
342    /// Constructs a [`BinaryAccessVectorRuleHeader`] for this [`RuleKey`] and specified [`RuleKind`].
343    fn to_header(&self, kind: RuleKind) -> BinaryAccessVectorRuleHeader {
344        BinaryAccessVectorRuleHeader {
345            source_type: self.source_type.as_u16(),
346            target_type: self.target_type.as_u16(),
347            class: self.class.as_u16(),
348            rule_flags: kind.into(),
349        }
350    }
351}
352
353impl Validate for RuleKey {
354    fn validate(&self, policy: &NewPolicy) -> Result<(), ValidateError> {
355        self.source_type.validate(policy)?;
356        self.target_type.validate(policy)?;
357        self.class.validate(policy)?;
358        Ok(())
359    }
360}
361
362/// Trait implemented by rule types that provide a [`RuleKey`].
363pub trait HasRuleKey {
364    /// Returns the [`RuleKey`] for this rule.
365    fn key(&self) -> RuleKey;
366}
367
368impl HasRuleKey for AccessRule {
369    fn key(&self) -> RuleKey {
370        self.key
371    }
372}
373
374impl HasRuleKey for TypeTransitionRule {
375    fn key(&self) -> RuleKey {
376        self.key
377    }
378}
379
380impl HasRuleKey for XpermRule {
381    fn key(&self) -> RuleKey {
382        self.key
383    }
384}
385
386/// Encapsulates the result of a permissions calculation, between
387/// source & target domains, for a specific class. Decisions describe
388/// which permissions are allowed, and whether permissions should be
389/// audit-logged when allowed, and when denied.
390#[derive(Debug, Clone, PartialEq)]
391pub struct AccessDecision {
392    pub allow: AccessVector,
393    pub auditallow: AccessVector,
394    pub auditdeny: AccessVector,
395    pub flags: u32,
396
397    /// If this field is set then denials should be audit-logged with "todo_deny" as the reason, with
398    /// the `bug` number included in the audit message.
399    pub todo_bug: Option<NonZeroU32>,
400}
401
402impl Default for AccessDecision {
403    fn default() -> Self {
404        Self::allow(AccessVector::NONE)
405    }
406}
407
408impl AccessDecision {
409    /// Returns an [`AccessDecision`] with the specified permissions to `allow`, and default audit
410    /// behaviour.
411    pub const fn allow(allow: AccessVector) -> Self {
412        Self {
413            allow,
414            auditallow: AccessVector::NONE,
415            auditdeny: AccessVector::ALL,
416            flags: 0,
417            todo_bug: None,
418        }
419    }
420}
421
422/// Standard access vector decisions (allow, auditallow, dontaudit) for a source, target, and class tuple.
423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424pub struct AccessVectorDecision {
425    /// Permissions explicitly granted by matching allow rules.
426    pub allow: Option<AccessVector>,
427    /// Permissions explicitly marked for audit logging on grant.
428    pub auditallow: Option<AccessVector>,
429    /// Permissions explicitly suppressed from audit logging on denial.
430    pub dontaudit: Option<AccessVector>,
431}
432
433/// Container for access vector rules optimizing rule lookups and memory layout while preserving
434/// byte-for-byte serialization.
435///
436/// This structure balances three primary goals:
437/// 1. **Separate Rule-Type Lookup Tables**: Maintains independent lookup tables for each
438///    [`RuleKind`] (such as `allow`, `dontaudit`, and `type_transition`) to avoid the overhead of
439///    hashing rule-type values and eliminate query-side type filtering.
440/// 2. **Homogeneous Payload Arrays**: Stores distinct rule payload types ([`AccessRule`],
441///    [`TypeTransitionRule`], and [`XpermRule`]) in separate contiguous arrays to eliminate memory
442///    and performance penalties from padding when mixing differently sized and aligned payload
443///    types in the same table.
444/// 3. **Byte-for-Byte Serialization**: Retains the original binary policy order via `rule_order` to
445///    re-serialize policy data losslessly without storing ordering metadata inside individual rules.
446///
447/// Lookups map [`RuleKey`] hashes to array positions via compact [`U24Index`] values.
448#[derive(Debug, Clone)]
449pub struct AccessVectorRules {
450    av_rules: Box<[AccessRule]>,
451    type_transitions: Box<[TypeTransitionRule]>,
452    xperm_rules: Box<[XpermRule]>,
453    rule_order: Box<[RuleKind]>,
454
455    allow: HashTable<U24Index>,
456    auditallow: HashTable<U24Index>,
457    dontaudit: HashTable<U24Index>,
458    type_transition: HashTable<U24Index>,
459    allowxperm: HashTable<U24Index>,
460    auditallowxperm: HashTable<U24Index>,
461    dontauditxperm: HashTable<U24Index>,
462    hasher: RapidBuildHasher,
463}
464
465impl PartialEq for AccessVectorRules {
466    fn eq(&self, other: &Self) -> bool {
467        self.av_rules == other.av_rules
468            && self.type_transitions == other.type_transitions
469            && self.xperm_rules == other.xperm_rules
470            && self.rule_order == other.rule_order
471    }
472}
473
474impl Eq for AccessVectorRules {}
475
476/// Extended permission decisions (allowxperm, auditallowxperm, dontauditxperm) for a source, target, and class tuple.
477pub struct XpermsDecisions<'a> {
478    /// Iterator yielding extended permissions granted by allowxperm rules.
479    pub allow: XpermsIter<'a>,
480    /// Iterator yielding extended permissions marked for audit logging by auditallowxperm rules.
481    pub auditallow: XpermsIter<'a>,
482    /// Iterator yielding extended permissions suppressed from audit logging by dontauditxperm rules.
483    pub dontaudit: XpermsIter<'a>,
484}
485
486impl AccessVectorRules {
487    /// Constructs a new [`AccessVectorRules`] table and builds dedicated lookup indexes for each rule type.
488    pub fn new(
489        av_rules: Box<[AccessRule]>,
490        type_transitions: Box<[TypeTransitionRule]>,
491        xperm_rules: Box<[XpermRule]>,
492        rule_order: Box<[RuleKind]>,
493    ) -> Result<Self, ParseError> {
494        let hasher = RapidBuildHasher::default();
495
496        let mut allow: HashTable<U24Index> = HashTable::new();
497        let mut auditallow: HashTable<U24Index> = HashTable::new();
498        let mut dontaudit: HashTable<U24Index> = HashTable::new();
499        let mut type_transition: HashTable<U24Index> = HashTable::new();
500
501        let mut allowxperm: HashTable<U24Index> = HashTable::new();
502        let mut auditallowxperm: HashTable<U24Index> = HashTable::new();
503        let mut dontauditxperm: HashTable<U24Index> = HashTable::new();
504
505        let mut av_idx = 0;
506        let mut tr_idx = 0;
507        let mut xp_idx = 0;
508
509        let mut rule_order_iter = rule_order.iter().peekable();
510        while let Some(&kind) = rule_order_iter.next() {
511            match kind {
512                RuleKind::Allow | RuleKind::AuditAllow | RuleKind::DontAudit => {
513                    let table = match kind {
514                        RuleKind::Allow => &mut allow,
515                        RuleKind::AuditAllow => &mut auditallow,
516                        RuleKind::DontAudit => &mut dontaudit,
517                        _ => unreachable!(),
518                    };
519                    insert_rule(table, &hasher, &av_rules, av_idx.try_into()?, kind)?;
520                    av_idx += 1;
521                }
522                RuleKind::TypeTransition | RuleKind::TypeChange | RuleKind::TypeMember => {
523                    if kind == RuleKind::TypeTransition {
524                        insert_rule(
525                            &mut type_transition,
526                            &hasher,
527                            &type_transitions,
528                            tr_idx.try_into()?,
529                            kind,
530                        )?;
531                    }
532                    tr_idx += 1;
533                }
534                RuleKind::AllowXperm | RuleKind::AuditAllowXperm | RuleKind::DontAuditXperm => {
535                    let table = match kind {
536                        RuleKind::AllowXperm => &mut allowxperm,
537                        RuleKind::AuditAllowXperm => &mut auditallowxperm,
538                        RuleKind::DontAuditXperm => &mut dontauditxperm,
539                        _ => unreachable!(),
540                    };
541                    insert_rule(table, &hasher, &xperm_rules, xp_idx.try_into()?, kind)?;
542
543                    while xp_idx + 1 < xperm_rules.len()
544                        && xperm_rules[xp_idx + 1].kind == kind
545                        && xperm_rules[xp_idx + 1].key() == xperm_rules[xp_idx].key()
546                    {
547                        xp_idx += 1;
548                        rule_order_iter.next();
549                    }
550                    xp_idx += 1;
551                }
552            }
553        }
554
555        Ok(Self {
556            av_rules,
557            type_transitions,
558            xperm_rules,
559            rule_order,
560            allow,
561            auditallow,
562            dontaudit,
563            type_transition,
564            allowxperm,
565            auditallowxperm,
566            dontauditxperm,
567            hasher,
568        })
569    }
570
571    /// Finds standard allow, auditallow, and dontaudit access vector decisions for the specified tuple.
572    pub fn find_av_decisions(
573        &self,
574        source: TypeId,
575        target: TypeId,
576        class: ClassId,
577    ) -> AccessVectorDecision {
578        let key = RuleKey::new(source, target, class);
579        let hash = key.hash(&self.hasher);
580
581        let lookup = |table: &HashTable<U24Index>| {
582            let idx = table.find(hash, |&i| self.av_rules[i].key() == key)?;
583            Some(self.av_rules[*idx].access_vector)
584        };
585
586        AccessVectorDecision {
587            allow: lookup(&self.allow),
588            auditallow: lookup(&self.auditallow),
589            dontaudit: lookup(&self.dontaudit),
590        }
591    }
592
593    /// Finds the target domain type transition for the specified source, target, and class tuple.
594    pub fn find_type_transition(
595        &self,
596        source: TypeId,
597        target: TypeId,
598        class: ClassId,
599    ) -> Option<TypeId> {
600        let key = RuleKey::new(source, target, class);
601        let hash = key.hash(&self.hasher);
602        let idx = self.type_transition.find(hash, |&i| self.type_transitions[i].key() == key)?;
603        Some(self.type_transitions[*idx].new_type)
604    }
605
606    /// Finds extended permission decisions (allowxperm, auditallowxperm, dontauditxperm) for the specified tuple.
607    pub fn find_xperms_decisions(
608        &self,
609        source: TypeId,
610        target: TypeId,
611        class: ClassId,
612    ) -> XpermsDecisions<'_> {
613        let key = RuleKey::new(source, target, class);
614        let hash = key.hash(&self.hasher);
615
616        let lookup = |table: &HashTable<U24Index>, kind: RuleKind| {
617            let r = table.find(hash, |&r| self.xperm_rules[r].key() == key).copied();
618            match r {
619                Some(r) => {
620                    let slice = &self.xperm_rules[usize::from(r)..];
621                    XpermsIter { iter: slice.iter(), key, kind }
622                }
623                None => XpermsIter { iter: [].iter(), key, kind },
624            }
625        };
626
627        XpermsDecisions {
628            allow: lookup(&self.allowxperm, RuleKind::AllowXperm),
629            auditallow: lookup(&self.auditallowxperm, RuleKind::AuditAllowXperm),
630            dontaudit: lookup(&self.dontauditxperm, RuleKind::DontAuditXperm),
631        }
632    }
633}
634
635/// Iterator over extended permissions rules matching a specific [`RuleKey`].
636pub struct XpermsIter<'a> {
637    iter: std::slice::Iter<'a, XpermRule>,
638    key: RuleKey,
639    kind: RuleKind,
640}
641
642impl<'a> Iterator for XpermsIter<'a> {
643    type Item = &'a ExtendedPermissions;
644    fn next(&mut self) -> Option<Self::Item> {
645        let rule = self.iter.as_slice().first()?;
646        if rule.key() != self.key || rule.kind != self.kind {
647            self.iter = [].iter();
648            return None;
649        }
650        let rule = self.iter.next()?;
651        Some(&rule.extended_permissions)
652    }
653}
654
655impl Parse for AccessVectorRules {
656    fn parse(cursor: &mut PolicyCursor<'_>) -> Result<Self, ParseError> {
657        let count = u32::parse(cursor)? as usize;
658
659        let mut av_rules = Vec::new();
660        let mut type_transitions = Vec::new();
661        let mut xperm_rules = Vec::new();
662        let mut rule_order = Vec::with_capacity(count);
663
664        for _ in 0..count {
665            let header = BinaryAccessVectorRuleHeader::parse(cursor)?;
666            let kind = RuleKind::try_from(header.rule_flags)?;
667
668            let source_val = header.source_type;
669            let source_type = TypeId::from_u16(source_val)
670                .ok_or(ParseError::InvalidId { value: source_val as u32 })?;
671
672            let target_val = header.target_type;
673            let target_type = TypeId::from_u16(target_val)
674                .ok_or(ParseError::InvalidId { value: target_val as u32 })?;
675
676            let class_val = header.class;
677            let class = ClassId::from_u16(class_val)
678                .ok_or(ParseError::InvalidId { value: class_val as u32 })?;
679
680            let key = RuleKey::new(source_type, target_type, class);
681
682            match kind {
683                RuleKind::AllowXperm | RuleKind::AuditAllowXperm | RuleKind::DontAuditXperm => {
684                    let extended_permissions = ExtendedPermissions::parse(cursor)?;
685                    xperm_rules.push(XpermRule { key, kind, extended_permissions });
686                    rule_order.push(kind);
687                }
688                RuleKind::TypeTransition | RuleKind::TypeChange | RuleKind::TypeMember => {
689                    let new_type = TypeId::parse(cursor)?;
690                    type_transitions.push(TypeTransitionRule { key, new_type });
691                    rule_order.push(kind);
692                }
693                RuleKind::Allow | RuleKind::AuditAllow | RuleKind::DontAudit => {
694                    let access_vector = AccessVector::parse(cursor)?;
695                    av_rules.push(AccessRule { key, access_vector });
696                    rule_order.push(kind);
697                }
698            }
699        }
700
701        Self::new(
702            av_rules.into_boxed_slice(),
703            type_transitions.into_boxed_slice(),
704            xperm_rules.into_boxed_slice(),
705            rule_order.into_boxed_slice(),
706        )
707    }
708}
709
710impl Serialize for AccessVectorRules {
711    fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
712        let count = self.rule_order.len() as u32;
713        count.serialize(writer)?;
714
715        let mut av_idx = 0;
716        let mut tr_idx = 0;
717        let mut xp_idx = 0;
718
719        for &kind in self.rule_order.iter() {
720            match kind {
721                RuleKind::Allow | RuleKind::AuditAllow | RuleKind::DontAudit => {
722                    let rule = &self.av_rules[av_idx];
723                    av_idx += 1;
724                    rule.key.to_header(kind).serialize(writer)?;
725                    let val: u32 = rule.access_vector.into();
726                    val.serialize(writer)?;
727                }
728                RuleKind::TypeTransition | RuleKind::TypeChange | RuleKind::TypeMember => {
729                    let rule = &self.type_transitions[tr_idx];
730                    tr_idx += 1;
731                    rule.key.to_header(kind).serialize(writer)?;
732                    rule.new_type.serialize(writer)?;
733                }
734                RuleKind::AllowXperm | RuleKind::AuditAllowXperm | RuleKind::DontAuditXperm => {
735                    let rule = &self.xperm_rules[xp_idx];
736                    xp_idx += 1;
737                    rule.key.to_header(kind).serialize(writer)?;
738                    rule.extended_permissions.serialize(writer)?;
739                }
740            }
741        }
742        Ok(())
743    }
744}
745
746impl Validate for AccessVectorRules {
747    fn validate(&self, policy: &NewPolicy) -> Result<(), ValidateError> {
748        for rule in self.av_rules.iter() {
749            rule.key.validate(policy)?;
750        }
751        for rule in self.type_transitions.iter() {
752            rule.key.validate(policy)?;
753            rule.new_type.validate(policy)?;
754        }
755        for rule in self.xperm_rules.iter() {
756            rule.key.validate(policy)?;
757            rule.extended_permissions.validate(policy)?;
758        }
759        Ok(())
760    }
761}
762
763/// Inserts an entry into `table` for the given rule's [`RuleKey`].
764///
765/// Returns [`ParseError::DuplicateAccessVectorRule`] if an entry for the same key is already occupied.
766fn insert_rule<R: HasRuleKey>(
767    table: &mut HashTable<U24Index>,
768    hasher: &RapidBuildHasher,
769    rules: &[R],
770    arr_idx: U24Index,
771    kind: RuleKind,
772) -> Result<(), ParseError> {
773    let key = rules[arr_idx].key();
774    let hash = key.hash(hasher);
775
776    match table.entry(hash, |&i| rules[i].key() == key, |&i| rules[i].key().hash(hasher)) {
777        Entry::Occupied(_) => Err(ParseError::DuplicateAccessVectorRule { key, kind }),
778        Entry::Vacant(entry) => {
779            entry.insert(arr_idx);
780            Ok(())
781        }
782    }
783}
784
785/// On-wire header identifying the source, target, class, and rule flags of an access vector rule.
786#[derive(Clone, Debug, Eq, PartialEq, Hash, Parse, Serialize)]
787struct BinaryAccessVectorRuleHeader {
788    source_type: u16,
789    target_type: u16,
790    class: u16,
791    rule_flags: u16,
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797    use crate::new_policy::traits::PolicyId;
798
799    #[test]
800    fn test_xperms_bitmap_ops_and_contains() {
801        let mut bitmap1 = XpermsBitmap::NONE;
802        let mut bitmap2 = XpermsBitmap::NONE;
803        assert!(!bitmap1.contains(10));
804        assert!(!bitmap2.contains(10));
805
806        bitmap1.0[0] = 1 << 10;
807        bitmap2.0[0] = 1 << 20;
808        assert!(bitmap1.contains(10));
809        assert!(!bitmap1.contains(20));
810        assert!(bitmap2.contains(20));
811
812        let or_bitmap = bitmap1 | bitmap2;
813        assert!(or_bitmap.contains(10));
814        assert!(or_bitmap.contains(20));
815
816        let and_bitmap = or_bitmap & bitmap1;
817        assert!(and_bitmap.contains(10));
818        assert!(!and_bitmap.contains(20));
819
820        let sub_bitmap = or_bitmap - bitmap1;
821        assert!(!sub_bitmap.contains(10));
822        assert!(sub_bitmap.contains(20));
823
824        let not_bitmap = !XpermsBitmap::NONE;
825        assert_eq!(not_bitmap, XpermsBitmap::ALL);
826        assert!(not_bitmap.contains(0));
827        assert!(not_bitmap.contains(255));
828    }
829
830    #[test]
831    fn test_av_rule_parse_and_serialize() {
832        let data = [
833            1, 0, 0, 0, // count = 1
834            1, 0, // source_type = 1
835            2, 0, // target_type = 2
836            3, 0, // class = 3
837            1, 0, // rule_type = 1 (ALLOW)
838            5, 0, 0, 0, // access_vector = 5
839        ];
840        let mut cursor = PolicyCursor::new(&data);
841        let av_rules = AccessVectorRules::parse(&mut cursor).expect("parse rules table");
842        assert_eq!(av_rules.av_rules.len(), 1);
843        assert_eq!(av_rules.av_rules[0].key.source_type, TypeId::from_u32(1).unwrap());
844        assert_eq!(av_rules.av_rules[0].key.target_type, TypeId::from_u32(2).unwrap());
845        assert_eq!(av_rules.av_rules[0].key.class, ClassId::from_u32(3).unwrap());
846        assert_eq!(av_rules.av_rules[0].access_vector, AccessVector::from(5));
847
848        let mut writer = Vec::new();
849        av_rules.serialize(&mut writer).expect("serialize rules table");
850        assert_eq!(writer.as_slice(), &data);
851    }
852
853    #[test]
854    fn test_av_rule_type_transition_parse_and_serialize() {
855        let data = [
856            1, 0, 0, 0, // count = 1
857            1, 0, // source_type = 1
858            2, 0, // target_type = 2
859            3, 0, // class = 3
860            16, 0, // rule_type = 16 (TYPE_TRANSITION)
861            10, 0, 0, 0, // new_type = 10
862        ];
863        let mut cursor = PolicyCursor::new(&data);
864        let av_rules = AccessVectorRules::parse(&mut cursor).expect("parse rules table");
865        assert_eq!(av_rules.type_transitions.len(), 1);
866        assert_eq!(av_rules.type_transitions[0].key.source_type, TypeId::from_u32(1).unwrap());
867        assert_eq!(av_rules.type_transitions[0].key.target_type, TypeId::from_u32(2).unwrap());
868        assert_eq!(av_rules.type_transitions[0].key.class, ClassId::from_u32(3).unwrap());
869        assert_eq!(av_rules.type_transitions[0].new_type, TypeId::from_u32(10).unwrap());
870
871        let mut writer = Vec::new();
872        av_rules.serialize(&mut writer).expect("serialize rules table");
873        assert_eq!(writer.as_slice(), &data);
874    }
875
876    #[test]
877    fn test_av_rule_xperm_parse_and_serialize() {
878        let mut data = vec![
879            1, 0, 0, 0, // count = 1
880            1, 0, // source_type = 1
881            2, 0, // target_type = 2
882            3, 0, // class = 3
883            0, 1, // rule_type = 0x0100 (ALLOWXPERM)
884            1, // xperms_type = 1 (XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES)
885            0, // xperms_optional_prefix = 0
886        ];
887        data.extend_from_slice(&[
888            1, 0, 0, 0, 0, 0, 0, 0, // word 0 = 1
889            0, 0, 0, 0, 0, 0, 0, 0, // word 1 = 0
890            0, 0, 0, 0, 0, 0, 0, 0, // word 2 = 0
891            0, 0, 0, 0, 0, 0, 0, 0, // word 3 = 0
892        ]);
893        let mut cursor = PolicyCursor::new(&data);
894        let av_rules = AccessVectorRules::parse(&mut cursor).expect("parse rules table");
895        assert_eq!(av_rules.xperm_rules.len(), 1);
896        let xp = &av_rules.xperm_rules[0].extended_permissions;
897        assert_eq!(xp.xperms_type, XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES);
898        assert!(xp.xperms_bitmap.contains(0));
899        assert!(!xp.xperms_bitmap.contains(1));
900
901        let mut writer = Vec::new();
902        av_rules.serialize(&mut writer).expect("serialize rules table");
903        assert_eq!(writer.as_slice(), &data);
904    }
905
906    #[test]
907    fn test_access_vector_rules_indexing_and_decisions() {
908        let data = [
909            2, 0, 0, 0, // count = 2 rules
910            // Rule 1: ALLOW (source 1, target 2, class 3)
911            1, 0, // source = 1
912            2, 0, // target = 2
913            3, 0, // class = 3
914            1, 0, // rule_type = 1 (ALLOW)
915            7, 0, 0, 0, // access_vector = 7
916            // Rule 2: TYPE_TRANSITION (source 1, target 2, class 3 -> new_type 9)
917            1, 0, // source = 1
918            2, 0, // target = 2
919            3, 0, // class = 3
920            16, 0, // rule_type = 16 (TYPE_TRANSITION)
921            9, 0, 0, 0, // new_type = 9
922        ];
923
924        let mut cursor = PolicyCursor::new(&data);
925        let av_rules = AccessVectorRules::parse(&mut cursor).expect("parse rules table");
926
927        let s1 = TypeId::from_u32(1).unwrap();
928        let t2 = TypeId::from_u32(2).unwrap();
929        let c3 = ClassId::from_u32(3).unwrap();
930
931        let decision = av_rules.find_av_decisions(s1, t2, c3);
932        assert_eq!(decision.allow, Some(AccessVector::from(7)));
933        assert_eq!(decision.auditallow, None);
934        assert_eq!(decision.dontaudit, None);
935
936        let transition = av_rules.find_type_transition(s1, t2, c3);
937        assert_eq!(transition, Some(TypeId::from_u32(9).unwrap()));
938    }
939}