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