From 6c50b4b4213c20054f392549e35df60914952a70 Mon Sep 17 00:00:00 2001 From: Gustavo Noronha Silva Date: Tue, 30 Dec 2025 21:52:38 -0300 Subject: [PATCH] Add support for legacy filesystem syscalls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements lookup_dcookie, nfsservctl, and utime lookup_dcookie: - Converts directory entry cookie to path - Arguments: cookie (u64), buffer pointer, buffer size - Returns path length on success - Used primarily by oprofile profiler - Requires CAP_SYS_ADMIN capability nfsservctl: - Obsolete NFS daemon control syscall (removed in Linux 3.1) - Arguments: cmd, argp pointer, resp pointer - Returns 0 on success - Replaced by nfsd filesystem interface utime (x86_64 only): - Change file access and modification times - Arguments: pathname, times pointer (or NULL) - times is a Utimbuf struct with actime and modtime fields - NULL times sets both timestamps to current time - Returns 0 on success Added Utimbuf struct to kernel_types.rs for utime syscall. Added 5 pretty printing tests covering all three syscalls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- pinchy-common/src/kernel_types.rs | 8 ++ pinchy-common/src/lib.rs | 27 ++++++ pinchy-ebpf/src/filesystem.rs | 44 ++++++++++ pinchy/src/events.rs | 42 +++++++++ pinchy/src/format_helpers.rs | 9 +- pinchy/src/server.rs | 4 + pinchy/src/tests/filesystem.rs | 139 ++++++++++++++++++++++++++++-- 7 files changed, 262 insertions(+), 11 deletions(-) diff --git a/pinchy-common/src/kernel_types.rs b/pinchy-common/src/kernel_types.rs index 66d3051..0d71589 100644 --- a/pinchy-common/src/kernel_types.rs +++ b/pinchy-common/src/kernel_types.rs @@ -45,6 +45,14 @@ pub struct Timespec { pub nanos: i64, } +/// Structure for utime syscall +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Utimbuf { + pub actime: i64, // Access time + pub modtime: i64, // Modification time +} + /// Structure for openat2 syscall, matching the kernel's struct open_how /// See: https://man7.org/linux/man-pages/man2/openat2.2.html #[repr(C)] diff --git a/pinchy-common/src/lib.rs b/pinchy-common/src/lib.rs index 45bb61a..66399ee 100644 --- a/pinchy-common/src/lib.rs +++ b/pinchy-common/src/lib.rs @@ -414,6 +414,9 @@ pub union SyscallEventData { pub remap_file_pages: RemapFilePagesData, pub restart_syscall: RestartSyscallData, pub kexec_load: KexecLoadData, + pub lookup_dcookie: LookupDcookieData, + pub nfsservctl: NfsservctlData, + pub utime: UtimeData, } #[repr(C)] @@ -3424,3 +3427,27 @@ pub struct KexecLoadData { pub segments_read: u64, pub parsed_segments: [kernel_types::KexecSegment; kernel_types::KEXEC_SEGMENT_ARRAY_CAP], } + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LookupDcookieData { + pub cookie: u64, + pub buffer: [u8; MEDIUM_READ_SIZE], + pub size: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct NfsservctlData { + pub cmd: i32, + pub argp: u64, + pub resp: u64, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UtimeData { + pub filename: [u8; DATA_READ_SIZE], + pub times: kernel_types::Utimbuf, + pub times_is_null: u8, +} diff --git a/pinchy-ebpf/src/filesystem.rs b/pinchy-ebpf/src/filesystem.rs index 7552d50..0077eb0 100644 --- a/pinchy-ebpf/src/filesystem.rs +++ b/pinchy-ebpf/src/filesystem.rs @@ -821,6 +821,50 @@ pub fn syscall_exit_filesystem(ctx: TracePointContext) -> u32 { data.id = args[2] as i32; data.addr = args[3] as u64; } + syscalls::SYS_lookup_dcookie => { + let data = data_mut!(entry, lookup_dcookie); + data.cookie = args[0] as u64; + data.size = args[2] as u64; + + let buffer_ptr = args[1] as *const u8; + + if !buffer_ptr.is_null() && return_value > 0 { + unsafe { + let _ = bpf_probe_read_buf(buffer_ptr, &mut data.buffer); + } + } + } + syscalls::SYS_nfsservctl => { + let data = data_mut!(entry, nfsservctl); + data.cmd = args[0] as i32; + data.argp = args[1] as u64; + data.resp = args[2] as u64; + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_utime => { + let data = data_mut!(entry, utime); + + let filename_ptr = args[0] as *const u8; + let times_ptr = args[1] as *const pinchy_common::kernel_types::Utimbuf; + + 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; + } + } + } + } _ => { entry.discard(); return Ok(()); diff --git a/pinchy/src/events.rs b/pinchy/src/events.rs index 7c2f3e7..f0c7205 100644 --- a/pinchy/src/events.rs +++ b/pinchy/src/events.rs @@ -4661,6 +4661,48 @@ pub async fn handle_event(event: &SyscallEvent, formatter: Formatter<'_>) -> any finish!(sf, event.return_value); } + syscalls::SYS_lookup_dcookie => { + let data = unsafe { event.data.lookup_dcookie }; + + argf!(sf, "cookie: {}", data.cookie); + + let buffer = format_path(&data.buffer, false); + + argf!(sf, "buffer: {}", buffer); + argf!(sf, "size: {}", data.size); + + finish!(sf, event.return_value); + } + syscalls::SYS_nfsservctl => { + let data = unsafe { event.data.nfsservctl }; + + argf!(sf, "cmd: {}", data.cmd); + argf!(sf, "argp: 0x{:x}", data.argp); + argf!(sf, "resp: 0x{:x}", data.resp); + + finish!(sf, event.return_value); + } + #[cfg(target_arch = "x86_64")] + syscalls::SYS_utime => { + let data = unsafe { event.data.utime }; + + 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: {{actime: {}, modtime: {}}}", + data.times.actime, + data.times.modtime + ); + } + + 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 0b6b95c..16dabad 100644 --- a/pinchy/src/format_helpers.rs +++ b/pinchy/src/format_helpers.rs @@ -2327,7 +2327,8 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C | syscalls::SYS_process_vm_readv | syscalls::SYS_process_vm_writev | syscalls::SYS_sched_getaffinity - | syscalls::SYS_copy_file_range => { + | syscalls::SYS_copy_file_range + | syscalls::SYS_lookup_dcookie => { if return_value >= 0 { std::borrow::Cow::Owned(format!("{return_value} (bytes)")) } else { @@ -2492,7 +2493,8 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C | syscalls::SYS_getresuid | syscalls::SYS_getresgid | syscalls::SYS_restart_syscall - | syscalls::SYS_kexec_load => match return_value { + | syscalls::SYS_kexec_load + | syscalls::SYS_nfsservctl => match return_value { 0 => std::borrow::Cow::Borrowed("0 (success)"), _ => std::borrow::Cow::Owned(format!("{return_value} (error)")), }, @@ -2532,7 +2534,8 @@ pub fn format_return_value(syscall_nr: i64, return_value: i64) -> std::borrow::C | syscalls::SYS_rename | syscalls::SYS_mknod | syscalls::SYS_stat - | syscalls::SYS_lstat => match return_value { + | syscalls::SYS_lstat + | syscalls::SYS_utime => match return_value { 0 => std::borrow::Cow::Borrowed("0 (success)"), _ => std::borrow::Cow::Owned(format!("{return_value} (error)")), }, diff --git a/pinchy/src/server.rs b/pinchy/src/server.rs index b3db851..bb4307e 100644 --- a/pinchy/src/server.rs +++ b/pinchy/src/server.rs @@ -592,6 +592,10 @@ fn load_tailcalls(ebpf: &mut Ebpf) -> anyhow::Result<()> { syscalls::SYS_utimensat, syscalls::SYS_quotactl, syscalls::SYS_quotactl_fd, + syscalls::SYS_lookup_dcookie, + syscalls::SYS_nfsservctl, + #[cfg(target_arch = "x86_64")] + syscalls::SYS_utime, ]; let filesystem_prog: &mut aya::programs::TracePoint = ebpf .program_mut("syscall_exit_filesystem") diff --git a/pinchy/src/tests/filesystem.rs b/pinchy/src/tests/filesystem.rs index d185265..657502e 100644 --- a/pinchy/src/tests/filesystem.rs +++ b/pinchy/src/tests/filesystem.rs @@ -11,18 +11,20 @@ use pinchy_common::{ self, SYS_acct, SYS_chdir, SYS_copy_file_range, SYS_faccessat, SYS_fallocate, SYS_fanotify_init, SYS_fanotify_mark, SYS_fchmod, SYS_fchmodat, SYS_fchown, SYS_fchownat, SYS_fdatasync, SYS_fstat, SYS_fsync, SYS_ftruncate, SYS_getcwd, SYS_getdents64, - SYS_inotify_add_watch, SYS_inotify_init1, SYS_inotify_rm_watch, SYS_linkat, SYS_mkdirat, - SYS_name_to_handle_at, SYS_newfstatat, SYS_open_by_handle_at, SYS_quotactl, - SYS_quotactl_fd, SYS_readlinkat, SYS_renameat, SYS_renameat2, SYS_statfs, - SYS_sync_file_range, SYS_syncfs, SYS_truncate, SYS_utimensat, + SYS_inotify_add_watch, SYS_inotify_init1, SYS_inotify_rm_watch, SYS_linkat, + SYS_lookup_dcookie, SYS_mkdirat, SYS_name_to_handle_at, SYS_newfstatat, SYS_nfsservctl, + SYS_open_by_handle_at, SYS_quotactl, SYS_quotactl_fd, SYS_readlinkat, SYS_renameat, + SYS_renameat2, SYS_statfs, SYS_sync_file_range, SYS_syncfs, SYS_truncate, SYS_utimensat, }, AcctData, CopyFileRangeData, FaccessatData, FallocateData, FanotifyInitData, FanotifyMarkData, FchmodData, FchmodatData, FchownData, FchownatData, FdatasyncData, FsyncData, FtruncateData, - InotifyAddWatchData, InotifyInit1Data, InotifyRmWatchData, LinkatData, MkdiratData, - MknodatData, NameToHandleAtData, OpenByHandleAtData, QuotactlFdData, Renameat2Data, - RenameatData, SyncFileRangeData, SyncfsData, SyscallEvent, SyscallEventData, UtimensatData, - DATA_READ_SIZE, MEDIUM_READ_SIZE, SMALLISH_READ_SIZE, + InotifyAddWatchData, InotifyInit1Data, InotifyRmWatchData, LinkatData, LookupDcookieData, + MkdiratData, MknodatData, NameToHandleAtData, NfsservctlData, OpenByHandleAtData, + QuotactlFdData, Renameat2Data, RenameatData, SyncFileRangeData, SyncfsData, SyscallEvent, + SyscallEventData, UtimensatData, DATA_READ_SIZE, MEDIUM_READ_SIZE, SMALLISH_READ_SIZE, }; +#[cfg(target_arch = "x86_64")] +use pinchy_common::{syscalls::SYS_utime, UtimeData}; use crate::syscall_test; @@ -3395,3 +3397,124 @@ syscall_test!( }, "10008 quotactl(op: 0x80000802 (QCMD(Q_SETQUOTA, PRJQUOTA)), special: \"/dev/sdc1\", id: 1001, addr: 0x7fff22222222) = 0 (success)\n" ); + +syscall_test!( + parse_lookup_dcookie_success, + { + SyscallEvent { + syscall_nr: SYS_lookup_dcookie, + pid: 123, + tid: 123, + return_value: 10, + data: SyscallEventData { + lookup_dcookie: LookupDcookieData { + cookie: 0x123456789abcdef0, + buffer: *b"/proc/sys\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", + size: 64, + }, + }, + } + }, + "123 lookup_dcookie(cookie: 1311768467463790320, buffer: \"/proc/sys\", size: 64) = 10 (bytes)\n" +); + +syscall_test!( + parse_lookup_dcookie_error, + { + SyscallEvent { + syscall_nr: SYS_lookup_dcookie, + pid: 123, + tid: 123, + return_value: -1, + data: SyscallEventData { + lookup_dcookie: LookupDcookieData { + cookie: 0x123456789abcdef0, + buffer: [0; MEDIUM_READ_SIZE], + size: 64, + }, + }, + } + }, + "123 lookup_dcookie(cookie: 1311768467463790320, buffer: \"\", size: 64) = -1 (error)\n" +); + +syscall_test!( + parse_nfsservctl_success, + { + SyscallEvent { + syscall_nr: SYS_nfsservctl, + pid: 123, + tid: 123, + return_value: 0, + data: SyscallEventData { + nfsservctl: NfsservctlData { + cmd: 1, + argp: 0x7fff1000, + resp: 0x7fff2000, + }, + }, + } + }, + "123 nfsservctl(cmd: 1, argp: 0x7fff1000, resp: 0x7fff2000) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_utime_with_times, + { + use pinchy_common::kernel_types::Utimbuf; + + SyscallEvent { + syscall_nr: SYS_utime, + pid: 123, + tid: 123, + return_value: 0, + data: SyscallEventData { + utime: UtimeData { + filename: { + let mut filename = [0u8; DATA_READ_SIZE]; + let name = b"/tmp/foo"; + filename[..name.len()].copy_from_slice(name); + + filename + }, + times: Utimbuf { + actime: 1234567890, + modtime: 1234567900, + }, + times_is_null: 0, + }, + }, + } + }, + "123 utime(filename: \"/tmp/foo\", times: {actime: 1234567890, modtime: 1234567900}) = 0 (success)\n" +); + +#[cfg(target_arch = "x86_64")] +syscall_test!( + parse_utime_null_times, + { + use pinchy_common::kernel_types::Utimbuf; + + SyscallEvent { + syscall_nr: SYS_utime, + pid: 123, + tid: 123, + return_value: 0, + data: SyscallEventData { + utime: UtimeData { + filename: { + let mut filename = [0u8; DATA_READ_SIZE]; + let name = b"/tmp/bar"; + filename[..name.len()].copy_from_slice(name); + + filename + }, + times: Utimbuf::default(), + times_is_null: 1, + }, + }, + } + }, + "123 utime(filename: \"/tmp/bar\", times: NULL) = 0 (success)\n" +);