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