Skip to content

Commit 13e6f5b

Browse files
authored
fix(evt): some correctness changes around cwd/perms/timestamps (#127)
1 parent 41e53fb commit 13e6f5b

4 files changed

Lines changed: 166 additions & 77 deletions

File tree

rust-toolchain.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "1.95.0"
3+
components = ["rustfmt", "rust-std", "clippy"]

src/base_plugin/mod.rs

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -319,39 +319,19 @@ impl EderaPlugin {
319319
}
320320

321321
pub fn extract_is_open_read(&mut self, mut req: ExtractRequest<Self>) -> Result<bool> {
322-
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| {
323-
if let Ok(res) = parsers::get_openstate(zone_evt) {
324-
return Ok(res == parsers::OpenType::Read);
325-
}
326-
Ok(false)
327-
})
322+
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| Ok(parsers::is_open_read(zone_evt)))
328323
}
329324

330325
pub fn extract_is_open_write(&mut self, mut req: ExtractRequest<Self>) -> Result<bool> {
331-
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| {
332-
if let Ok(res) = parsers::get_openstate(zone_evt) {
333-
return Ok(res == parsers::OpenType::Write);
334-
}
335-
Ok(false)
336-
})
326+
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| Ok(parsers::is_open_write(zone_evt)))
337327
}
338328

339329
pub fn extract_is_open_exec(&mut self, mut req: ExtractRequest<Self>) -> Result<bool> {
340-
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| {
341-
if let Ok(res) = parsers::get_openstate(zone_evt) {
342-
return Ok(res == parsers::OpenType::Exec);
343-
}
344-
Ok(false)
345-
})
330+
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| Ok(parsers::is_open_exec(zone_evt)))
346331
}
347332

348333
pub fn extract_is_open_create(&mut self, mut req: ExtractRequest<Self>) -> Result<bool> {
349-
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| {
350-
if let Ok(res) = parsers::get_openstate(zone_evt) {
351-
return Ok(res == parsers::OpenType::Create);
352-
}
353-
Ok(false)
354-
})
334+
self.with_zone_syscall_evt_ctx(&mut req, |zone_evt| Ok(parsers::is_open_create(zone_evt)))
355335
}
356336

357337
pub fn extract_count(&mut self, _: ExtractRequest<Self>) -> Result<u64> {
@@ -935,7 +915,7 @@ impl EderaPlugin {
935915
self.threadstate
936916
.with_threadinfo(&evt.zone_id, &evt.thread_id, |tinfo| {
937917
if tinfo.clone_ts != 0 {
938-
Some(evt.timestamp - tinfo.clone_ts)
918+
Some(evt.timestamp.saturating_sub(tinfo.clone_ts))
939919
} else {
940920
None
941921
}
@@ -1154,7 +1134,11 @@ impl EderaPlugin {
11541134
if evt.event_type == event_codes::PPME_SCHEDSWITCH_1_E as u32
11551135
|| evt.event_type == event_codes::PPME_SCHEDSWITCH_6_E as u32
11561136
{
1157-
Some(exec_time.last_switch_ts - exec_time.previous_switch_ts)
1137+
Some(
1138+
exec_time
1139+
.last_switch_ts
1140+
.saturating_sub(exec_time.previous_switch_ts),
1141+
)
11581142
} else {
11591143
// TODO(bml) libsinsp only reports this for explicit switch events,
11601144
// we could actually do better, but for now maintain strict compat
@@ -1207,12 +1191,12 @@ impl EderaPlugin {
12071191
.filter(|s| !s.is_empty())
12081192
.find_map(|entry| {
12091193
// Each entry is "subsystem=path"
1210-
if let Some((name, path)) = entry.split_once('=')
1211-
&& (name == subsystem || name == format!("{}_cgroup", subsystem))
1212-
{
1213-
CString::new(path.to_string()).ok();
1194+
let (name, path) = entry.split_once('=')?;
1195+
if name == subsystem || name == format!("{}_cgroup", subsystem) {
1196+
CString::new(path.to_string()).ok()
1197+
} else {
1198+
None
12141199
}
1215-
None
12161200
})
12171201
})
12181202
.unwrap_or(CString::new("NA").expect("default value must parse")))
@@ -2422,7 +2406,7 @@ impl EderaPlugin {
24222406
.and_then(|evt| {
24232407
self.with_nth_parent_proc_thread(&evt.zone_id, &evt.thread_id, 1, |atinfo| {
24242408
if atinfo.clone_ts != 0 {
2425-
Some(evt.timestamp - atinfo.clone_ts)
2409+
Some(evt.timestamp.saturating_sub(atinfo.clone_ts))
24262410
} else {
24272411
None
24282412
}

src/parsers.rs

Lines changed: 138 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,53 +21,83 @@ const FLAGS_SOCKET_CONNECTED: u32 = 1 << 13;
2121
const FLAGS_OVERLAY_UPPER: u32 = 1 << 17;
2222
const FLAGS_OVERLAY_LOWER: u32 = 1 << 18;
2323

24-
#[derive(PartialEq)]
25-
pub enum OpenType {
26-
Read,
27-
Write,
28-
Exec,
29-
Create,
30-
}
31-
32-
pub fn get_openstate(evt: &ZoneKernelSyscallEvent) -> Result<OpenType> {
33-
// strum from discriminant to make this simpler
34-
let etype =
35-
event_codes::from_repr(evt.event_type).ok_or(anyhow!("could not parse event type"))?;
36-
37-
if is_open_file(etype) {
38-
let is_new_version = etype == event_codes::PPME_SYSCALL_OPENAT_2_X
39-
|| etype == event_codes::PPME_SYSCALL_OPENAT2_X;
40-
// new versions have open flags at arg 3 insted of arg 2
41-
let flags = if is_new_version {
42-
u32::from_ne_bytes(evt.event_params[3].param_data.as_slice().try_into()?)
43-
} else {
44-
u32::from_ne_bytes(evt.event_params[2].param_data.as_slice().try_into()?)
45-
};
24+
// Open access/create/exec classification mirrors libsinsp
25+
// `evt.is_open_read/write/exec/create`.
26+
const OPEN_EXEC_MODE_MASK: u32 =
27+
ppm_consts::PPM_S_IXUSR | ppm_consts::PPM_S_IXGRP | ppm_consts::PPM_S_IXOTH;
28+
29+
fn read_u32_param(evt: &ZoneKernelSyscallEvent, idx: usize) -> Option<u32> {
30+
let param = evt.event_params.get(idx)?;
31+
Some(u32::from_ne_bytes(
32+
param.param_data.as_slice().try_into().ok()?,
33+
))
34+
}
4635

47-
if (flags & ppm_consts::PPM_O_RDONLY) != 0 {
48-
return Ok(OpenType::Read);
49-
} else if (flags & ppm_consts::PPM_O_WRONLY) != 0 {
50-
return Ok(OpenType::Write);
51-
} else if (flags & ppm_consts::PPM_O_F_CREATED) != 0 {
52-
return Ok(OpenType::Create);
53-
} else if (flags & (ppm_consts::PPM_O_TMPFILE | ppm_consts::PPM_O_CREAT)) != 0
54-
&& etype != event_codes::PPME_SYSCALL_OPEN_BY_HANDLE_AT_X
55-
{
56-
let mode_bits = if is_new_version {
57-
u32::from_ne_bytes(evt.event_params[4].param_data.as_slice().try_into()?)
58-
} else {
59-
u32::from_ne_bytes(evt.event_params[3].param_data.as_slice().try_into()?)
36+
/// Normalized open flags for an open-family exit event (the `flags` param), or
37+
/// `None` if this isn't such an event or the param is absent/malformed. The
38+
/// modern openat variants carry flags at arg 3, the legacy forms at arg 2.
39+
fn open_flags(evt: &ZoneKernelSyscallEvent, etype: event_codes) -> Option<u32> {
40+
let idx = match etype {
41+
event_codes::PPME_SYSCALL_OPENAT_2_X | event_codes::PPME_SYSCALL_OPENAT2_X => 3,
42+
event_codes::PPME_SYSCALL_OPEN_X | event_codes::PPME_SYSCALL_OPEN_BY_HANDLE_AT_X => 2,
43+
_ => return None,
44+
};
45+
read_u32_param(evt, idx)
46+
}
47+
48+
pub fn is_open_read(evt: &ZoneKernelSyscallEvent) -> bool {
49+
let Some(etype) = event_codes::from_repr(evt.event_type) else {
50+
return false;
51+
};
52+
open_flags(evt, etype).is_some_and(|flags| (flags & ppm_consts::PPM_O_RDONLY) != 0)
53+
}
54+
55+
pub fn is_open_write(evt: &ZoneKernelSyscallEvent) -> bool {
56+
let Some(etype) = event_codes::from_repr(evt.event_type) else {
57+
return false;
58+
};
59+
open_flags(evt, etype).is_some_and(|flags| (flags & ppm_consts::PPM_O_WRONLY) != 0)
60+
}
61+
62+
pub fn is_open_create(evt: &ZoneKernelSyscallEvent) -> bool {
63+
let Some(etype) = event_codes::from_repr(evt.event_type) else {
64+
return false;
65+
};
66+
let Some(flags) = open_flags(evt, etype) else {
67+
return false;
68+
};
69+
// O_F_CREATED means the file was created; O_TMPFILE creates one only on success.
70+
(flags & ppm_consts::PPM_O_F_CREATED) != 0
71+
|| ((flags & ppm_consts::PPM_O_TMPFILE) != 0 && get_retval(evt).is_some_and(|r| r >= 0))
72+
}
73+
74+
pub fn is_open_exec(evt: &ZoneKernelSyscallEvent) -> bool {
75+
let Some(etype) = event_codes::from_repr(evt.event_type) else {
76+
return false;
77+
};
78+
// `creat` carries mode at arg 2; open-family carries it at arg 3 (legacy) or
79+
// arg 4 (modern), and only counts as exec when the open can create the file.
80+
// open_by_handle_at has no mode param and is excluded, as in libsinsp.
81+
let mode_idx = match etype {
82+
event_codes::PPME_SYSCALL_CREAT_X => 2,
83+
event_codes::PPME_SYSCALL_OPEN_X
84+
| event_codes::PPME_SYSCALL_OPENAT_2_X
85+
| event_codes::PPME_SYSCALL_OPENAT2_X => {
86+
let Some(flags) = open_flags(evt, etype) else {
87+
return false;
6088
};
61-
if (mode_bits
62-
& (ppm_consts::PPM_S_IXUSR | ppm_consts::PPM_S_IXGRP | ppm_consts::PPM_S_IXOTH))
63-
!= 0
64-
{
65-
return Ok(OpenType::Exec);
89+
if (flags & (ppm_consts::PPM_O_TMPFILE | ppm_consts::PPM_O_CREAT)) == 0 {
90+
return false;
91+
}
92+
if etype == event_codes::PPME_SYSCALL_OPEN_X {
93+
3
94+
} else {
95+
4
6696
}
6797
}
68-
}
69-
70-
Err(anyhow!("not an open event"))
98+
_ => return false,
99+
};
100+
read_u32_param(evt, mode_idx).is_some_and(|mode| (mode & OPEN_EXEC_MODE_MASK) != 0)
71101
}
72102

73103
pub fn is_enter(evt: &ZoneKernelSyscallEvent) -> bool {
@@ -436,4 +466,69 @@ mod tests {
436466
assert_eq!(get_retval(&evt), None);
437467
assert_eq!(syscall_failed(&evt), None);
438468
}
469+
470+
// constructs a modern openat exit event: params are [fd, dirfd, name, flags, mode].
471+
// fd=3 (success) so the O_TMPFILE create path is satisfied when exercised.
472+
fn openat2x_event(flags: u32, mode: u32) -> ZoneKernelSyscallEvent {
473+
let u32_param = |v: u32| ZoneKernelEventParam {
474+
param_data: v.to_ne_bytes().to_vec(),
475+
..Default::default()
476+
};
477+
ZoneKernelSyscallEvent {
478+
event_type: event_codes::PPME_SYSCALL_OPENAT_2_X as u32,
479+
event_category: "EC_FILE | EC_SYSCALL".to_string(),
480+
event_params: vec![
481+
u32_param(3),
482+
u32_param(0),
483+
u32_param(0),
484+
u32_param(flags),
485+
u32_param(mode),
486+
],
487+
..Default::default()
488+
}
489+
}
490+
491+
#[test]
492+
fn rdonly_open_is_read_only() {
493+
let evt = openat2x_event(ppm_consts::PPM_O_RDONLY, 0);
494+
assert!(is_open_read(&evt));
495+
assert!(!is_open_write(&evt));
496+
}
497+
498+
#[test]
499+
fn wronly_open_is_write_only() {
500+
let evt = openat2x_event(ppm_consts::PPM_O_WRONLY, 0);
501+
assert!(!is_open_read(&evt));
502+
assert!(is_open_write(&evt));
503+
}
504+
505+
#[test]
506+
fn rdwr_open_is_both_read_and_write() {
507+
let evt = openat2x_event(ppm_consts::PPM_O_RDWR, 0);
508+
assert!(is_open_read(&evt));
509+
assert!(is_open_write(&evt));
510+
}
511+
512+
#[test]
513+
fn created_flag_is_create_independent_of_access_mode() {
514+
let evt = openat2x_event(ppm_consts::PPM_O_WRONLY | ppm_consts::PPM_O_F_CREATED, 0);
515+
assert!(is_open_create(&evt));
516+
assert!(is_open_write(&evt));
517+
}
518+
519+
#[test]
520+
fn creating_open_with_exec_mode_is_exec() {
521+
let evt = openat2x_event(
522+
ppm_consts::PPM_O_WRONLY | ppm_consts::PPM_O_CREAT,
523+
ppm_consts::PPM_S_IXUSR,
524+
);
525+
assert!(is_open_exec(&evt));
526+
}
527+
528+
#[test]
529+
fn plain_read_open_is_neither_create_nor_exec() {
530+
let evt = openat2x_event(ppm_consts::PPM_O_RDONLY, 0);
531+
assert!(!is_open_create(&evt));
532+
assert!(!is_open_exec(&evt));
533+
}
439534
}

src/threadstate.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,12 @@ impl ZoneInfo {
20892089
}
20902090

20912091
fn parse_chdir_exit(&mut self, event: &ZoneKernelSyscallEvent) -> Result<()> {
2092+
// if syscall failed, just bail, we didn't actually change the cwd
2093+
if parsers::get_retval(event).filter(|&v| v >= 0).is_none() {
2094+
debug!("no success retval found for chdir event: {:?}", event);
2095+
return Ok(());
2096+
};
2097+
20922098
// if we have a thread for this event, update the cwd of that thread, otherwise NBD
20932099
self.with_mut_threadinfo_ctx(event, |tinfo| {
20942100
tinfo.cwd = event.event_params[1].param_pretty.clone(); // pretty version is already a string
@@ -2449,8 +2455,9 @@ impl ZoneInfo {
24492455
ExecTime {
24502456
last_switch_ts: event.timestamp,
24512457
previous_switch_ts: exectime.last_switch_ts,
2452-
cumulative_switch_time: exectime.cumulative_switch_time
2453-
+ (event.timestamp - exectime.last_switch_ts),
2458+
cumulative_switch_time: exectime
2459+
.cumulative_switch_time
2460+
.saturating_add(event.timestamp.saturating_sub(exectime.last_switch_ts)),
24542461
},
24552462
);
24562463

0 commit comments

Comments
 (0)