@@ -21,53 +21,83 @@ const FLAGS_SOCKET_CONNECTED: u32 = 1 << 13;
2121const FLAGS_OVERLAY_UPPER : u32 = 1 << 17 ;
2222const 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
73103pub 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}
0 commit comments