selinux/policy/
constraints.rs

1// Copyright 2025 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 super::extensible_bitmap::ExtensibleBitmap;
6use super::security_context::{Level, SecurityContext, SecurityLevel};
7use super::symbols::{
8    CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2, CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2,
9    CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1, CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2,
10    CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2, CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2,
11    CONSTRAINT_EXPR_OPERAND_TYPE_ROLE, CONSTRAINT_EXPR_OPERAND_TYPE_TYPE,
12    CONSTRAINT_EXPR_OPERAND_TYPE_USER, CONSTRAINT_EXPR_OPERATOR_TYPE_DOM,
13    CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY, CONSTRAINT_EXPR_OPERATOR_TYPE_EQ,
14    CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP, CONSTRAINT_EXPR_OPERATOR_TYPE_NE,
15    CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK, CONSTRAINT_TERM_TYPE_AND_OPERATOR,
16    CONSTRAINT_TERM_TYPE_EXPR, CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES,
17    CONSTRAINT_TERM_TYPE_NOT_OPERATOR, CONSTRAINT_TERM_TYPE_OR_OPERATOR, ConstraintExpr,
18    ConstraintTerm,
19};
20use super::{RoleId, TypeId, UserId};
21
22use std::cmp::Ordering;
23use std::collections::HashSet;
24use std::num::NonZeroU32;
25use thiserror::Error;
26
27#[derive(Clone, Debug, Error, Eq, PartialEq)]
28pub(super) enum ConstraintError {
29    #[error("missing names for constraint term")]
30    MissingNames,
31    #[error("invalid constraint term type {type_:?}")]
32    InvalidTermType { type_: u32 },
33    #[error("invalid operator type for context expression: {type_:?}")]
34    InvalidContextOperatorType { type_: u32 },
35    #[error("invalid operand type for context expression: {type_:?}")]
36    InvalidContextOperandType { type_: u32 },
37    #[error("invalid operand type for context expression with names: {type_:?}")]
38    InvalidContextWithNamesOperandType { type_: u32 },
39    #[error("invalid operator type {operator:?} for operands ({left:?}, {right:?})")]
40    InvalidContextOperatorForOperands {
41        operator: ContextOperator,
42        left: ContextOperand,
43        right: ContextOperand,
44    },
45    #[error("invalid pair of context operands: ({left:?}, {right:?})")]
46    InvalidContextOperands { left: ContextOperand, right: ContextOperand },
47    #[error("invalid constraint term sequence")]
48    InvalidTermSequence,
49}
50
51/// Given a [`ConstraintExpr`] and source and target [`SecurityContext`]s,
52/// decode the constraint expression and evaluate it for the security contexts.
53///
54/// Assumes that the terms of the [`ConstraintExpr`] were sequenced in postfix
55/// order by the policy compiler.
56///
57/// This implementation deliberately avoids shortcuts, since it is used to
58/// validate that constraint expressions are well-formed as well as for
59/// access decisions.
60// TODO: https://fxbug.dev/372400976 - Consider optimizations if this is a
61// performance bottleneck.
62pub(super) fn evaluate_constraint(
63    constraint_expr: &ConstraintExpr,
64    source: &SecurityContext,
65    target: &SecurityContext,
66) -> Result<bool, ConstraintError> {
67    let nodes = constraint_expr
68        .constraint_terms()
69        .iter()
70        .map(|term| ConstraintNode::try_from_constraint_term(term, source, target))
71        .collect::<Result<Vec<_>, _>>()?;
72    let mut stack = Vec::new();
73    for node in nodes.iter() {
74        match node {
75            ConstraintNode::Leaf(expr) => stack.push(expr.evaluate()?),
76            ConstraintNode::Branch(op) => match op {
77                BooleanOperator::Not => {
78                    let arg = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
79                    *arg = !*arg;
80                }
81                BooleanOperator::And => {
82                    let right = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
83                    let left = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
84                    *left = *left && right;
85                }
86                BooleanOperator::Or => {
87                    let right = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
88                    let left = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
89                    *left = *left || right;
90                }
91            },
92        }
93    }
94    let result = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
95    if !stack.is_empty() {
96        return Err(ConstraintError::InvalidTermSequence);
97    }
98    Ok(result)
99}
100
101/// A node in the parse tree of a [`ConstraintExpr`].
102#[derive(Debug)]
103enum ConstraintNode<'a> {
104    Branch(BooleanOperator),
105    Leaf(ContextExpression<'a>),
106}
107
108impl<'a> ConstraintNode<'a> {
109    fn try_from_constraint_term(
110        value: &'a ConstraintTerm,
111        source: &SecurityContext,
112        target: &SecurityContext,
113    ) -> Result<ConstraintNode<'a>, ConstraintError> {
114        if let Ok(op) = BooleanOperator::try_from_constraint_term(value) {
115            Ok(ConstraintNode::Branch(op))
116        } else {
117            Ok(ConstraintNode::Leaf(ContextExpression::try_from_constraint_term(
118                value, source, target,
119            )?))
120        }
121    }
122}
123
124/// A branch node in the parse tree of a [`ConstraintExpr`],
125/// representing an operator on the boolean values of the subtree(s)
126/// below that node.
127#[derive(Debug, Eq, PartialEq)]
128enum BooleanOperator {
129    Not,
130    And,
131    Or,
132}
133
134impl BooleanOperator {
135    fn try_from_constraint_term(
136        value: &ConstraintTerm,
137    ) -> Result<BooleanOperator, ConstraintError> {
138        match value.constraint_term_type() {
139            CONSTRAINT_TERM_TYPE_NOT_OPERATOR => Ok(BooleanOperator::Not),
140            CONSTRAINT_TERM_TYPE_AND_OPERATOR => Ok(BooleanOperator::And),
141            CONSTRAINT_TERM_TYPE_OR_OPERATOR => Ok(BooleanOperator::Or),
142            _ => Err(ConstraintError::InvalidTermType { type_: value.constraint_term_type() }),
143        }
144    }
145}
146
147/// An operator on [`SecurityContext`] fields in a
148/// [`ContextExpression`].
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub(super) enum ContextOperator {
151    Equal,        // `eq` or `==` in policy language
152    NotEqual,     // `ne` or `!=` in policy language
153    Dominates,    // `dom` in policy language
154    DominatedBy,  // `domby` in policy language
155    Incomparable, // `incomp` in policy language
156}
157
158/// An operand in a [`ContextExpression`].
159#[derive(Clone, Debug, Eq, PartialEq)]
160pub(super) enum ContextOperand {
161    UserId(UserId),
162    RoleId(RoleId),
163    TypeId(TypeId),
164    Level(SecurityLevel),
165    UserIds(HashSet<UserId>),
166    RoleIds(HashSet<RoleId>),
167    TypeIds(HashSet<TypeId>),
168}
169
170#[derive(Clone, Debug)]
171pub(super) struct TypeIds<'a> {
172    underlying: &'a ExtensibleBitmap,
173}
174
175impl<'a> TypeIds<'a> {
176    fn contains(&self, id: &TypeId) -> bool {
177        self.underlying.is_set(id.0.get() - 1)
178    }
179}
180
181/// Like [`ContextOperand`] but lifetime-bound to a [`TypeIds`].
182#[derive(Clone, Debug)]
183enum Operand<'a> {
184    UserId(UserId),
185    RoleId(RoleId),
186    TypeId(TypeId),
187    Level(SecurityLevel),
188    UserIds(HashSet<UserId>),
189    RoleIds(HashSet<RoleId>),
190    TypeIds(TypeIds<'a>),
191}
192
193impl From<&Operand<'_>> for ContextOperand {
194    fn from(value: &Operand<'_>) -> Self {
195        match value {
196            Operand::UserId(user_id) => Self::UserId(user_id.clone()),
197            Operand::RoleId(role_id) => Self::RoleId(role_id.clone()),
198            Operand::TypeId(type_id) => Self::TypeId(type_id.clone()),
199            Operand::Level(security_level) => Self::Level(security_level.clone()),
200            Operand::UserIds(user_ids) => Self::UserIds(user_ids.clone()),
201            Operand::RoleIds(role_ids) => Self::RoleIds(role_ids.clone()),
202            Operand::TypeIds(type_ids) => Self::TypeIds(
203                type_ids
204                    .underlying
205                    .indices_of_set_bits()
206                    .map(|i| TypeId(NonZeroU32::new(i + 1).unwrap()))
207                    .collect(),
208            ),
209        }
210    }
211}
212
213/// A leaf node in the parse tree of a [`ConstraintExpr`]. Represents
214/// a boolean expression in terms of source and target
215/// [`SecurityContext`]s.
216#[derive(Debug)]
217struct ContextExpression<'a> {
218    left: Operand<'a>,
219    right: Operand<'a>,
220    operator: ContextOperator,
221}
222
223impl<'a> ContextExpression<'a> {
224    fn evaluate(&self) -> Result<bool, ConstraintError> {
225        match (&self.left, &self.right) {
226            (Operand::UserId(left_id), Operand::UserId(right_id)) => match self.operator {
227                ContextOperator::Equal => Ok(left_id == right_id),
228                ContextOperator::NotEqual => Ok(left_id != right_id),
229                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
230                    operator: self.operator.clone(),
231                    left: ContextOperand::from(&self.left),
232                    right: ContextOperand::from(&self.right),
233                }),
234            },
235            (Operand::RoleId(left_id), Operand::RoleId(right_id)) => match self.operator {
236                ContextOperator::Equal => Ok(left_id == right_id),
237                ContextOperator::NotEqual => Ok(left_id != right_id),
238                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
239                    operator: self.operator.clone(),
240                    left: ContextOperand::from(&self.left),
241                    right: ContextOperand::from(&self.right),
242                }),
243            },
244            (Operand::TypeId(left_id), Operand::TypeId(right_id)) => match self.operator {
245                ContextOperator::Equal => Ok(left_id == right_id),
246                ContextOperator::NotEqual => Ok(left_id != right_id),
247                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
248                    operator: self.operator.clone(),
249                    left: ContextOperand::from(&self.left),
250                    right: ContextOperand::from(&self.right),
251                }),
252            },
253            (Operand::UserId(id), Operand::UserIds(ids)) => match self.operator {
254                ContextOperator::Equal => Ok(ids.contains(id)),
255                ContextOperator::NotEqual => Ok(!ids.contains(id)),
256                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
257                    operator: self.operator.clone(),
258                    left: ContextOperand::from(&self.left),
259                    right: ContextOperand::from(&self.right),
260                }),
261            },
262            (Operand::RoleId(id), Operand::RoleIds(ids)) => match self.operator {
263                ContextOperator::Equal => Ok(ids.contains(id)),
264                ContextOperator::NotEqual => Ok(!ids.contains(id)),
265                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
266                    operator: self.operator.clone(),
267                    left: ContextOperand::from(&self.left),
268                    right: ContextOperand::from(&self.right),
269                }),
270            },
271            (Operand::TypeId(id), Operand::TypeIds(ids)) => match self.operator {
272                ContextOperator::Equal => Ok(ids.contains(id)),
273                ContextOperator::NotEqual => Ok(!ids.contains(id)),
274                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
275                    operator: self.operator.clone(),
276                    left: ContextOperand::from(&self.left),
277                    right: ContextOperand::from(&self.right),
278                }),
279            },
280            (Operand::Level(left), Operand::Level(right)) => match self.operator {
281                ContextOperator::Equal => Ok(left.compare(right) == Some(Ordering::Equal)),
282                ContextOperator::NotEqual => Ok(left.compare(right) != Some(Ordering::Equal)),
283                ContextOperator::Dominates => Ok(left.dominates(right)),
284                ContextOperator::DominatedBy => Ok(right.dominates(left)),
285                ContextOperator::Incomparable => Ok(left.compare(right).is_none()),
286            },
287            _ => Err(ConstraintError::InvalidContextOperands {
288                left: ContextOperand::from(&self.left),
289                right: ContextOperand::from(&self.right),
290            }),
291        }
292    }
293
294    fn try_from_constraint_term(
295        value: &'a ConstraintTerm,
296        source: &SecurityContext,
297        target: &SecurityContext,
298    ) -> Result<ContextExpression<'a>, ConstraintError> {
299        let (left, right) = match value.constraint_term_type() {
300            CONSTRAINT_TERM_TYPE_EXPR => {
301                ContextExpression::operands_from_expr(value.expr_operand_type(), source, target)
302            }
303            CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES => {
304                if let Some(names) = value.names() {
305                    ContextExpression::operands_from_expr_with_names(
306                        value.expr_operand_type(),
307                        names,
308                        source,
309                        target,
310                    )
311                } else {
312                    Err(ConstraintError::MissingNames)
313                }
314            }
315            _ => Err(ConstraintError::InvalidTermType { type_: value.constraint_term_type() }),
316        }?;
317        let operator = match value.expr_operator_type() {
318            CONSTRAINT_EXPR_OPERATOR_TYPE_EQ => Ok(ContextOperator::Equal),
319            CONSTRAINT_EXPR_OPERATOR_TYPE_NE => Ok(ContextOperator::NotEqual),
320            CONSTRAINT_EXPR_OPERATOR_TYPE_DOM => Ok(ContextOperator::Dominates),
321            CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY => Ok(ContextOperator::DominatedBy),
322            CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP => Ok(ContextOperator::Incomparable),
323            _ => Err(ConstraintError::InvalidContextOperatorType {
324                type_: value.expr_operator_type(),
325            }),
326        }?;
327        Ok(ContextExpression { left, right, operator })
328    }
329
330    fn operands_from_expr(
331        operand_type: u32,
332        source: &SecurityContext,
333        target: &SecurityContext,
334    ) -> Result<(Operand<'a>, Operand<'a>), ConstraintError> {
335        match operand_type {
336            CONSTRAINT_EXPR_OPERAND_TYPE_USER => {
337                Ok((Operand::UserId(source.user()), Operand::UserId(target.user())))
338            }
339            CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => {
340                Ok((Operand::RoleId(source.role()), Operand::RoleId(target.role())))
341            }
342            CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => {
343                Ok((Operand::TypeId(source.type_()), Operand::TypeId(target.type_())))
344            }
345            CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2 => Ok((
346                Operand::Level(source.low_level().clone()),
347                Operand::Level(target.low_level().clone()),
348            )),
349            CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2 => Ok((
350                Operand::Level(source.low_level().clone()),
351                Operand::Level(target.effective_high_level().clone()),
352            )),
353            CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2 => Ok((
354                Operand::Level(source.effective_high_level().clone()),
355                Operand::Level(target.low_level().clone()),
356            )),
357            CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2 => Ok((
358                Operand::Level(source.effective_high_level().clone()),
359                Operand::Level(target.effective_high_level().clone()),
360            )),
361            CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1 => Ok((
362                Operand::Level(source.low_level().clone()),
363                Operand::Level(source.effective_high_level().clone()),
364            )),
365            CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2 => Ok((
366                Operand::Level(target.low_level().clone()),
367                Operand::Level(target.effective_high_level().clone()),
368            )),
369            _ => Err(ConstraintError::InvalidContextOperandType { type_: operand_type }),
370        }
371    }
372
373    fn operands_from_expr_with_names(
374        operand_type: u32,
375        names: &'a ExtensibleBitmap,
376        source: &SecurityContext,
377        target: &SecurityContext,
378    ) -> Result<(Operand<'a>, Operand<'a>), ConstraintError> {
379        let ids = names.indices_of_set_bits().map(|i| NonZeroU32::new(i + 1).unwrap());
380
381        if operand_type & CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK == 0 {
382            match operand_type {
383                CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
384                    Operand::UserId(source.user()),
385                    Operand::UserIds(ids.map(|id| UserId(id)).collect()),
386                )),
387                CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
388                    Operand::RoleId(source.role()),
389                    Operand::RoleIds(ids.map(|id| RoleId(id)).collect()),
390                )),
391                CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
392                    Operand::TypeId(source.type_()),
393                    Operand::TypeIds(TypeIds { underlying: names }),
394                )),
395                _ => {
396                    Err(ConstraintError::InvalidContextWithNamesOperandType { type_: operand_type })
397                }
398            }
399        } else {
400            match operand_type ^ CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK {
401                CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
402                    Operand::UserId(target.user()),
403                    Operand::UserIds(ids.map(|id| UserId(id)).collect()),
404                )),
405                CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
406                    Operand::RoleId(target.role()),
407                    Operand::RoleIds(ids.map(|id| RoleId(id)).collect()),
408                )),
409                CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
410                    Operand::TypeId(target.type_()),
411                    Operand::TypeIds(TypeIds { underlying: names }),
412                )),
413                _ => {
414                    Err(ConstraintError::InvalidContextWithNamesOperandType { type_: operand_type })
415                }
416            }
417        }
418    }
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use crate::policy::{find_class_by_name, parse_policy_by_value};
425
426    impl PartialEq for Operand<'_> {
427        fn eq(&self, other: &Self) -> bool {
428            ContextOperand::from(self) == ContextOperand::from(other)
429        }
430    }
431
432    impl Eq for Operand<'_> {}
433
434    impl PartialEq for ContextExpression<'_> {
435        fn eq(&self, other: &Self) -> bool {
436            self.operator == other.operator && self.left == other.left && self.right == other.right
437        }
438    }
439
440    impl Eq for ContextExpression<'_> {}
441
442    impl PartialEq for ConstraintNode<'_> {
443        fn eq(&self, other: &Self) -> bool {
444            match (self, other) {
445                (Self::Branch(self_operator), Self::Branch(other_operator)) => {
446                    self_operator == other_operator
447                }
448                (Self::Leaf(self_expression), Self::Leaf(other_expression)) => {
449                    self_expression == other_expression
450                }
451                _ => false,
452            }
453        }
454    }
455
456    impl Eq for ConstraintNode<'_> {}
457
458    fn normalize_context_expr<'a>(expr: ContextExpression<'a>) -> ContextExpression<'a> {
459        let (left, right) = match expr.operator {
460            ContextOperator::Dominates | ContextOperator::DominatedBy => (expr.left, expr.right),
461            ContextOperator::Equal | ContextOperator::NotEqual | ContextOperator::Incomparable => {
462                match (&expr.left, &expr.right) {
463                    (Operand::UserId(left), Operand::UserId(right)) => (
464                        Operand::UserId(std::cmp::min(*left, *right)),
465                        Operand::UserId(std::cmp::max(*left, *right)),
466                    ),
467                    (Operand::TypeId(left), Operand::TypeId(right)) => (
468                        Operand::TypeId(std::cmp::min(*left, *right)),
469                        Operand::TypeId(std::cmp::max(*left, *right)),
470                    ),
471                    (Operand::RoleId(left), Operand::RoleId(right)) => (
472                        Operand::RoleId(std::cmp::min(*left, *right)),
473                        Operand::RoleId(std::cmp::max(*left, *right)),
474                    ),
475                    _ => (expr.left, expr.right),
476                }
477            }
478        };
479        ContextExpression { operator: expr.operator, left, right }
480    }
481
482    fn normalize<'a>(expr: Vec<ConstraintNode<'a>>) -> Vec<ConstraintNode<'a>> {
483        expr.into_iter()
484            .map(|node| match node {
485                ConstraintNode::Leaf(context_expr) => {
486                    ConstraintNode::Leaf(normalize_context_expr(context_expr))
487                }
488                ConstraintNode::Branch(_) => node,
489            })
490            .collect()
491    }
492
493    #[test]
494    fn decode_constraint_expr() {
495        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
496        let policy = parse_policy_by_value(policy_bytes.to_vec())
497            .expect("parse policy")
498            .validate()
499            .expect("validate policy");
500        let parsed_policy = policy.0.parsed_policy();
501
502        let source = policy
503            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
504            .expect("valid source security context");
505        let target = policy
506            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
507            .expect("valid target security context");
508
509        let class = find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
510            .expect("look up class");
511        let constraints = class.constraints();
512        assert_eq!(constraints.len(), 1);
513        let constraint = &constraints[0].constraint_expr();
514        let result: Result<Vec<ConstraintNode<'_>>, ConstraintError> = constraint
515            .constraint_terms()
516            .iter()
517            .map(|x| ConstraintNode::try_from_constraint_term(x, &source, &target))
518            .collect();
519        let constraint_nodes = normalize(result.expect("decode constraint terms"));
520        let expected = vec![
521            // ( u2 == { user0 user1 } )
522            ConstraintNode::Leaf(ContextExpression {
523                left: Operand::UserId(UserId(NonZeroU32::new(2).unwrap())),
524                right: Operand::UserIds(HashSet::from([
525                    UserId(NonZeroU32::new(1).unwrap()),
526                    UserId(NonZeroU32::new(2).unwrap()),
527                ])),
528                operator: ContextOperator::Equal,
529            }),
530            // ( r1 == r2 )
531            ConstraintNode::Leaf(ContextExpression {
532                left: Operand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
533                right: Operand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
534                operator: ContextOperator::Equal,
535            }),
536            // ( (u2 == { user0 user1 }) and (r1 == r2) )
537            ConstraintNode::Branch(BooleanOperator::And),
538            // (u1 == u2)
539            ConstraintNode::Leaf(ContextExpression {
540                left: Operand::UserId(UserId(NonZeroU32::new(1).unwrap())),
541                right: Operand::UserId(UserId(NonZeroU32::new(2).unwrap())),
542                operator: ContextOperator::Equal,
543            }),
544            // (t1 == t2)
545            ConstraintNode::Leaf(ContextExpression {
546                left: Operand::TypeId(TypeId(NonZeroU32::new(1).unwrap())),
547                right: Operand::TypeId(TypeId(NonZeroU32::new(2).unwrap())),
548                operator: ContextOperator::Equal,
549            }),
550            // not (t1 == t2)
551            ConstraintNode::Branch(BooleanOperator::Not),
552            // (( u1 == u2 ) and ( not (t1 == t2)))
553            ConstraintNode::Branch(BooleanOperator::And),
554            // ( (u2 == { user0 user1 }) and (r1 == r2) ) or (( u1 == u2 ) and ( not (t1 == t2)))
555            ConstraintNode::Branch(BooleanOperator::Or),
556        ];
557
558        assert_eq!(constraint_nodes, expected)
559    }
560
561    #[test]
562    fn evaluate_constraint_expr() {
563        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
564        let policy = parse_policy_by_value(policy_bytes.to_vec())
565            .expect("parse policy")
566            .validate()
567            .expect("validate policy");
568        let parsed_policy = policy.0.parsed_policy();
569
570        let source = policy
571            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
572            .expect("valid source security context");
573        let target = policy
574            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
575            .expect("valid target security context");
576
577        let class_constraint_eq =
578            find_class_by_name(parsed_policy.classes(), "class_constraint_eq")
579                .expect("look up class");
580        let class_constraint_eq_constraints = class_constraint_eq.constraints();
581        assert_eq!(class_constraint_eq_constraints.len(), 1);
582        // ( u1 == u2 )
583        let constraint_eq = &class_constraint_eq_constraints[0].constraint_expr();
584        assert_eq!(
585            evaluate_constraint(constraint_eq, &source, &target).expect("evaluate constraint"),
586            false
587        );
588
589        let class_constraint_with_and =
590            find_class_by_name(parsed_policy.classes(), "class_constraint_with_and")
591                .expect("look up class");
592        let class_constraint_with_and_constraints = class_constraint_with_and.constraints();
593        assert_eq!(class_constraint_with_and_constraints.len(), 1);
594        // ( ( u1 == u2 ) and ( t1 == t2 ) )
595        let constraint_with_and = &class_constraint_with_and_constraints[0].constraint_expr();
596        assert_eq!(
597            evaluate_constraint(constraint_with_and, &source, &target)
598                .expect("evaluate constraint"),
599            false
600        );
601
602        let class_constraint_with_not =
603            find_class_by_name(parsed_policy.classes(), "class_constraint_with_not")
604                .expect("look up class");
605        let class_constraint_with_not_constraints = class_constraint_with_not.constraints();
606        assert_eq!(class_constraint_with_not_constraints.len(), 1);
607        // ( not ( ( u1 == u2 ) and ( t1 == t2 ) )
608        let constraint_with_not = &class_constraint_with_not_constraints[0].constraint_expr();
609        assert_eq!(
610            evaluate_constraint(constraint_with_not, &source, &target)
611                .expect("evaluate constraint"),
612            true
613        );
614
615        let class_constraint_with_names =
616            find_class_by_name(parsed_policy.classes(), "class_constraint_with_names")
617                .expect("look up class");
618        let class_constraint_with_names_constraints = class_constraint_with_names.constraints();
619        assert_eq!(class_constraint_with_names_constraints.len(), 1);
620        // ( u1 != { user0 user1 })
621        let constraint_with_names = &class_constraint_with_names_constraints[0].constraint_expr();
622        assert_eq!(
623            evaluate_constraint(constraint_with_names, &source, &target)
624                .expect("evaluate constraint"),
625            false
626        );
627
628        let class_constraint_nested =
629            find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
630                .expect("look up class");
631        let class_constraint_nested_constraints = class_constraint_nested.constraints();
632        assert_eq!(class_constraint_nested_constraints.len(), 1);
633        // ( ( ( u2 == { user0 user1} ) and ( r1 == r2 ) ) or ( ( u1 == u2 ) and ( not (t1 == t2 ) ) ) )
634        let constraint_nested = &class_constraint_nested_constraints[0].constraint_expr();
635        assert_eq!(
636            evaluate_constraint(constraint_nested, &source, &target).expect("evaluate constraint"),
637            true
638        )
639    }
640
641    #[test]
642    fn evaluate_mls_constraint_expr() {
643        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
644        let policy = parse_policy_by_value(policy_bytes.to_vec())
645            .expect("parse policy")
646            .validate()
647            .expect("validate policy");
648        let parsed_policy = policy.0.parsed_policy();
649
650        let source = policy
651            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
652            .expect("valid source security context");
653        let target = policy
654            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
655            .expect("valid target security context");
656
657        let class = find_class_by_name(parsed_policy.classes(), "class_mls_constraints")
658            .expect("look up class");
659        let constraints = class.constraints();
660        // Constraints appear in reverse order in parsed policy.
661        let expected = vec![
662            false, // l1 incomp h1
663            false, // h1 incomp h2
664            true,  // l1 domby h2
665            false, // h1 dom l2
666            false, // l2 != h2
667            false, // l1 == l2
668        ];
669        for (i, constraint) in constraints.iter().enumerate() {
670            assert_eq!(
671                evaluate_constraint(constraint.constraint_expr(), &source, &target)
672                    .expect("evaluate constraint",),
673                expected[i],
674                "constraint {}",
675                i
676            );
677        }
678    }
679}