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    ConstraintExpr, ConstraintTerm, CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2,
9    CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2, CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1,
10    CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2, CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2,
11    CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2, CONSTRAINT_EXPR_OPERAND_TYPE_ROLE,
12    CONSTRAINT_EXPR_OPERAND_TYPE_TYPE, CONSTRAINT_EXPR_OPERAND_TYPE_USER,
13    CONSTRAINT_EXPR_OPERATOR_TYPE_DOM, CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY,
14    CONSTRAINT_EXPR_OPERATOR_TYPE_EQ, CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP,
15    CONSTRAINT_EXPR_OPERATOR_TYPE_NE, CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK,
16    CONSTRAINT_TERM_TYPE_AND_OPERATOR, CONSTRAINT_TERM_TYPE_EXPR,
17    CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES, CONSTRAINT_TERM_TYPE_NOT_OPERATOR,
18    CONSTRAINT_TERM_TYPE_OR_OPERATOR,
19};
20use super::{ParseStrategy, 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<PS: ParseStrategy>(
63    constraint_expr: &ConstraintExpr<PS>,
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, Eq, PartialEq)]
103enum ConstraintNode {
104    Branch(BooleanOperator),
105    Leaf(ContextExpression),
106}
107
108impl ConstraintNode {
109    fn try_from_constraint_term<PS: ParseStrategy>(
110        value: &ConstraintTerm<PS>,
111        source: &SecurityContext,
112        target: &SecurityContext,
113    ) -> Result<ConstraintNode, 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<PS: ParseStrategy>(
136        value: &ConstraintTerm<PS>,
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/// A leaf node in the parse tree of a [`ConstraintExpr`]. Represents
171/// a boolean expression in terms of source and target
172/// [`SecurityContext`]s.
173#[derive(Debug, Eq, PartialEq)]
174struct ContextExpression {
175    left: ContextOperand,
176    right: ContextOperand,
177    operator: ContextOperator,
178}
179
180impl ContextExpression {
181    fn evaluate(&self) -> Result<bool, ConstraintError> {
182        match (&self.left, &self.right) {
183            (ContextOperand::UserId(_), ContextOperand::UserId(_))
184            | (ContextOperand::RoleId(_), ContextOperand::RoleId(_))
185            | (ContextOperand::TypeId(_), ContextOperand::TypeId(_)) => match self.operator {
186                ContextOperator::Equal => Ok(self.left == self.right),
187                ContextOperator::NotEqual => Ok(self.left != self.right),
188                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
189                    operator: self.operator.clone(),
190                    left: self.left.clone(),
191                    right: self.right.clone(),
192                }),
193            },
194            (ContextOperand::UserId(id), ContextOperand::UserIds(ids)) => match self.operator {
195                ContextOperator::Equal => Ok(ids.contains(id)),
196                ContextOperator::NotEqual => Ok(!ids.contains(id)),
197                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
198                    operator: self.operator.clone(),
199                    left: self.left.clone(),
200                    right: self.right.clone(),
201                }),
202            },
203            (ContextOperand::RoleId(id), ContextOperand::RoleIds(ids)) => match self.operator {
204                ContextOperator::Equal => Ok(ids.contains(id)),
205                ContextOperator::NotEqual => Ok(!ids.contains(id)),
206                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
207                    operator: self.operator.clone(),
208                    left: self.left.clone(),
209                    right: self.right.clone(),
210                }),
211            },
212            (ContextOperand::TypeId(id), ContextOperand::TypeIds(ids)) => match self.operator {
213                ContextOperator::Equal => Ok(ids.contains(id)),
214                ContextOperator::NotEqual => Ok(!ids.contains(id)),
215                _ => Err(ConstraintError::InvalidContextOperatorForOperands {
216                    operator: self.operator.clone(),
217                    left: self.left.clone(),
218                    right: self.right.clone(),
219                }),
220            },
221            (ContextOperand::Level(left), ContextOperand::Level(right)) => match self.operator {
222                ContextOperator::Equal => Ok(left.compare(right) == Some(Ordering::Equal)),
223                ContextOperator::NotEqual => Ok(left.compare(right) != Some(Ordering::Equal)),
224                ContextOperator::Dominates => Ok(left.dominates(right)),
225                ContextOperator::DominatedBy => Ok(right.dominates(left)),
226                ContextOperator::Incomparable => Ok(left.compare(right).is_none()),
227            },
228            _ => Err(ConstraintError::InvalidContextOperands {
229                left: self.left.clone(),
230                right: self.right.clone(),
231            }),
232        }
233    }
234
235    fn try_from_constraint_term<PS: ParseStrategy>(
236        value: &ConstraintTerm<PS>,
237        source: &SecurityContext,
238        target: &SecurityContext,
239    ) -> Result<ContextExpression, ConstraintError> {
240        let (left, right) = match value.constraint_term_type() {
241            CONSTRAINT_TERM_TYPE_EXPR => {
242                ContextExpression::operands_from_expr(value.expr_operand_type(), source, target)
243            }
244            CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES => {
245                if let Some(names) = value.names() {
246                    ContextExpression::operands_from_expr_with_names(
247                        value.expr_operand_type(),
248                        names,
249                        source,
250                        target,
251                    )
252                } else {
253                    Err(ConstraintError::MissingNames)
254                }
255            }
256            _ => Err(ConstraintError::InvalidTermType { type_: value.constraint_term_type() }),
257        }?;
258        let operator = match value.expr_operator_type() {
259            CONSTRAINT_EXPR_OPERATOR_TYPE_EQ => Ok(ContextOperator::Equal),
260            CONSTRAINT_EXPR_OPERATOR_TYPE_NE => Ok(ContextOperator::NotEqual),
261            CONSTRAINT_EXPR_OPERATOR_TYPE_DOM => Ok(ContextOperator::Dominates),
262            CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY => Ok(ContextOperator::DominatedBy),
263            CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP => Ok(ContextOperator::Incomparable),
264            _ => Err(ConstraintError::InvalidContextOperatorType {
265                type_: value.expr_operator_type(),
266            }),
267        }?;
268        Ok(ContextExpression { left, right, operator })
269    }
270
271    fn operands_from_expr(
272        operand_type: u32,
273        source: &SecurityContext,
274        target: &SecurityContext,
275    ) -> Result<(ContextOperand, ContextOperand), ConstraintError> {
276        match operand_type {
277            CONSTRAINT_EXPR_OPERAND_TYPE_USER => {
278                Ok((ContextOperand::UserId(source.user()), ContextOperand::UserId(target.user())))
279            }
280            CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => {
281                Ok((ContextOperand::RoleId(source.role()), ContextOperand::RoleId(target.role())))
282            }
283            CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => {
284                Ok((ContextOperand::TypeId(source.type_()), ContextOperand::TypeId(target.type_())))
285            }
286            CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2 => Ok((
287                ContextOperand::Level(source.low_level().clone()),
288                ContextOperand::Level(target.low_level().clone()),
289            )),
290            CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2 => Ok((
291                ContextOperand::Level(source.low_level().clone()),
292                ContextOperand::Level(target.effective_high_level().clone()),
293            )),
294            CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2 => Ok((
295                ContextOperand::Level(source.effective_high_level().clone()),
296                ContextOperand::Level(target.low_level().clone()),
297            )),
298            CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2 => Ok((
299                ContextOperand::Level(source.effective_high_level().clone()),
300                ContextOperand::Level(target.effective_high_level().clone()),
301            )),
302            CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1 => Ok((
303                ContextOperand::Level(source.low_level().clone()),
304                ContextOperand::Level(source.effective_high_level().clone()),
305            )),
306            CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2 => Ok((
307                ContextOperand::Level(target.low_level().clone()),
308                ContextOperand::Level(target.effective_high_level().clone()),
309            )),
310            _ => Err(ConstraintError::InvalidContextOperandType { type_: operand_type }),
311        }
312    }
313
314    fn operands_from_expr_with_names<PS: ParseStrategy>(
315        operand_type: u32,
316        names: &ExtensibleBitmap<PS>,
317        source: &SecurityContext,
318        target: &SecurityContext,
319    ) -> Result<(ContextOperand, ContextOperand), ConstraintError> {
320        let ids = names
321            .spans()
322            .flat_map(|span| span.low..=span.high)
323            .map(|i| NonZeroU32::new(i + 1).unwrap());
324
325        let (left, right) =
326            if operand_type & CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK == 0 {
327                match operand_type {
328                    CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
329                        ContextOperand::UserId(source.user()),
330                        ContextOperand::UserIds(ids.map(|id| UserId(id)).collect()),
331                    )),
332                    CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
333                        ContextOperand::RoleId(source.role()),
334                        ContextOperand::RoleIds(ids.map(|id| RoleId(id)).collect()),
335                    )),
336                    CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
337                        ContextOperand::TypeId(source.type_()),
338                        ContextOperand::TypeIds(ids.map(|id| TypeId(id)).collect()),
339                    )),
340                    _ => Err(ConstraintError::InvalidContextWithNamesOperandType {
341                        type_: operand_type,
342                    }),
343                }
344            } else {
345                match operand_type ^ CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK {
346                    CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
347                        ContextOperand::UserId(target.user()),
348                        ContextOperand::UserIds(ids.map(|id| UserId(id)).collect()),
349                    )),
350                    CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
351                        ContextOperand::RoleId(target.role()),
352                        ContextOperand::RoleIds(ids.map(|id| RoleId(id)).collect()),
353                    )),
354                    CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
355                        ContextOperand::TypeId(target.type_()),
356                        ContextOperand::TypeIds(ids.map(|id| TypeId(id)).collect()),
357                    )),
358                    _ => Err(ConstraintError::InvalidContextWithNamesOperandType {
359                        type_: operand_type,
360                    }),
361                }
362            }?;
363        Ok((left, right))
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use crate::policy::{find_class_by_name, parse_policy_by_reference};
371
372    fn normalize_context_expr(expr: ContextExpression) -> ContextExpression {
373        let (left, right) = match expr.operator {
374            ContextOperator::Dominates | ContextOperator::DominatedBy => (expr.left, expr.right),
375            ContextOperator::Equal | ContextOperator::NotEqual | ContextOperator::Incomparable => {
376                match (&expr.left, &expr.right) {
377                    (ContextOperand::UserId(left), ContextOperand::UserId(right)) => (
378                        ContextOperand::UserId(std::cmp::min(*left, *right)),
379                        ContextOperand::UserId(std::cmp::max(*left, *right)),
380                    ),
381                    (ContextOperand::TypeId(left), ContextOperand::TypeId(right)) => (
382                        ContextOperand::TypeId(std::cmp::min(*left, *right)),
383                        ContextOperand::TypeId(std::cmp::max(*left, *right)),
384                    ),
385                    (ContextOperand::RoleId(left), ContextOperand::RoleId(right)) => (
386                        ContextOperand::RoleId(std::cmp::min(*left, *right)),
387                        ContextOperand::RoleId(std::cmp::max(*left, *right)),
388                    ),
389                    _ => (expr.left, expr.right),
390                }
391            }
392        };
393        ContextExpression { operator: expr.operator, left, right }
394    }
395
396    fn normalize(expr: Vec<ConstraintNode>) -> Vec<ConstraintNode> {
397        expr.into_iter()
398            .map(|node| match node {
399                ConstraintNode::Leaf(context_expr) => {
400                    ConstraintNode::Leaf(normalize_context_expr(context_expr))
401                }
402                ConstraintNode::Branch(_) => node,
403            })
404            .collect()
405    }
406
407    #[test]
408    fn decode_constraint_expr() {
409        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
410        let policy = parse_policy_by_reference(policy_bytes.as_slice())
411            .expect("parse policy")
412            .validate()
413            .expect("validate policy");
414        let parsed_policy = policy.0.parsed_policy();
415
416        let source = policy
417            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
418            .expect("valid source security context");
419        let target = policy
420            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
421            .expect("valid target security context");
422
423        let class = find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
424            .expect("look up class");
425        let constraints = class.constraints();
426        assert_eq!(constraints.len(), 1);
427        let constraint = &constraints[0].constraint_expr();
428        let result: Result<Vec<ConstraintNode>, ConstraintError> = constraint
429            .constraint_terms()
430            .iter()
431            .map(|x| ConstraintNode::try_from_constraint_term(x, &source, &target))
432            .collect();
433        let constraint_nodes = normalize(result.expect("decode constraint terms"));
434        let expected = vec![
435            // ( u2 == { user0 user1 } )
436            ConstraintNode::Leaf(ContextExpression {
437                left: ContextOperand::UserId(UserId(NonZeroU32::new(2).unwrap())),
438                right: ContextOperand::UserIds(HashSet::from([
439                    UserId(NonZeroU32::new(1).unwrap()),
440                    UserId(NonZeroU32::new(2).unwrap()),
441                ])),
442                operator: ContextOperator::Equal,
443            }),
444            // ( r1 == r2 )
445            ConstraintNode::Leaf(ContextExpression {
446                left: ContextOperand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
447                right: ContextOperand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
448                operator: ContextOperator::Equal,
449            }),
450            // ( (u2 == { user0 user1 }) and (r1 == r2) )
451            ConstraintNode::Branch(BooleanOperator::And),
452            // (u1 == u2)
453            ConstraintNode::Leaf(ContextExpression {
454                left: ContextOperand::UserId(UserId(NonZeroU32::new(1).unwrap())),
455                right: ContextOperand::UserId(UserId(NonZeroU32::new(2).unwrap())),
456                operator: ContextOperator::Equal,
457            }),
458            // (t1 == t2)
459            ConstraintNode::Leaf(ContextExpression {
460                left: ContextOperand::TypeId(TypeId(NonZeroU32::new(1).unwrap())),
461                right: ContextOperand::TypeId(TypeId(NonZeroU32::new(2).unwrap())),
462                operator: ContextOperator::Equal,
463            }),
464            // not (t1 == t2)
465            ConstraintNode::Branch(BooleanOperator::Not),
466            // (( u1 == u2 ) and ( not (t1 == t2)))
467            ConstraintNode::Branch(BooleanOperator::And),
468            // ( (u2 == { user0 user1 }) and (r1 == r2) ) or (( u1 == u2 ) and ( not (t1 == t2)))
469            ConstraintNode::Branch(BooleanOperator::Or),
470        ];
471
472        assert_eq!(constraint_nodes, expected)
473    }
474
475    #[test]
476    fn evaluate_constraint_expr() {
477        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
478        let policy = parse_policy_by_reference(policy_bytes.as_slice())
479            .expect("parse policy")
480            .validate()
481            .expect("validate policy");
482        let parsed_policy = policy.0.parsed_policy();
483
484        let source = policy
485            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
486            .expect("valid source security context");
487        let target = policy
488            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
489            .expect("valid target security context");
490
491        let class_constraint_eq =
492            find_class_by_name(parsed_policy.classes(), "class_constraint_eq")
493                .expect("look up class");
494        let class_constraint_eq_constraints = class_constraint_eq.constraints();
495        assert_eq!(class_constraint_eq_constraints.len(), 1);
496        // ( u1 == u2 )
497        let constraint_eq = &class_constraint_eq_constraints[0].constraint_expr();
498        assert_eq!(
499            evaluate_constraint(constraint_eq, &source, &target).expect("evaluate constraint"),
500            false
501        );
502
503        let class_constraint_with_and =
504            find_class_by_name(parsed_policy.classes(), "class_constraint_with_and")
505                .expect("look up class");
506        let class_constraint_with_and_constraints = class_constraint_with_and.constraints();
507        assert_eq!(class_constraint_with_and_constraints.len(), 1);
508        // ( ( u1 == u2 ) and ( t1 == t2 ) )
509        let constraint_with_and = &class_constraint_with_and_constraints[0].constraint_expr();
510        assert_eq!(
511            evaluate_constraint(constraint_with_and, &source, &target)
512                .expect("evaluate constraint"),
513            false
514        );
515
516        let class_constraint_with_not =
517            find_class_by_name(parsed_policy.classes(), "class_constraint_with_not")
518                .expect("look up class");
519        let class_constraint_with_not_constraints = class_constraint_with_not.constraints();
520        assert_eq!(class_constraint_with_not_constraints.len(), 1);
521        // ( not ( ( u1 == u2 ) and ( t1 == t2 ) )
522        let constraint_with_not = &class_constraint_with_not_constraints[0].constraint_expr();
523        assert_eq!(
524            evaluate_constraint(constraint_with_not, &source, &target)
525                .expect("evaluate constraint"),
526            true
527        );
528
529        let class_constraint_with_names =
530            find_class_by_name(parsed_policy.classes(), "class_constraint_with_names")
531                .expect("look up class");
532        let class_constraint_with_names_constraints = class_constraint_with_names.constraints();
533        assert_eq!(class_constraint_with_names_constraints.len(), 1);
534        // ( u1 != { user0 user1 })
535        let constraint_with_names = &class_constraint_with_names_constraints[0].constraint_expr();
536        assert_eq!(
537            evaluate_constraint(constraint_with_names, &source, &target)
538                .expect("evaluate constraint"),
539            false
540        );
541
542        let class_constraint_nested =
543            find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
544                .expect("look up class");
545        let class_constraint_nested_constraints = class_constraint_nested.constraints();
546        assert_eq!(class_constraint_nested_constraints.len(), 1);
547        // ( ( ( u2 == { user0 user1} ) and ( r1 == r2 ) ) or ( ( u1 == u2 ) and ( not (t1 == t2 ) ) ) )
548        let constraint_nested = &class_constraint_nested_constraints[0].constraint_expr();
549        assert_eq!(
550            evaluate_constraint(constraint_nested, &source, &target).expect("evaluate constraint"),
551            true
552        )
553    }
554
555    #[test]
556    fn evaluate_mls_constraint_expr() {
557        let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
558        let policy = parse_policy_by_reference(policy_bytes.as_slice())
559            .expect("parse policy")
560            .validate()
561            .expect("validate policy");
562        let parsed_policy = policy.0.parsed_policy();
563
564        let source = policy
565            .parse_security_context(b"user0:object_r:type0:s0-s0".into())
566            .expect("valid source security context");
567        let target = policy
568            .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
569            .expect("valid target security context");
570
571        let class = find_class_by_name(parsed_policy.classes(), "class_mls_constraints")
572            .expect("look up class");
573        let constraints = class.constraints();
574        // Constraints appear in reverse order in parsed policy.
575        let expected = vec![
576            false, // l1 incomp h1
577            false, // h1 incomp h2
578            true,  // l1 domby h2
579            false, // h1 dom l2
580            false, // l2 != h2
581            false, // l1 == l2
582        ];
583        for (i, constraint) in constraints.iter().enumerate() {
584            assert_eq!(
585                evaluate_constraint(constraint.constraint_expr(), &source, &target)
586                    .expect("evaluate constraint",),
587                expected[i],
588                "constraint {}",
589                i
590            );
591        }
592    }
593}