1use std::sync::OnceLock;
4
5use anyhow::Result;
6use libbpf_rs::{RingBuffer, RingBufferBuilder};
7use nix::sys::signal::Signal;
8use zerocopy::FromBytes;
9
10include!(concat!(env!("OUT_DIR"), "/logging_types.rs"));
11
12impl log_hdr {
13 pub fn level(&self) -> LogLevel {
18 LogLevel::from_repr(self.level as u32).unwrap_or(LogLevel::LOG_LEVEL_ERROR)
20 }
21
22 fn reason(&self) -> LogReason {
27 LogReason::from_repr(self.reason as u32).unwrap_or(LogReason::LOG_REASON_UNKNOWN)
29 }
30
31 fn reason_str(&self) -> String {
33 enum_str_no_prefix(self.reason(), "LOG_REASON_")
34 }
35
36 pub fn type_(&self) -> EventType {
41 EventType::from_repr(self.type_ as u32).unwrap_or(EventType::EVENT_TYPE_UNKNOWN)
43 }
44
45 fn type_str(&self) -> String {
47 enum_str_no_prefix(self.type_(), "EVENT_TYPE_")
48 }
49}
50
51impl std::fmt::Display for log_hdr {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 let mut comm_str = char_array_to_str(&self.comm);
54 if comm_str.is_empty() {
55 comm_str = "(empty)";
56 }
57 let policy_str: String = if self.pol_id == crate::seabee::NO_POL_ID.into()
58 || self.pol_id == crate::seabee::BASE_POLICY_ID.into()
59 {
60 String::from("SeaBee")
61 } else {
62 format!("SeaBee policy {}", self.pol_id)
64 };
65 write!(
66 f,
67 "{}: {}: {} {}({})",
68 policy_str,
69 self.type_str(),
70 self.reason_str(),
71 comm_str,
72 self.pid
73 )
74 }
75}
76
77pub fn setup_logger<'a>(ringbuf: &libbpf_rs::MapHandle) -> Result<RingBuffer<'a>> {
81 let mut rbb = RingBufferBuilder::new();
82 rbb.add(ringbuf, rb_callback)?;
83 Ok(rbb.build()?)
84}
85
86#[macro_export]
88macro_rules! log_struct {
89 ($t:ty,$h:ident,$d:ident) => {
90 match <$t>::ref_from_bytes($d) {
91 Ok(log_struct) => {
92 log(
93 $h.level(),
94 format!("{} {}", $h, log_struct.to_string()),
96 )
97 }
98 Err(e) => log(
99 $h.level(),
100 format!("{} (unable to parse log struct - {}) {:?}", $h, e, $d),
101 ),
102 }
103 };
104}
105
106fn rb_callback(data: &[u8]) -> i32 {
126 if let Some(header) = log_header(data) {
127 match header.type_() {
128 EventType::EVENT_TYPE_MSG => {
129 log_struct!(generic_msg_log, header, data)
130 }
131 EventType::EVENT_TYPE_FILE_ACCESS => {
132 log_struct!(inode_access_log, header, data)
133 }
134 EventType::EVENT_TYPE_SB_UMOUNT => {
135 log_struct!(sb_umount_log, header, data)
136 }
137 EventType::EVENT_TYPE_BPF_MAP => {
138 log_struct!(bpf_map_log, header, data)
139 }
140 EventType::EVENT_TYPE_TASK_KILL => {
141 log_struct!(task_kill_log, header, data)
142 }
143 EventType::EVENT_TYPE_KERNEL_MODULE_REQUEST => {
144 log_struct!(kernel_module_request_log, header, data)
145 }
146 EventType::EVENT_TYPE_KERNEL_READ_FILE => {
147 log_struct!(kernel_read_file_log, header, data)
148 }
149 EventType::EVENT_TYPE_KERNEL_LOAD_DATA => {
150 log_struct!(kernel_load_data_log, header, data)
151 }
152 EventType::EVENT_TYPE_PTRACE_ACCESS_CHECK => {
153 log_struct!(ptrace_access_check_log, header, data)
154 }
155 _ => {
158 let hdr_size = std::mem::size_of::<log_hdr>();
159 if data.len() == hdr_size {
160 log(header.level(), format!("{header}"))
161 } else {
162 log(
163 header.level(),
164 format!(
165 "{} (unimplemented log struct for {} bytes of data) {:?}",
166 header,
167 hdr_size - data.len(),
168 data
169 ),
170 )
171 }
172 }
173 }
174 }
175 0
176}
177
178pub fn log(level: LogLevel, log: String) {
180 match level {
181 LogLevel::LOG_LEVEL_OFF => (),
182 LogLevel::LOG_LEVEL_TRACE => tracing::trace!("{}", log),
183 LogLevel::LOG_LEVEL_DEBUG => tracing::debug!("{}", log),
184 LogLevel::LOG_LEVEL_INFO => tracing::info!("{}", log),
185 LogLevel::LOG_LEVEL_WARN => tracing::warn!("{}", log),
186 LogLevel::LOG_LEVEL_ERROR => tracing::error!("{}", log),
187 };
188}
189
190pub static LOG_LEVEL: OnceLock<LogLevel> = OnceLock::new();
192
193pub static LOG_FILTER: OnceLock<std::collections::HashSet<EventType>> = OnceLock::new();
194
195pub fn log_header(data: &[u8]) -> Option<&log_hdr> {
199 if let Ok((header, _)) = log_hdr::ref_from_prefix(data) {
201 if header.level() as u32 > *LOG_LEVEL.get().unwrap() as u32 {
203 return None;
204 }
205 if LOG_FILTER.get().unwrap().contains(&header.type_()) {
206 return None;
207 }
208 Some(header)
209 } else {
210 log(
211 LogLevel::LOG_LEVEL_ERROR,
212 format!("(unable to parse log header) {data:?}"),
213 );
214 None
215 }
216}
217
218pub fn char_array_to_str(array: &[u8]) -> &str {
221 match std::ffi::CStr::from_bytes_until_nul(array) {
222 Ok(cstr) => cstr.to_str().unwrap_or("(non-utf8 string)"),
223 Err(_) => "(bad bytes)",
224 }
225}
226
227fn enum_str_no_prefix<T: AsRef<str>>(bindgen_enum: T, prefix: &str) -> String {
231 bindgen_enum
232 .as_ref()
233 .strip_prefix(prefix)
234 .unwrap_or("(error)")
235 .to_ascii_lowercase()
236}
237
238impl std::fmt::Display for generic_msg_log {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 let msg = char_array_to_str(&self.msg);
242 write!(f, "{msg}")
243 }
244}
245
246impl std::fmt::Display for sb_umount_log {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 write!(f, "dev {}", self.target_dev)
250 }
251}
252
253impl std::fmt::Display for bpf_map_log {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 let name = char_array_to_str(&self.name);
256 write!(f, "access to map {}({})", name, self.map_id)
257 }
258}
259
260impl std::fmt::Display for task_kill_log {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 let signal_str = if let Ok(signal) = Signal::try_from(self.signum) {
263 format!("{}({})", signal.as_str(), self.signum)
264 } else {
265 format!("unknown({})", self.signum)
266 };
267 let target_comm = char_array_to_str(&self.target_comm);
268
269 write!(
270 f,
271 "send {} to {}({})",
272 signal_str, target_comm, self.target_pid
273 )
274 }
275}
276impl std::fmt::Display for kernel_module_request_log {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 let name = char_array_to_str(&self.kmod_name);
279 write!(f, "auto load module: '{name}'")
280 }
281}
282
283impl std::fmt::Display for kernel_read_file_log {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 let filename = char_array_to_str(&self.filename);
286 write!(f, "load file '{}', id: {}", filename, self.id)
287 }
288}
289
290impl std::fmt::Display for kernel_load_data_log {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 write!(f, "id: {}", self.id)
293 }
294}
295
296impl std::fmt::Display for ptrace_access_check_log {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 let target_comm = char_array_to_str(&self.target_comm);
299 write!(
300 f,
301 "ptrace mode {} on {}({})",
302 self.mode, target_comm, self.target_pid
303 )
304 }
305}
306
307impl std::fmt::Display for inode_access_log {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 let name = char_array_to_str(&self.name);
310 let action =
311 InodeAction::from_repr(self.action).unwrap_or(InodeAction::INODE_ACTION_UNKNOWN);
312 let action_str = enum_str_no_prefix(action, "");
313 match action {
314 InodeAction::INODE_PERMISSION => write!(f, "{action_str}"),
315 _ => write!(f, "{action_str} on {name}"),
316 }
317 }
318}