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::{InitialSid, ReferenceInitialSid, 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![
41                Entry::Invalid { context_string: Vec::new() };
42                ReferenceInitialSid::FirstUnused as usize
43            ],
44        )
45    }
46
47    pub fn new_from_previous(policy: Arc<Policy>, previous: &Self) -> Self {
48        let mut new_entries = vec![
49            Entry::Invalid { context_string: Vec::new() };
50            ReferenceInitialSid::FirstUnused as usize
51        ];
52        new_entries.reserve(previous.entries.len());
53
54        // Remap any existing Security Contexts to use Ids defined by the new policy.
55        // TODO: https://fxbug.dev/330677360 - replace serialize/parse with an
56        // efficient implementation.
57        new_entries.extend(
58            previous.entries[ReferenceInitialSid::FirstUnused as usize..].iter().map(
59                |previous_entry| {
60                    let serialized_context = match previous_entry {
61                        Entry::Valid { security_context } => {
62                            previous.policy.serialize_security_context(&security_context)
63                        }
64                        Entry::Invalid { context_string } => context_string.clone(),
65                    };
66                    let context =
67                        policy.parse_security_context(serialized_context.as_slice().into());
68                    if let Ok(context) = context {
69                        Entry::Valid { security_context: context }
70                    } else {
71                        Entry::Invalid { context_string: serialized_context }
72                    }
73                },
74            ),
75        );
76
77        Self::new_from(policy, new_entries)
78    }
79
80    /// Returns the `SecurityId` (SID) representing the supplied `security_context`, if it is
81    /// already present in the SID table. If a SID is returned then `security_context` must be valid
82    /// by definition, since `security_context_to_sid()` will only insert validated contexts.
83    pub fn security_context_to_existing_sid(
84        &self,
85        security_context: &SecurityContext,
86    ) -> Option<SecurityId> {
87        let index = &self.entries[ReferenceInitialSid::FirstUnused as usize..]
88            .iter()
89            .position(|entry| match entry {
90                Entry::Valid { security_context: entry_security_context } => {
91                    security_context == entry_security_context
92                }
93                Entry::Invalid { .. } => false,
94            })
95            .map(|slice_relative_index| {
96                slice_relative_index + (ReferenceInitialSid::FirstUnused as usize)
97            })?;
98        Some(SecurityId(NonZeroU32::new(*index as u32).unwrap()))
99    }
100
101    /// Returns a `SecurityId` (SID) representing the supplied `security_context`.
102    /// If an entry already exists that matches `security_context` then the existing SID is
103    /// returned.
104    /// Otherwise the `security_context`'s fields are validated against the policy constraints,
105    /// and an explanatory error returned if they are inconsistent.
106    pub fn security_context_to_sid(
107        &mut self,
108        security_context: &SecurityContext,
109    ) -> Result<SecurityId, SecurityContextError> {
110        if let Some(sid) = self.security_context_to_existing_sid(security_context) {
111            return Ok(sid);
112        }
113        self.policy.validate_security_context(security_context)?;
114        let index = self.entries.len();
115        self.entries.push(Entry::Valid { security_context: security_context.clone() });
116        Ok(SecurityId(NonZeroU32::new(index as u32).unwrap()))
117    }
118
119    /// Returns the `SecurityContext` associated with `sid`.
120    /// If `sid` was invalidated by a policy reload then the "unlabeled"
121    /// context is returned instead.
122    pub fn sid_to_security_context(&self, sid: SecurityId) -> &SecurityContext {
123        &self.try_sid_to_security_context(sid).unwrap_or_else(|| {
124            self.try_sid_to_security_context(InitialSid::Unlabeled.into()).unwrap()
125        })
126    }
127
128    /// Returns the `SecurityContext` associated with `sid`, unless `sid` was invalidated by a
129    /// policy reload. Query implementations should use `sid_to_security_context()`.
130    pub fn try_sid_to_security_context(&self, sid: SecurityId) -> Option<&SecurityContext> {
131        match &self.entries[sid.0.get() as usize] {
132            Entry::Valid { security_context } => Some(&security_context),
133            Entry::Invalid { .. } => None,
134        }
135    }
136
137    fn new_from(policy: Arc<Policy>, mut new_entries: Vec<Entry>) -> Self {
138        for initial_sid in InitialSid::all_variants() {
139            let initial_context = policy.initial_context(*initial_sid);
140            new_entries[*initial_sid as usize] =
141                Entry::Valid { security_context: initial_context.clone() };
142        }
143
144        SidTable { policy, entries: new_entries }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    use crate::policy::parse_policy_by_value;
153
154    const TESTS_BINARY_POLICY: &[u8] =
155        include_bytes!("../testdata/micro_policies/security_server_tests_policy.pp");
156
157    fn test_policy() -> Arc<Policy> {
158        let unvalidated = parse_policy_by_value(TESTS_BINARY_POLICY.to_vec()).unwrap();
159        Arc::new(unvalidated.validate().unwrap())
160    }
161
162    #[test]
163    fn sid_to_security_context() {
164        let policy = test_policy();
165        let security_context = policy
166            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
167            .unwrap();
168        let mut sid_table = SidTable::new(policy);
169        let sid = sid_table.security_context_to_sid(&security_context).unwrap();
170        assert_eq!(*sid_table.sid_to_security_context(sid), security_context);
171    }
172
173    #[test]
174    fn sids_for_different_security_contexts_differ() {
175        let policy = test_policy();
176        let mut sid_table = SidTable::new(policy.clone());
177        let sid1 = sid_table.security_context_to_sid(
178            &policy.parse_security_context(b"user0:object_r:type0:s0".into()).unwrap(),
179        );
180        let sid2 = sid_table.security_context_to_sid(
181            &policy
182                .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
183                .unwrap(),
184        );
185        assert_ne!(sid1, sid2);
186    }
187
188    #[test]
189    fn sids_for_same_security_context_are_equal() {
190        let policy = test_policy();
191        let security_context = policy
192            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
193            .unwrap();
194        let mut sid_table = SidTable::new(policy);
195        let sid_count_before = sid_table.entries.len();
196        let sid1 = sid_table.security_context_to_sid(&security_context);
197        let sid2 = sid_table.security_context_to_sid(&security_context);
198        assert_eq!(sid1, sid2);
199        assert_eq!(sid_table.entries.len(), sid_count_before + 1);
200    }
201
202    #[test]
203    fn sids_allocated_outside_initial_range() {
204        let policy = test_policy();
205        let security_context = policy
206            .parse_security_context(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
207            .unwrap();
208        let mut sid_table = SidTable::new(policy);
209        let sid_count_before = sid_table.entries.len();
210        let sid = sid_table.security_context_to_sid(&security_context).unwrap();
211        assert_eq!(sid_table.entries.len(), sid_count_before + 1);
212        assert!(sid.0.get() >= ReferenceInitialSid::FirstUnused as u32);
213    }
214
215    #[test]
216    fn initial_sids_remapped_to_dynamic_sids() {
217        let file_initial_sid = InitialSid::File.into();
218        let policy = test_policy();
219        let mut sid_table = SidTable::new(policy);
220        let file_initial_security_context = sid_table.sid_to_security_context(file_initial_sid);
221        let file_dynamic_sid =
222            sid_table.security_context_to_sid(&file_initial_security_context.clone()).unwrap();
223        assert_ne!(file_initial_sid.0.get(), file_dynamic_sid.0.get());
224        assert!(file_dynamic_sid.0.get() >= ReferenceInitialSid::FirstUnused as u32);
225    }
226}