diff --git a/pinchy-common/src/kernel_types.rs b/pinchy-common/src/kernel_types.rs index 0d71589..57188f1 100644 --- a/pinchy-common/src/kernel_types.rs +++ b/pinchy-common/src/kernel_types.rs @@ -169,6 +169,16 @@ pub struct LinuxDirent64 { pub d_name: [u8; SMALL_READ_SIZE], } +/// Linux directory entry structure (legacy getdents syscall) +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LinuxDirent { + pub d_ino: u64, + pub d_off: u64, + pub d_reclen: u16, + pub d_name: [u8; SMALL_READ_SIZE], +} + /// Filesystem statistics structure, matching the kernel's struct statfs #[repr(C)] #[derive(Debug, Default, Copy, Clone)] @@ -523,6 +533,15 @@ pub struct Seminfo { pub semaem: i32, } +/// System V semaphore operation structure, matching the kernel's struct sembuf. +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Sembuf { + pub sem_num: u16, // Semaphore number + pub sem_op: i16, // Semaphore operation + pub sem_flg: i16, // Operation flags +} + #[repr(C)] #[derive(Clone, Copy)] pub union Semun { diff --git a/pinchy-common/src/lib.rs b/pinchy-common/src/lib.rs index 66399ee..0ee6658 100644 --- a/pinchy-common/src/lib.rs +++ b/pinchy-common/src/lib.rs @@ -130,6 +130,9 @@ pub union SyscallEventData { pub fstat: FstatData, pub newfstatat: NewfstatatData, pub getdents64: Getdents64Data, + pub getdents: GetdentsData, + pub semtimedop: SemtimedopData, + pub sendfile: SendfileData, pub mmap: MmapData, pub munmap: MunmapData, pub brk: BrkData, @@ -170,6 +173,8 @@ pub union SyscallEventData { pub gettid: GettidData, pub getuid: GetuidData, pub geteuid: GeteuidData, + pub fork: ForkData, + pub vfork: VforkData, pub getgid: GetgidData, pub getegid: GetegidData, pub getppid: GetppidData, @@ -417,6 +422,15 @@ pub union SyscallEventData { pub lookup_dcookie: LookupDcookieData, pub nfsservctl: NfsservctlData, pub utime: UtimeData, + pub access: AccessData, + pub chmod: ChmodData, + pub creat: CreatData, + pub mkdir: MkdirData, + pub readlink: ReadlinkData, + pub stat: StatData, + pub lstat: LstatData, + pub utimes: UtimesData, + pub futimesat: FutimesatData, } #[repr(C)] @@ -750,6 +764,14 @@ pub struct GetegidData; #[derive(Clone, Copy)] pub struct GetppidData; +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ForkData; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VforkData; + #[repr(C)] #[derive(Clone, Copy)] pub struct IoctlData { @@ -834,6 +856,35 @@ pub struct Getdents64Data { pub num_dirents: u8, } +#[repr(C)] +#[derive(Clone, Copy)] +pub struct GetdentsData { + pub fd: i32, + pub count: usize, + pub dirents: [crate::kernel_types::LinuxDirent; 4], + pub num_dirents: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SemtimedopData { + pub semid: i32, + pub sops: [crate::kernel_types::Sembuf; 4], + pub nsops: usize, + pub timeout: crate::kernel_types::Timespec, + pub timeout_is_null: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SendfileData { + pub out_fd: i32, + pub in_fd: i32, + pub offset: u64, + pub offset_is_null: u8, + pub count: usize, +} + #[repr(C)] #[derive(Clone, Copy)] pub struct MmapData { @@ -3451,3 +3502,70 @@ pub struct UtimeData { pub times: kernel_types::Utimbuf, pub times_is_null: u8, } + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct AccessData { + pub pathname: [u8; SMALL_READ_SIZE], + pub mode: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ChmodData { + pub pathname: [u8; SMALL_READ_SIZE], + pub mode: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct CreatData { + pub pathname: [u8; SMALL_READ_SIZE], + pub mode: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct MkdirData { + pub pathname: [u8; SMALL_READ_SIZE], + pub mode: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ReadlinkData { + pub pathname: [u8; SMALL_READ_SIZE], + pub buf: [u8; MEDIUM_READ_SIZE], + pub bufsiz: u64, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct StatData { + pub pathname: [u8; SMALL_READ_SIZE], + pub statbuf: kernel_types::Stat, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LstatData { + pub pathname: [u8; SMALL_READ_SIZE], + pub statbuf: kernel_types::Stat, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UtimesData { + pub filename: [u8; SMALL_READ_SIZE], + pub times: [kernel_types::Timeval; 2], + pub times_is_null: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FutimesatData { + pub dirfd: i32, + pub pathname: [u8; SMALL_READ_SIZE], + pub times: [kernel_types::Timeval; 2], + pub times_is_null: u8, +} diff --git a/pinchy-ebpf/src/basic_io.rs b/pinchy-ebpf/src/basic_io.rs index 7b29b44..181e015 100644 --- a/pinchy-ebpf/src/basic_io.rs +++ b/pinchy-ebpf/src/basic_io.rs @@ -577,6 +577,23 @@ pub fn syscall_exit_basic_io(ctx: TracePointContext) -> u32 { data.arg = args[2] as u64; data.nr_args = args[3] as u32; } + #[cfg(x86_64)] + syscalls::SYS_sendfile => { + let data = data_mut!(entry, sendfile); + + data.out_fd = args[0] as i32; + data.in_fd = args[1] as i32; + data.count = args[3] as usize; + + let offset_ptr = args[2] as *const u64; + + if offset_ptr.is_null() { + data.offset_is_null = 1; + } else { + data.offset_is_null = 0; + data.offset = unsafe { bpf_probe_read_user::(offset_ptr).unwrap_or(0) }; + } + } _ => { entry.discard(); return Ok(()); diff --git a/pinchy-ebpf/src/filesystem.rs b/pinchy-ebpf/src/filesystem.rs index 0077eb0..70485c8 100644 --- a/pinchy-ebpf/src/filesystem.rs +++ b/pinchy-ebpf/src/filesystem.rs @@ -7,6 +7,8 @@ use aya_ebpf::{ programs::TracePointContext, }; use aya_log_ebpf::error; +#[cfg(x86_64)] +use pinchy_common::kernel_types::LinuxDirent; use pinchy_common::{ kernel_types::{LinuxDirent64, Stat}, syscalls, DATA_READ_SIZE, @@ -88,6 +90,43 @@ pub fn syscall_exit_filesystem(ctx: TracePointContext) -> u32 { } } } + #[cfg(x86_64)] + syscalls::SYS_getdents => { + let data = data_mut!(entry, getdents); + + data.fd = args[0] as i32; + data.count = args[2] as usize; + data.num_dirents = 0; + + let dirp = args[1] as *const u8; + let mut offset = 0usize; + + for dirent in data.dirents.iter_mut() { + if offset < data.count { + let base = unsafe { dirp.add(offset) }; + + if base.is_null() { + break; + } + + if let Ok(val) = + unsafe { bpf_probe_read_user::(base as *const _) } + { + *dirent = val; + } + + let reclen = dirent.d_reclen as usize; + + if reclen == 0 { + error!(&ctx, "Read a dent with reclen=0 in getdents handler."); + break; + } + + data.num_dirents += 1; + offset += reclen; + } + } + } syscalls::SYS_readlinkat => { let data = data_mut!(entry, readlinkat); data.dirfd = args[0] as i32; @@ -840,7 +879,7 @@ pub fn syscall_exit_filesystem(ctx: TracePointContext) -> u32 { data.argp = args[1] as u64; data.resp = args[2] as u64; } - #[cfg(target_arch = "x86_64")] + #[cfg(x86_64)] syscalls::SYS_utime => { let data = data_mut!(entry, utime); @@ -865,6 +904,179 @@ pub fn syscall_exit_filesystem(ctx: TracePointContext) -> u32 { } } } + #[cfg(x86_64)] + syscalls::SYS_access => { + let data = data_mut!(entry, access); + + let pathname_ptr = args[0] as *const u8; + data.mode = args[1] as i32; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_chmod => { + let data = data_mut!(entry, chmod); + + let pathname_ptr = args[0] as *const u8; + data.mode = args[1] as u32; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_creat => { + let data = data_mut!(entry, creat); + + let pathname_ptr = args[0] as *const u8; + data.mode = args[1] as u32; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_mkdir => { + let data = data_mut!(entry, mkdir); + + let pathname_ptr = args[0] as *const u8; + data.mode = args[1] as u32; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_readlink => { + let data = data_mut!(entry, readlink); + + let pathname_ptr = args[0] as *const u8; + let buf_ptr = args[1] as *const u8; + data.bufsiz = args[2] as u64; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + + if !buf_ptr.is_null() && return_value > 0 { + unsafe { + let _ = bpf_probe_read_buf(buf_ptr, &mut data.buf); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_stat => { + let data = data_mut!(entry, stat); + + let pathname_ptr = args[0] as *const u8; + let statbuf_ptr = args[1] as *const u8; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + + if !statbuf_ptr.is_null() && return_value == 0 { + unsafe { + let _ = bpf_probe_read_buf( + statbuf_ptr, + core::slice::from_raw_parts_mut( + &mut data.statbuf as *mut _ as *mut u8, + core::mem::size_of::(), + ), + ); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_lstat => { + let data = data_mut!(entry, lstat); + + let pathname_ptr = args[0] as *const u8; + let statbuf_ptr = args[1] as *const u8; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + + if !statbuf_ptr.is_null() && return_value == 0 { + unsafe { + let _ = bpf_probe_read_buf( + statbuf_ptr, + core::slice::from_raw_parts_mut( + &mut data.statbuf as *mut _ as *mut u8, + core::mem::size_of::(), + ), + ); + } + } + } + #[cfg(x86_64)] + syscalls::SYS_utimes => { + let data = data_mut!(entry, utimes); + + let filename_ptr = args[0] as *const u8; + let times_ptr = args[1] as *const [pinchy_common::kernel_types::Timeval; 2]; + + if !filename_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(filename_ptr, &mut data.filename); + } + } + + if times_ptr.is_null() { + data.times_is_null = 1; + } else { + data.times_is_null = 0; + + unsafe { + if let Ok(times) = bpf_probe_read_user(times_ptr) { + data.times = times; + } + } + } + } + #[cfg(x86_64)] + syscalls::SYS_futimesat => { + let data = data_mut!(entry, futimesat); + + data.dirfd = args[0] as i32; + let pathname_ptr = args[1] as *const u8; + let times_ptr = args[2] as *const [pinchy_common::kernel_types::Timeval; 2]; + + if !pathname_ptr.is_null() { + unsafe { + let _ = bpf_probe_read_buf(pathname_ptr, &mut data.pathname); + } + } + + if times_ptr.is_null() { + data.times_is_null = 1; + } else { + data.times_is_null = 0; + + unsafe { + if let Ok(times) = bpf_probe_read_user(times_ptr) { + data.times = times; + } + } + } + } _ => { entry.discard(); return Ok(()); diff --git a/pinchy-ebpf/src/ipc.rs b/pinchy-ebpf/src/ipc.rs index 825dea5..36acd61 100644 --- a/pinchy-ebpf/src/ipc.rs +++ b/pinchy-ebpf/src/ipc.rs @@ -99,6 +99,38 @@ pub fn syscall_exit_ipc(ctx: TracePointContext) -> u32 { data.sops = args[1]; data.nsops = args[2] as usize; } + syscalls::SYS_semtimedop => { + let data = data_mut!(entry, semtimedop); + + data.semid = args[0] as i32; + data.nsops = args[2] as usize; + + let sops_ptr = args[1] as *const kernel_types::Sembuf; + + for (i, sop) in data.sops.iter_mut().enumerate() { + if i < data.nsops && i < 4 { + unsafe { + if let Ok(val) = + bpf_probe_read_user::(sops_ptr.add(i)) + { + *sop = val; + } + } + } + } + + let timeout_ptr = args[3] as *const kernel_types::Timespec; + + if timeout_ptr.is_null() { + data.timeout_is_null = 1; + } else { + data.timeout_is_null = 0; + data.timeout = unsafe { + bpf_probe_read_user::(timeout_ptr) + .unwrap_or(kernel_types::Timespec::default()) + }; + } + } syscalls::SYS_semctl => { let data = data_mut!(entry, semctl); data.semid = args[0] as i32; diff --git a/pinchy-ebpf/src/main.rs b/pinchy-ebpf/src/main.rs index 163089c..690978d 100644 --- a/pinchy-ebpf/src/main.rs +++ b/pinchy-ebpf/src/main.rs @@ -590,6 +590,8 @@ pub fn syscall_exit_trivial(ctx: TracePointContext) -> u32 { | syscalls::SYS_setsid | syscalls::SYS_munlockall | syscalls::SYS_vhangup => {} + #[cfg(x86_64)] + syscalls::SYS_fork | syscalls::SYS_vfork => {} syscalls::SYS_set_mempolicy_home_node => { let data = unsafe { &mut entry.data.set_mempolicy_home_node }; data.start = args[0] as u64; diff --git a/pinchy/src/events.rs b/pinchy/src/events.rs index f0c7205..9af8ec7 100644 --- a/pinchy/src/events.rs +++ b/pinchy/src/events.rs @@ -78,7 +78,11 @@ pub async fn handle_event(event: &SyscallEvent, formatter: Formatter<'_>) -> any finish!(sf, event.return_value); } #[cfg(target_arch = "x86_64")] - syscalls::SYS_pause | syscalls::SYS_inotify_init | syscalls::SYS_getpgrp => { + syscalls::SYS_pause + | syscalls::SYS_inotify_init + | syscalls::SYS_getpgrp + | syscalls::SYS_fork + | syscalls::SYS_vfork => { finish!(sf, event.return_value); } syscalls::SYS_capget | syscalls::SYS_capset => { @@ -1718,6 +1722,77 @@ pub async fn handle_event(event: &SyscallEvent, formatter: Formatter<'_>) -> any )); } } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_getdents => { + let data = unsafe { event.data.getdents }; + + argf!(sf, "fd: {}", data.fd); + argf!(sf, "count: {}", data.count); + arg!(sf, "entries:"); + + with_array!(sf, { + for dirent in data.dirents.iter().take(data.num_dirents as usize) { + arg!(sf, "dirent"); + with_struct!(sf, { + argf!(sf, "ino: {}", dirent.d_ino); + argf!(sf, "off: {}", dirent.d_off); + argf!(sf, "reclen: {}", dirent.d_reclen); + argf!(sf, "name: {}", format_path(&dirent.d_name, false)); + }); + } + }); + + finish!(sf, event.return_value); + } + syscalls::SYS_semtimedop => { + let data = unsafe { event.data.semtimedop }; + + argf!(sf, "semid: {}", data.semid); + arg!(sf, "sops:"); + + with_array!(sf, { + for sop in data.sops.iter().take(core::cmp::min(data.nsops, 4)) { + arg!(sf, "sembuf"); + with_struct!(sf, { + argf!(sf, "sem_num: {}", sop.sem_num); + argf!(sf, "sem_op: {}", sop.sem_op); + argf!(sf, "sem_flg: 0x{:x}", sop.sem_flg); + }); + } + }); + + argf!(sf, "nsops: {}", data.nsops); + + if data.timeout_is_null != 0 { + argf!(sf, "timeout: NULL"); + } else { + argf!( + sf, + "timeout: {{tv_sec: {}, tv_nsec: {}}}", + data.timeout.seconds, + data.timeout.nanos + ); + } + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_sendfile => { + let data = unsafe { event.data.sendfile }; + + argf!(sf, "out_fd: {}", data.out_fd); + argf!(sf, "in_fd: {}", data.in_fd); + + if data.offset_is_null != 0 { + argf!(sf, "offset: NULL"); + } else { + argf!(sf, "offset: {}", data.offset); + } + + argf!(sf, "count: {}", data.count); + + finish!(sf, event.return_value); + } syscalls::SYS_mmap => { let data = unsafe { event.data.mmap }; @@ -4703,6 +4778,134 @@ pub async fn handle_event(event: &SyscallEvent, formatter: Formatter<'_>) -> any finish!(sf, event.return_value); } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_access => { + let data = unsafe { event.data.access }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + argf!(sf, "mode: {}", format_access_mode(data.mode)); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_chmod => { + let data = unsafe { event.data.chmod }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + argf!(sf, "mode: {}", format_mode(data.mode)); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_creat => { + let data = unsafe { event.data.creat }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + argf!(sf, "mode: {}", format_mode(data.mode)); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_mkdir => { + let data = unsafe { event.data.mkdir }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + argf!(sf, "mode: {}", format_mode(data.mode)); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_readlink => { + let data = unsafe { event.data.readlink }; + + let pathname = format_path(&data.pathname, false); + let buf = format_path(&data.buf, false); + + argf!(sf, "pathname: {}", pathname); + argf!(sf, "buf: {}", buf); + argf!(sf, "bufsiz: {}", data.bufsiz); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_stat => { + let data = unsafe { event.data.stat }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + arg!(sf, "statbuf:"); + format_stat(&mut sf, &data.statbuf).await?; + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_lstat => { + let data = unsafe { event.data.lstat }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "pathname: {}", pathname); + arg!(sf, "statbuf:"); + format_stat(&mut sf, &data.statbuf).await?; + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_utimes => { + let data = unsafe { event.data.utimes }; + + let filename = format_path(&data.filename, false); + + argf!(sf, "filename: {}", filename); + + if data.times_is_null != 0 { + argf!(sf, "times: NULL"); + } else { + argf!( + sf, + "times: [{{tv_sec: {}, tv_usec: {}}}, {{tv_sec: {}, tv_usec: {}}}]", + data.times[0].tv_sec, + data.times[0].tv_usec, + data.times[1].tv_sec, + data.times[1].tv_usec + ); + } + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_futimesat => { + let data = unsafe { event.data.futimesat }; + + let pathname = format_path(&data.pathname, false); + + argf!(sf, "dirfd: {}", format_dirfd(data.dirfd)); + argf!(sf, "pathname: {}", pathname); + + if data.times_is_null != 0 { + argf!(sf, "times: NULL"); + } else { + argf!( + sf, + "times: [{{tv_sec: {}, tv_usec: {}}}, {{tv_sec: {}, tv_usec: {}}}]", + data.times[0].tv_sec, + data.times[0].tv_usec, + data.times[1].tv_sec, + data.times[1].tv_usec + ); + } + + finish!(sf, event.return_value); + } _ => { let data = unsafe { event.data.generic }; diff --git a/pinchy/src/format_helpers.rs b/pinchy/src/format_helpers.rs index 16dabad..da334d1 100644 --- a/pinchy/src/format_helpers.rs +++ b/pinchy/src/format_helpers.rs @@ -2336,6 +2336,15 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C } } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_readlink | syscalls::SYS_getdents => { + if return_value >= 0 { + std::borrow::Cow::Owned(format!("{} (bytes)", return_value)) + } else { + std::borrow::Cow::Owned(format!("{} (error)", return_value)) + } + } + // Message count returning syscalls syscalls::SYS_recvmmsg | syscalls::SYS_sendmmsg => { if return_value >= 0 { @@ -2445,6 +2454,7 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C | syscalls::SYS_shmdt | syscalls::SYS_msgsnd | syscalls::SYS_semop + | syscalls::SYS_semtimedop | syscalls::SYS_acct | syscalls::SYS_getcpu | syscalls::SYS_shutdown @@ -2535,7 +2545,9 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C | syscalls::SYS_mknod | syscalls::SYS_stat | syscalls::SYS_lstat - | syscalls::SYS_utime => match return_value { + | syscalls::SYS_utime + | syscalls::SYS_utimes + | syscalls::SYS_futimesat => match return_value { 0 => std::borrow::Cow::Borrowed("0 (success)"), _ => std::borrow::Cow::Owned(format!("{return_value} (error)")), }, @@ -2565,6 +2577,14 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C } } + // fork/vfork return child PID in parent, 0 in child + #[cfg(target_arch = "x86_64")] + syscalls::SYS_fork | syscalls::SYS_vfork => match return_value { + 0 => std::borrow::Cow::Borrowed("0 (child)"), + v if v > 0 => std::borrow::Cow::Owned(format!("{v} (child pid)")), + _ => std::borrow::Cow::Owned(format!("{return_value} (error)")), + }, + // PID returning syscalls syscalls::SYS_getpid | syscalls::SYS_getppid @@ -2590,15 +2610,6 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C } } - #[cfg(target_arch = "x86_64")] - syscalls::SYS_fork | syscalls::SYS_vfork => { - if return_value >= 0 { - std::borrow::Cow::Owned(format!("{} (pid)", return_value)) - } else { - std::borrow::Cow::Owned(format!("{} (error)", return_value)) - } - } - // UID/GID returning syscalls syscalls::SYS_getuid | syscalls::SYS_geteuid diff --git a/pinchy/src/server.rs b/pinchy/src/server.rs index bb4307e..9af00bd 100644 --- a/pinchy/src/server.rs +++ b/pinchy/src/server.rs @@ -444,6 +444,10 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_inotify_rm_watch, syscalls::SYS_timer_delete, syscalls::SYS_timer_getoverrun, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_fork, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_vfork, ]; for &syscall_nr in TRIVIAL_SYSCALLS { prog_array.set(syscall_nr as u32, prog.fd()?, 0)?; @@ -521,6 +525,8 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_fstat, syscalls::SYS_newfstatat, syscalls::SYS_getdents64, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_getdents, syscalls::SYS_statfs, syscalls::SYS_fstatfs, syscalls::SYS_fsopen, @@ -596,6 +602,24 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_nfsservctl, #[cfg(target_arch = "x86_64")] syscalls::SYS_utime, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_access, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_chmod, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_creat, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_mkdir, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_readlink, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_stat, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_lstat, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_utimes, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_futimesat, ]; let filesystem_prog: &mut aya::programs::TracePoint = ebpf .program_mut("syscall_exit_filesystem") @@ -637,6 +661,8 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_pipe2, syscalls::SYS_splice, syscalls::SYS_tee, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_sendfile, syscalls::SYS_vmsplice, // Async I/O syscalls syscalls::SYS_io_setup, @@ -764,6 +790,7 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_msgctl, syscalls::SYS_semget, syscalls::SYS_semop, + syscalls::SYS_semtimedop, syscalls::SYS_semctl, syscalls::SYS_mq_open, syscalls::SYS_mq_unlink, diff --git a/pinchy/src/tests/basic_io.rs b/pinchy/src/tests/basic_io.rs index b8f0bb3..8481465 100644 --- a/pinchy/src/tests/basic_io.rs +++ b/pinchy/src/tests/basic_io.rs @@ -25,8 +25,8 @@ use pinchy_common::{ }; #[cfg(target_arch = "x86_64")] use pinchy_common::{ - syscalls::{SYS_dup2, SYS_epoll_create, SYS_poll}, - Dup2Data, EpollCreateData, PollData, + syscalls::{SYS_dup2, SYS_epoll_create, SYS_poll, SYS_sendfile}, + Dup2Data, EpollCreateData, PollData, SendfileData, }; use crate::{ @@ -1186,3 +1186,49 @@ syscall_test!( }, "444 io_uring_register(fd: 4, opcode: IORING_REGISTER_PROBE, arg: 0x7ffeabcd3000, nr_args: 0x80000004 (4|IORING_REGISTER_USE_REGISTERED_RING)) = 0 (success)\n" ); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_sendfile_with_offset, + { + SyscallEvent { + syscall_nr: SYS_sendfile, + pid: 400, + tid: 400, + return_value: 1024, + data: SyscallEventData { + sendfile: SendfileData { + out_fd: 4, + in_fd: 3, + offset: 512, + offset_is_null: 0, + count: 2048, + }, + }, + } + }, + "400 sendfile(out_fd: 4, in_fd: 3, offset: 512, count: 2048) = 1024 (bytes)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_sendfile_null_offset, + { + SyscallEvent { + syscall_nr: SYS_sendfile, + pid: 401, + tid: 401, + return_value: 4096, + data: SyscallEventData { + sendfile: SendfileData { + out_fd: 5, + in_fd: 3, + offset: 0, + offset_is_null: 1, + count: 8192, + }, + }, + } + }, + "401 sendfile(out_fd: 5, in_fd: 3, offset: NULL, count: 8192) = 4096 (bytes)\n" +); diff --git a/pinchy/src/tests/filesystem.rs b/pinchy/src/tests/filesystem.rs index 657502e..3616eca 100644 --- a/pinchy/src/tests/filesystem.rs +++ b/pinchy/src/tests/filesystem.rs @@ -24,7 +24,15 @@ use pinchy_common::{ SyscallEventData, UtimensatData, DATA_READ_SIZE, MEDIUM_READ_SIZE, SMALLISH_READ_SIZE, }; #[cfg(target_arch = "x86_64")] -use pinchy_common::{syscalls::SYS_utime, UtimeData}; +use pinchy_common::{ + kernel_types::{LinuxDirent, Stat}, + syscalls::{ + SYS_access, SYS_chmod, SYS_creat, SYS_futimesat, SYS_getdents, SYS_lstat, SYS_mkdir, + SYS_readlink, SYS_stat, SYS_utime, SYS_utimes, + }, + AccessData, ChmodData, CreatData, FutimesatData, GetdentsData, LstatData, MkdirData, + ReadlinkData, StatData, UtimeData, UtimesData, +}; use crate::syscall_test; @@ -3518,3 +3526,385 @@ syscall_test!( }, "123 utime(filename: \"/tmp/bar\", times: NULL) = 0 (success)\n" ); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_access_success, + { + SyscallEvent { + syscall_nr: SYS_access, + pid: 123, + tid: 123, + return_value: 0, + data: SyscallEventData { + access: AccessData { + pathname: *b"/tmp/foo", + mode: libc::R_OK | libc::W_OK, + }, + }, + } + }, + "123 access(pathname: \"/tmp/foo\" ... (truncated), mode: R_OK|W_OK) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_access_error, + { + SyscallEvent { + syscall_nr: SYS_access, + pid: 124, + tid: 124, + return_value: -1, + data: SyscallEventData { + access: AccessData { + pathname: *b"/root/.s", + mode: libc::X_OK, + }, + }, + } + }, + "124 access(pathname: \"/root/.s\" ... (truncated), mode: X_OK) = -1 (error)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_access_f_ok, + { + SyscallEvent { + syscall_nr: SYS_access, + pid: 125, + tid: 125, + return_value: 0, + data: SyscallEventData { + access: AccessData { + pathname: *b"/tmp/tes", + mode: libc::F_OK, + }, + }, + } + }, + "125 access(pathname: \"/tmp/tes\" ... (truncated), mode: F_OK) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_chmod_success, + { + SyscallEvent { + syscall_nr: SYS_chmod, + pid: 126, + tid: 126, + return_value: 0, + data: SyscallEventData { + chmod: ChmodData { + pathname: *b"/tmp/foo", + mode: 0o755, + }, + }, + } + }, + "126 chmod(pathname: \"/tmp/foo\" ... (truncated), mode: 0o755 (rwxr-xr-x)) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_creat_success, + { + SyscallEvent { + syscall_nr: SYS_creat, + pid: 127, + tid: 127, + return_value: 3, + data: SyscallEventData { + creat: CreatData { + pathname: *b"/tmp/new", + mode: 0o644, + }, + }, + } + }, + "127 creat(pathname: \"/tmp/new\" ... (truncated), mode: 0o644 (rw-r--r--)) = 3 (fd)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_mkdir_success, + { + SyscallEvent { + syscall_nr: SYS_mkdir, + pid: 128, + tid: 128, + return_value: 0, + data: SyscallEventData { + mkdir: MkdirData { + pathname: *b"/tmp/dir", + mode: 0o755, + }, + }, + } + }, + "128 mkdir(pathname: \"/tmp/dir\" ... (truncated), mode: 0o755 (rwxr-xr-x)) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_readlink_success, + { + SyscallEvent { + syscall_nr: SYS_readlink, + pid: 128, + tid: 128, + return_value: 8, + data: SyscallEventData { + readlink: ReadlinkData { + pathname: *b"/tmp/lnk", + buf: *b"/tmp/foo\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + bufsiz: 64, + }, + }, + } + }, + "128 readlink(pathname: \"/tmp/lnk\" ... (truncated), buf: \"/tmp/foo\", bufsiz: 64) = 8 (bytes)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_readlink_error, + { + SyscallEvent { + syscall_nr: SYS_readlink, + pid: 129, + tid: 129, + return_value: -1, + data: SyscallEventData { + readlink: ReadlinkData { + pathname: *b"/tmp/not", + buf: [0; MEDIUM_READ_SIZE], + bufsiz: 64, + }, + }, + } + }, + "129 readlink(pathname: \"/tmp/not\" ... (truncated), buf: \"\", bufsiz: 64) = -1 (error)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_stat_success, + { + let mut event = SyscallEvent { + syscall_nr: SYS_stat, + pid: 130, + tid: 130, + return_value: 0, + data: SyscallEventData { + stat: StatData { + pathname: *b"/tmp/foo", + statbuf: Stat::default(), + }, + }, + }; + + let stat_data = unsafe { &mut event.data.stat.statbuf }; + + stat_data.st_mode = libc::S_IFREG | 0o644; + stat_data.st_size = 1024; + stat_data.st_uid = 1000; + stat_data.st_gid = 1000; + stat_data.st_blocks = 8; + stat_data.st_blksize = 4096; + stat_data.st_ino = 12345; + event + }, + &"130 stat(pathname: \"/tmp/foo\" ... (truncated), statbuf:, mode: 0o644 (rw-r--r--), ino: 12345, dev: 0, nlink: 0, uid: 1000, gid: 1000, size: 1024, blksize: 4096, blocks: 8, atime: 0, mtime: 0, ctime: 0) = 0 (success)\n".to_string() +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_lstat_success, + { + let mut event = SyscallEvent { + syscall_nr: SYS_lstat, + pid: 131, + tid: 131, + return_value: 0, + data: SyscallEventData { + lstat: LstatData { + pathname: *b"/tmp/lnk", + statbuf: Stat::default(), + }, + }, + }; + + let stat_data = unsafe { &mut event.data.lstat.statbuf }; + + stat_data.st_mode = libc::S_IFLNK | 0o777; + stat_data.st_size = 8; + stat_data.st_uid = 1000; + stat_data.st_gid = 1000; + stat_data.st_blocks = 0; + stat_data.st_blksize = 4096; + stat_data.st_ino = 54321; + event + }, + &"131 lstat(pathname: \"/tmp/lnk\" ... (truncated), statbuf:, mode: 0o777 (rwxrwxrwx), ino: 54321, dev: 0, nlink: 0, uid: 1000, gid: 1000, size: 8, blksize: 4096, blocks: 0, atime: 0, mtime: 0, ctime: 0) = 0 (success)\n".to_string() +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_utimes_with_times, + { + use pinchy_common::kernel_types::Timeval; + + SyscallEvent { + syscall_nr: SYS_utimes, + pid: 132, + tid: 132, + return_value: 0, + data: SyscallEventData { + utimes: UtimesData { + filename: *b"/tmp/foo", + times: [ + Timeval { + tv_sec: 1234567890, + tv_usec: 123456, + }, + Timeval { + tv_sec: 1234567900, + tv_usec: 654321, + }, + ], + times_is_null: 0, + }, + }, + } + }, + "132 utimes(filename: \"/tmp/foo\" ... (truncated), times: [{tv_sec: 1234567890, tv_usec: 123456}, {tv_sec: 1234567900, tv_usec: 654321}]) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_utimes_null, + { + use pinchy_common::kernel_types::Timeval; + + SyscallEvent { + syscall_nr: SYS_utimes, + pid: 133, + tid: 133, + return_value: 0, + data: SyscallEventData { + utimes: UtimesData { + filename: *b"/tmp/bar", + times: [Timeval::default(), Timeval::default()], + times_is_null: 1, + }, + }, + } + }, + "133 utimes(filename: \"/tmp/bar\" ... (truncated), times: NULL) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_futimesat_with_times, + { + use pinchy_common::kernel_types::Timeval; + + SyscallEvent { + syscall_nr: SYS_futimesat, + pid: 134, + tid: 134, + return_value: 0, + data: SyscallEventData { + futimesat: FutimesatData { + dirfd: libc::AT_FDCWD, + pathname: *b"/tmp/foo", + times: [ + Timeval { + tv_sec: 1111111111, + tv_usec: 111111, + }, + Timeval { + tv_sec: 2222222222, + tv_usec: 222222, + }, + ], + times_is_null: 0, + }, + }, + } + }, + "134 futimesat(dirfd: AT_FDCWD, pathname: \"/tmp/foo\" ... (truncated), times: [{tv_sec: 1111111111, tv_usec: 111111}, {tv_sec: 2222222222, tv_usec: 222222}]) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_futimesat_fd, + { + use pinchy_common::kernel_types::Timeval; + + SyscallEvent { + syscall_nr: SYS_futimesat, + pid: 135, + tid: 135, + return_value: 0, + data: SyscallEventData { + futimesat: FutimesatData { + dirfd: 5, + pathname: *b"/tmp/foo", + times: [ + Timeval { + tv_sec: 3333333333, + tv_usec: 333333, + }, + Timeval { + tv_sec: 4444444444, + tv_usec: 444444, + }, + ], + times_is_null: 0, + }, + }, + } + }, + "135 futimesat(dirfd: 5, pathname: \"/tmp/foo\" ... (truncated), times: [{tv_sec: 3333333333, tv_usec: 333333}, {tv_sec: 4444444444, tv_usec: 444444}]) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_getdents_success, + { + let mut event = SyscallEvent { + syscall_nr: SYS_getdents, + pid: 200, + tid: 200, + return_value: 48, + data: SyscallEventData { + getdents: GetdentsData { + fd: 3, + count: 1024, + dirents: [LinuxDirent::default(); 4], + num_dirents: 2, + }, + }, + }; + + let data = unsafe { &mut event.data.getdents }; + + data.dirents[0].d_ino = 12345; + data.dirents[0].d_off = 24; + data.dirents[0].d_reclen = 24; + data.dirents[0].d_name[0] = b'.'; + + data.dirents[1].d_ino = 12346; + data.dirents[1].d_off = 48; + data.dirents[1].d_reclen = 24; + data.dirents[1].d_name[0] = b'.'; + data.dirents[1].d_name[1] = b'.'; + + event + }, + "200 getdents(fd: 3, count: 1024, entries: [ dirent { ino: 12345, off: 24, reclen: 24, name: \".\" }, dirent { ino: 12346, off: 48, reclen: 24, name: \"..\" } ]) = 48 (bytes)\n" +); diff --git a/pinchy/src/tests/ipc.rs b/pinchy/src/tests/ipc.rs index ff30aac..696e33e 100644 --- a/pinchy/src/tests/ipc.rs +++ b/pinchy/src/tests/ipc.rs @@ -866,3 +866,76 @@ syscall_test!( }, "2468 mq_getsetattr(mqdes: 99, newattr: NULL, oldattr: NULL) = -1 (error)\n" ); + +syscall_test!( + parse_semtimedop_success, + { + use pinchy_common::kernel_types::{Sembuf, Timespec}; + + SyscallEvent { + syscall_nr: pinchy_common::syscalls::SYS_semtimedop, + pid: 300, + tid: 300, + return_value: 0, + data: SyscallEventData { + semtimedop: pinchy_common::SemtimedopData { + semid: 123, + sops: [ + Sembuf { + sem_num: 0, + sem_op: -1, + sem_flg: 0, + }, + Sembuf { + sem_num: 1, + sem_op: 1, + sem_flg: libc::IPC_NOWAIT as i16, + }, + Sembuf::default(), + Sembuf::default(), + ], + nsops: 2, + timeout: Timespec { + seconds: 5, + nanos: 500000000, + }, + timeout_is_null: 0, + }, + }, + } + }, + "300 semtimedop(semid: 123, sops: [ sembuf { sem_num: 0, sem_op: -1, sem_flg: 0x0 }, sembuf { sem_num: 1, sem_op: 1, sem_flg: 0x800 } ], nsops: 2, timeout: {tv_sec: 5, tv_nsec: 500000000}) = 0 (success)\n" +); + +syscall_test!( + parse_semtimedop_null_timeout, + { + use pinchy_common::kernel_types::{Sembuf, Timespec}; + + SyscallEvent { + syscall_nr: pinchy_common::syscalls::SYS_semtimedop, + pid: 301, + tid: 301, + return_value: 0, + data: SyscallEventData { + semtimedop: pinchy_common::SemtimedopData { + semid: 456, + sops: [ + Sembuf { + sem_num: 0, + sem_op: 1, + sem_flg: 0, + }, + Sembuf::default(), + Sembuf::default(), + Sembuf::default(), + ], + nsops: 1, + timeout: Timespec::default(), + timeout_is_null: 1, + }, + }, + } + }, + "301 semtimedop(semid: 456, sops: [ sembuf { sem_num: 0, sem_op: 1, sem_flg: 0x0 } ], nsops: 1, timeout: NULL) = 0 (success)\n" +); diff --git a/pinchy/src/tests/process.rs b/pinchy/src/tests/process.rs index f1ec82a..710bebe 100644 --- a/pinchy/src/tests/process.rs +++ b/pinchy/src/tests/process.rs @@ -2,9 +2,7 @@ // Copyright (c) 2025 Gustavo Noronha Silva #[cfg(target_arch = "x86_64")] -use pinchy_common::syscalls::SYS_getpgrp; -#[cfg(target_arch = "x86_64")] -use pinchy_common::GetpgrpData; +use pinchy_common::syscalls::{SYS_fork, SYS_getpgrp, SYS_vfork}; use pinchy_common::{ kernel_types::CloneArgs, syscalls::{ @@ -20,6 +18,8 @@ use pinchy_common::{ SetnsData, SetpgidData, SetregidData, SetresgidData, SetresuidData, SetreuidData, SetsidData, SetuidData, SyscallEvent, SyscallEventData, UnshareData, SMALL_READ_SIZE, }; +#[cfg(target_arch = "x86_64")] +use pinchy_common::{ForkData, GetpgrpData, VforkData}; use crate::{format_helpers::kcmp_constants, syscall_test}; @@ -1273,3 +1273,93 @@ syscall_test!( }, "5000 getresgid(rgid: 1000, egid: 1000, sgid: 1000) = 0 (success)\n" ); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_fork_parent, + { + SyscallEvent { + syscall_nr: SYS_fork, + pid: 1000, + tid: 1000, + return_value: 1001, + data: SyscallEventData { fork: ForkData }, + } + }, + "1000 fork() = 1001 (child pid)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_fork_child, + { + SyscallEvent { + syscall_nr: SYS_fork, + pid: 1001, + tid: 1001, + return_value: 0, + data: SyscallEventData { fork: ForkData }, + } + }, + "1001 fork() = 0 (child)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_fork_error, + { + SyscallEvent { + syscall_nr: SYS_fork, + pid: 1000, + tid: 1000, + return_value: -1, + data: SyscallEventData { fork: ForkData }, + } + }, + "1000 fork() = -1 (error)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_vfork_parent, + { + SyscallEvent { + syscall_nr: SYS_vfork, + pid: 2000, + tid: 2000, + return_value: 2001, + data: SyscallEventData { vfork: VforkData }, + } + }, + "2000 vfork() = 2001 (child pid)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_vfork_child, + { + SyscallEvent { + syscall_nr: SYS_vfork, + pid: 2001, + tid: 2001, + return_value: 0, + data: SyscallEventData { vfork: VforkData }, + } + }, + "2001 vfork() = 0 (child)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + test_vfork_error, + { + SyscallEvent { + syscall_nr: SYS_vfork, + pid: 2000, + tid: 2000, + return_value: -1, + data: SyscallEventData { vfork: VforkData }, + } + }, + "2000 vfork() = -1 (error)\n" +);