selinux/
sid_table.rs

1// Copyright 2024 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 crate::policy::parser::ByValue;
6use crate::policy::{Policy, SecurityContext, SecurityContextError};
7use crate::{InitialSid, SecurityId, FIRST_UNUSED_SID};
8
9use std::num::NonZeroU32;
10use std::sync::Arc;
11
12#[derive(Clone)]
13enum Entry {
14    /// Used in the "typical" case, when a SID is mapped to a [`SecurityContext`].
15    Valid { security_context: SecurityContext },
16
17    /// Used for the cases of unused initial SIDs (which are never mapped to [`SecurityContext`]s)
18    /// and of SIDs which after a load of a new policy are no longer mapped to valid
19    /// [`SecurityContext`]s.
20    Invalid { context_string: Vec<u8> },
21}
22
23/// Maintains the mapping between [`SecurityId`]s and [`SecurityContext`]s. Grows with use:
24/// `SecurityId`s are minted when code asks a `SidTable` for the `SecurityId` associated
25/// with a given `SecurityContext` and the `SidTable` doesn't happen to have seen that
26/// `SecurityContext` before.
27pub struct SidTable {
28    /// The policy associated with this [`SidTable`].
29    policy: Arc<Policy<ByValue<Vec<u8>>>>,
30
31    /// The mapping from [`SecurityId`] (represented by integer position) to
32    /// [`SecurityContext`] (or for some [`SecurityId`]s, something other than a valid
33    /// [`SecurityContext`]).
34    entries: Vec<Entry>,
35}
36
37impl SidTable {
38    pub fn new(policy: Arc<Policy<ByValue<Vec<u8>>>>) -> Self {
39        Self::new_from(
40            policy,
41            vec![Entry::Invalid { context_string: Vec::new() }; FIRST_UNUSED_SID as usize],
42        )
43    }
44
45    pub fn new_from_previous(policy: Arc<Policy<ByValue<Vec<u8>>>>, previous: &Self) -> Self {
46        let mut new_entries =
47            vec![Entry::Invalid { context_string: Vec::new() }; FIRST_UNUSED_SID as usize];
48        new_entries.reserve(previous.entries.len());
49
50        // Remap any existing Security Contexts to use Ids defined by the new policy.
51        // TODO: https://fxbug.dev/330677360 - replace serialize/parse with an
52        // efficient implementation.
53        new_entries.extend(previous.entries[FIRST_UNUSED_SID as usize..].iter().map(
54            |previous_entry| {
55                let serialized_context = match previous_entry {
56                    Entry::Valid { security_context } => {
57                        previous.policy.serialize_security_context(&security_context)
58                    }
59                    Entry::Invalid { context_string } => context_string.clone(),
60                };
61                let context = policy.parse_security_context(serialized_context.as_slice().into());
62                if let Ok(context) = context {
63                    Entry::Valid { security_context: context }
64                } else {
65                    Entry::Invalid { context_string: serialized_context }
66                }
67            },
68        ));
69
70        Self::new_from(policy, new_entries)
71    }
72
73    /// Returns a `SecurityId` (SID) representing the supplied `security_context`.
74    /// If an entry already exists that matches `security_context` then the existing SID is
75    /// returned.
76    /// Otherwise the `security_context`'s fields are validated against the policy constraints,
77    /// and an explanatory error returned if they are inconsistent.
78    pub fn security_context_to_sid(
79        &mut self,
80        security_context: &SecurityContext,
81    ) -> Result<SecurityId, SecurityContextError> {
82        let existing = &self.entries[FIRST_UNUSED_SID as usize..]
83            .iter()
84            .position(|entry| match entry {
85                Entry::Valid { security_context: entry_security_context } => {
86                    security_context == entry_security_context
87                }
88                Entry::Invalid { .. } => false,
89            })
90            .map(|slice_relative_index| slice_relative_index + (FIRST_UNUSED_SID as usize));
91        let index = if let Some(index) = existing {
92            *index
93        } else {
94            self.policy.validate_security_context(security_context)?;
95            let index = self.entries.len();
96            self.entries.push(Entry::Valid { security_context: security_context.clone() });
97            index
98        };
99        Ok(SecurityId(NonZeroU32::new(index as u32).unwrap()))
100    }
101
102    /// Returns the `SecurityContext` associated with `sid`.
103    /// If `sid` was invalidated by a policy reload then the "unlabeled"
104    /// context is returned instead.
105    pub fn sid_to_security_context(&self, sid: SecurityId) -> &SecurityContext {
106        &self.try_sid_to_security_context(sid).unwrap_or_else(|| {
107            self.try_sid_to_security_context(SecurityId::initial(InitialSid::Unlabeled)).unwrap()
108        })
109    }
110
111    /// Returns the `SecurityContext` associated with `sid`, unless `sid` was invalidated by a
112    /// policy reload. Query implementations should use `sid_to_security_context()`.
113    pub fn try_sid_to_security_context(&self, sid: SecurityId) -> Option<&SecurityContext> {
114        match &self.entries[sid.0.get() as usize] {
115            Entry::Valid { security_context } => Some(&security_context),
116            Entry::Invalid { .. } => None,
117        }
118    }
119
120    fn new_from(policy: Arc<Policy<ByValue<Vec<u8>>>>, mut new_entries: Vec<Entry>) -> Self {
121        for initial_sid in InitialSid::all_variants() {
122            let initial_context = policy.initial_context(initial_sid);
123            new_entries[initial_sid as usize] =
124                Entry::Valid { security_context: initial_context.clone() };
125        }
126
127        SidTable { policy, entries: new_entries }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    use crate::policy::parse_policy_by_value;
136
137    const TESTS_BINARY_POLICY: &[u8] =
138        include_bytes!("../testdata/micro_policies/security_server_tests_policy.pp");
139
140    fn test_policy() -> Arc<Policy<ByValue<Vec<u8>>>> {
141        let (unvalidated, _binary) = parse_policy_by_value(TESTS_BINARY_POLICY.to_vec()).unwrap();
142        Arc::new(unvalidated.validate().unwrap())
143    }
144
145    #[test]
146    fn sid_to_security_context() {
147        let policy = test_policy();
148        let security_context = policy
149            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
150            .unwrap();
151        let mut sid_table = SidTable::new(policy);
152        let sid = sid_table.security_context_to_sid(&security_context).unwrap();
153        assert_eq!(*sid_table.sid_to_security_context(sid), security_context);
154    }
155
156    #[test]
157    fn sids_for_different_security_contexts_differ() {
158        let policy = test_policy();
159        let mut sid_table = SidTable::new(policy.clone());
160        let sid1 = sid_table.security_context_to_sid(
161            &policy.parse_security_context(b"user0:object_r:type0:s0".into()).unwrap(),
162        );
163        let sid2 = sid_table.security_context_to_sid(
164            &policy
165                .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
166                .unwrap(),
167        );
168        assert_ne!(sid1, sid2);
169    }
170
171    #[test]
172    fn sids_for_same_security_context_are_equal() {
173        let policy = test_policy();
174        let security_context = policy
175            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
176            .unwrap();
177        let mut sid_table = SidTable::new(policy);
178        let sid_count_before = sid_table.entries.len();
179        let sid1 = sid_table.security_context_to_sid(&security_context);
180        let sid2 = sid_table.security_context_to_sid(&security_context);
181        assert_eq!(sid1, sid2);
182        assert_eq!(sid_table.entries.len(), sid_count_before + 1);
183    }
184
185    #[test]
186    fn sids_allocated_outside_initial_range() {
187        let policy = test_policy();
188        let security_context = policy
189            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
190            .unwrap();
191        let mut sid_table = SidTable::new(policy);
192        let sid_count_before = sid_table.entries.len();
193        let sid = sid_table.security_context_to_sid(&security_context).unwrap();
194        assert_eq!(sid_table.entries.len(), sid_count_before + 1);
195        assert!(sid.0.get() >= FIRST_UNUSED_SID);
196    }
197
198    #[test]
199    fn initial_sids_remapped_to_dynamic_sids() {
200        let file_initial_sid = SecurityId::initial(InitialSid::File);
201        let policy = test_policy();
202        let mut sid_table = SidTable::new(policy);
203        let file_initial_security_context = sid_table.sid_to_security_context(file_initial_sid);
204        let file_dynamic_sid =
205            sid_table.security_context_to_sid(&file_initial_security_context.clone()).unwrap();
206        assert_ne!(file_initial_sid.0.get(), file_dynamic_sid.0.get());
207        assert!(file_dynamic_sid.0.get() >= FIRST_UNUSED_SID);
208    }
209}