starnix_core/security/selinux_hooks/
audit.rs1use crate::task::{CurrentTask, Task};
6use crate::vfs::{
7 DirEntry, DirEntryHandle, FileObject, FileSystem, FsNode, FsStr, NamespaceNode,
8 PathWithReachability,
9};
10use bstr::BStr;
11use fuchsia_sync::Mutex;
12use hex;
13use linux_uapi::AUDIT_AVC;
14use selinux::permission_check::{PermissionCheck, PermissionCheckResult};
15use selinux::{ClassPermission, KernelClass, KernelPermission, SecurityId};
16use starnix_logging::{BugRef, CATEGORY_STARNIX_SECURITY, trace_instant};
17use std::collections::HashMap;
18use std::fmt::{Display, Error};
19use std::num::NonZeroU64;
20use std::sync::LazyLock;
21
22#[derive(Clone, Eq, Hash, PartialEq)]
24struct AuditableInstance {
25 source_sid: SecurityId,
26 target_sid: SecurityId,
27 class: KernelClass,
28 bug: NonZeroU64,
29}
30
31static TODO_DENY_COUNTS: LazyLock<Mutex<HashMap<AuditableInstance, u64>>> =
33 LazyLock::new(|| Mutex::new(HashMap::new()));
34
35fn should_audit(
37 source_sid: SecurityId,
38 target_sid: SecurityId,
39 class: KernelClass,
40 bug: NonZeroU64,
41) -> bool {
42 const MAX_TODO_AUDIT_DENIALS: u64 = 5;
44
45 let mut counts = TODO_DENY_COUNTS.lock();
46 let count = counts.entry(AuditableInstance { source_sid, target_sid, class, bug }).or_default();
47 *count += 1;
48 *count <= MAX_TODO_AUDIT_DENIALS
49}
50
51#[derive(Clone, Copy)]
73pub enum Auditable<'a> {
74 AuditContext(&'a [Auditable<'a>]),
76 Bug(u64),
77 CurrentTask,
78 DirEntry(&'a DirEntry),
79 FileObject(&'a FileObject),
80 FileSystem(&'a FileSystem),
81 FsNode(&'a FsNode),
82 IoctlCommand(u16),
83 Location(&'a std::panic::Location<'a>),
84 Name(&'a FsStr),
85 NamespaceNode(&'a NamespaceNode),
86 NlMsgtype(u16),
87 None,
88 SockOptArguments(u32, u32),
89 Task(&'a Task),
90 TodoCheck,
91 }
93
94impl Auditable<'_> {
95 fn from_bug(bug_id: u64) -> Self {
96 Auditable::Bug(bug_id)
97 }
98}
99
100impl<'a> From<&'a CurrentTask> for Auditable<'a> {
101 fn from(_value: &'a CurrentTask) -> Self {
102 Auditable::CurrentTask
104 }
105}
106
107impl<'a> From<&'a Task> for Auditable<'a> {
108 fn from(value: &'a Task) -> Self {
109 Auditable::Task(value)
110 }
111}
112
113impl<'a> From<&'a DirEntry> for Auditable<'a> {
114 fn from(value: &'a DirEntry) -> Self {
115 Auditable::DirEntry(value)
116 }
117}
118
119impl<'a> From<&'a DirEntryHandle> for Auditable<'a> {
120 fn from(value: &'a DirEntryHandle) -> Self {
121 Auditable::DirEntry(&*value)
122 }
123}
124
125impl<'a> From<&'a FileObject> for Auditable<'a> {
126 fn from(value: &'a FileObject) -> Self {
127 Auditable::FileObject(value)
128 }
129}
130
131impl<'a> From<&'a FsNode> for Auditable<'a> {
132 fn from(value: &'a FsNode) -> Self {
133 Auditable::FsNode(value)
134 }
135}
136
137impl<'a> From<&'a FileSystem> for Auditable<'a> {
138 fn from(value: &'a FileSystem) -> Self {
139 Auditable::FileSystem(value)
140 }
141}
142
143impl<'a> From<&'a std::panic::Location<'a>> for Auditable<'a> {
144 fn from(value: &'a std::panic::Location<'a>) -> Self {
145 Auditable::Location(value)
146 }
147}
148
149impl<'a> From<&'a NamespaceNode> for Auditable<'a> {
150 fn from(value: &'a NamespaceNode) -> Self {
151 Auditable::NamespaceNode(value)
152 }
153}
154
155impl<'a, const N: usize> From<&'a [Auditable<'a>; N]> for Auditable<'a> {
156 fn from(value: &'a [Auditable<'a>; N]) -> Self {
157 Auditable::AuditContext(value)
158 }
159}
160
161pub(super) fn audit_decision(
175 current_task: &CurrentTask,
176 permission_check: &PermissionCheck<'_>,
177 result: PermissionCheckResult,
178 source_sid: SecurityId,
179 target_sid: SecurityId,
180 permission: KernelPermission,
181 audit_data: Auditable<'_>,
182) {
183 trace_instant!(
184 CATEGORY_STARNIX_SECURITY,
185 match (result.permit, result.todo_bug) {
186 (true, None) => c"audit.granted",
187 (true, Some(_)) => c"audit.todo_deny",
188 _ => c"audit.denied",
189 },
190 fuchsia_trace::Scope::Thread
191 );
192
193 let decision = if let Some(todo_bug) = result.todo_bug {
194 if !should_audit(source_sid, target_sid, permission.class(), todo_bug) {
200 return;
201 }
202
203 "todo_deny"
205 } else {
206 if result.permit { "granted" } else { "denied" }
207 };
208
209 let audit_data_with_bug =
211 [Auditable::from_bug(result.todo_bug.map(NonZeroU64::get).unwrap_or(0)), audit_data];
212 let audit_data =
213 if result.todo_bug.is_some() { (&audit_data_with_bug).into() } else { audit_data };
214
215 let audit_logger = current_task.kernel().audit_logger();
216 audit_logger.audit_log(
217 AUDIT_AVC as u16,
218 || {
219 let tclass = permission.class().name();
220 let permission_name = permission.name();
221
222 let security_server = permission_check.security_server();
225 let scontext = security_server.sid_to_security_context(source_sid).unwrap();
226 let scontext = BStr::new(&scontext);
227 let tcontext = security_server.sid_to_security_context(target_sid).unwrap();
228 let tcontext = BStr::new(&tcontext);
229
230 let pid = current_task.get_pid();
232 let command = current_task.command();
233 format!("avc: {decision} {{ {permission_name} }} for pid={pid} comm=\"{command}\"{audit_data} scontext={scontext} tcontext={tcontext} tclass={tclass}")
234 }
235 );
236}
237
238pub(super) fn audit_todo_decision(
241 current_task: &CurrentTask,
242 bug: BugRef,
243 permission_check: &PermissionCheck<'_>,
244 mut result: PermissionCheckResult,
245 source_sid: SecurityId,
246 target_sid: SecurityId,
247 permission: KernelPermission,
248 audit_context: Auditable<'_>,
249) {
250 if result.todo_bug.is_none() {
251 result.todo_bug = Some(bug.into());
252 audit_decision(
253 current_task,
254 permission_check,
255 result,
256 source_sid,
257 target_sid,
258 permission,
259 (&[Auditable::TodoCheck, audit_context]).into(),
260 )
261 } else {
262 audit_decision(
263 current_task,
264 permission_check,
265 result,
266 source_sid,
267 target_sid,
268 permission,
269 audit_context,
270 )
271 }
272}
273
274impl Display for Auditable<'_> {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), Error> {
276 match self {
277 Auditable::AuditContext(audit_context) => {
278 for item in *audit_context {
279 item.fmt(f)?;
280 }
281 Ok(())
282 }
283 Auditable::Bug(bug_id) => {
284 write!(f, " bug={}", bug_id)
285 }
286 Auditable::CurrentTask => Ok(()),
287 Auditable::DirEntry(entry) => {
288 write!(f, " name={}", hex_escape(entry.read().local_name()))
289 }
290 Auditable::FileObject(file) => {
291 write!(f, " path={}", hex_escape(&file.name.path_escaping_chroot()))
292 }
293 Auditable::FileSystem(fs) => {
294 write!(f, " dev={}", hex_escape(&fs.options.source))
295 }
296 Auditable::FsNode(node) => {
297 write!(f, " ino={}", node.ino)
298 }
299 Auditable::IoctlCommand(ioctl) => {
300 write!(f, " ioctlcmd={:#x}", ioctl)
301 }
302 Auditable::NlMsgtype(message_type) => {
303 write!(f, " nl-msgtype={}", message_type)
304 }
305 Auditable::Location(location) => {
306 write!(f, " caller={:?}", location)
307 }
308 Auditable::Name(name) => {
309 write!(f, " name={}", hex_escape(name))
310 }
311 Auditable::NamespaceNode(node) => {
312 let PathWithReachability::Reachable(path) = node.path_from_root(None) else {
313 return Ok(());
314 };
315 write!(f, " path={}", hex_escape(&path))
316 }
317 Auditable::SockOptArguments(level, optname) => {
318 write!(f, " level={}, optname={}", level, optname)
319 }
320 Auditable::None => Ok(()),
321 Auditable::Task(task) => {
322 write!(f, " pid={}, comm={}", task.get_pid(), task.command())
323 }
324 Auditable::TodoCheck => {
325 write!(f, " todo_check")
326 }
327 }
328 }
329}
330
331struct EscapedString<'a> {
332 value: &'a [u8],
333}
334
335impl<'a> Display for EscapedString<'a> {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), Error> {
337 let maybe_utf8 = str::from_utf8(self.value).ok();
342 if let Some(utf8) = maybe_utf8 {
343 if utf8.find(|c| c <= ' ').is_none() {
344 return write!(f, "\"{}\"", BStr::new(self.value));
345 }
346 }
347 hex::encode_upper(self.value).fmt(f)
348 }
349}
350
351fn hex_escape<'a>(value: &'a [u8]) -> EscapedString<'a> {
352 EscapedString { value }
353}