diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8f6cc79..bd7346d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@master with: - toolchain: "1.80" + toolchain: "1.82" targets: ${{ matrix.target }} components: rustfmt - name: Install cross-compilation tools diff --git a/src/arch.rs b/src/arch.rs index bfe3b2e6..501d4a82 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -14,3 +14,14 @@ cfg_if! { compile_error!("unsupported architecture"); } } + +#[allow(unused)] +mod audit { + include!("arch/audit.rs"); +} + +pub use audit::*; + +pub trait RegsExt { + fn syscall_arg(&self, idx: usize, is_32bit: bool) -> usize; +} diff --git a/src/arch/aarch64.rs b/src/arch/aarch64.rs index c2709979..3b134f4a 100644 --- a/src/arch/aarch64.rs +++ b/src/arch/aarch64.rs @@ -1,40 +1,26 @@ use nix::libc::user_regs_struct; +use super::RegsExt; -pub type PtraceRegisters = user_regs_struct; +pub const NATIVE_AUDIT_ARCH: u32 = super::AUDIT_ARCH_AARCH64; +pub const HAS_32BIT: bool = false; -macro_rules! syscall_no_from_regs { - ($regs:ident) => { - $regs.regs[8] as i64 - }; +pub type Regs = user_regs_struct; +pub type RegsPayload = Regs; +#[repr(transparent)] +pub struct RegsRepr { + pub payload: RegsPayload, } -macro_rules! syscall_res_from_regs { - ($regs:ident) => { - $regs.regs[0] as i64 - }; +impl RegsExt for Regs { + fn syscall_arg(&self, idx: usize, _is_32bit: bool) -> usize { + (match idx { + 0 => self.regs[0], + 1 => self.regs[1], + 2 => self.regs[2], + 3 => self.regs[3], + 4 => self.regs[4], + 5 => self.regs[5], + _ => unimplemented!(), + } as usize) + } } - -macro_rules! syscall_arg { - ($regs:ident, 0) => { - $regs.regs[0] - }; - ($regs:ident, 1) => { - $regs.regs[1] - }; - ($regs:ident, 2) => { - $regs.regs[2] - }; - ($regs:ident, 3) => { - $regs.regs[3] - }; - ($regs:ident, 4) => { - $regs.regs[4] - }; - ($regs:ident, 5) => { - $regs.regs[5] - }; -} - -pub(crate) use syscall_arg; -pub(crate) use syscall_no_from_regs; -pub(crate) use syscall_res_from_regs; diff --git a/src/arch/audit.rs b/src/arch/audit.rs new file mode 100644 index 00000000..595edcdc --- /dev/null +++ b/src/arch/audit.rs @@ -0,0 +1,55 @@ +/* automatically generated by rust-bindgen 0.70.1 */ + +pub const AUDIT_ARCH_AARCH64: u32 = 3221225655; +pub const AUDIT_ARCH_ALPHA: u32 = 3221262374; +pub const AUDIT_ARCH_ARCOMPACT: u32 = 1073741917; +pub const AUDIT_ARCH_ARCOMPACTBE: u32 = 93; +pub const AUDIT_ARCH_ARCV2: u32 = 1073742019; +pub const AUDIT_ARCH_ARCV2BE: u32 = 195; +pub const AUDIT_ARCH_ARM: u32 = 1073741864; +pub const AUDIT_ARCH_ARMEB: u32 = 40; +pub const AUDIT_ARCH_C6X: u32 = 1073741964; +pub const AUDIT_ARCH_C6XBE: u32 = 140; +pub const AUDIT_ARCH_CRIS: u32 = 1073741900; +pub const AUDIT_ARCH_CSKY: u32 = 1073742076; +pub const AUDIT_ARCH_FRV: u32 = 21569; +pub const AUDIT_ARCH_H8300: u32 = 46; +pub const AUDIT_ARCH_HEXAGON: u32 = 164; +pub const AUDIT_ARCH_I386: u32 = 1073741827; +pub const AUDIT_ARCH_IA64: u32 = 3221225522; +pub const AUDIT_ARCH_M32R: u32 = 88; +pub const AUDIT_ARCH_M68K: u32 = 4; +pub const AUDIT_ARCH_MICROBLAZE: u32 = 189; +pub const AUDIT_ARCH_MIPS: u32 = 8; +pub const AUDIT_ARCH_MIPSEL: u32 = 1073741832; +pub const AUDIT_ARCH_MIPS64: u32 = 2147483656; +pub const AUDIT_ARCH_MIPS64N32: u32 = 2684354568; +pub const AUDIT_ARCH_MIPSEL64: u32 = 3221225480; +pub const AUDIT_ARCH_MIPSEL64N32: u32 = 3758096392; +pub const AUDIT_ARCH_NDS32: u32 = 1073741991; +pub const AUDIT_ARCH_NDS32BE: u32 = 167; +pub const AUDIT_ARCH_NIOS2: u32 = 1073741937; +pub const AUDIT_ARCH_OPENRISC: u32 = 92; +pub const AUDIT_ARCH_PARISC: u32 = 15; +pub const AUDIT_ARCH_PARISC64: u32 = 2147483663; +pub const AUDIT_ARCH_PPC: u32 = 20; +pub const AUDIT_ARCH_PPC64: u32 = 2147483669; +pub const AUDIT_ARCH_PPC64LE: u32 = 3221225493; +pub const AUDIT_ARCH_RISCV32: u32 = 1073742067; +pub const AUDIT_ARCH_RISCV64: u32 = 3221225715; +pub const AUDIT_ARCH_S390: u32 = 22; +pub const AUDIT_ARCH_S390X: u32 = 2147483670; +pub const AUDIT_ARCH_SH: u32 = 42; +pub const AUDIT_ARCH_SHEL: u32 = 1073741866; +pub const AUDIT_ARCH_SH64: u32 = 2147483690; +pub const AUDIT_ARCH_SHEL64: u32 = 3221225514; +pub const AUDIT_ARCH_SPARC: u32 = 2; +pub const AUDIT_ARCH_SPARC64: u32 = 2147483691; +pub const AUDIT_ARCH_TILEGX: u32 = 3221225663; +pub const AUDIT_ARCH_TILEGX32: u32 = 1073742015; +pub const AUDIT_ARCH_TILEPRO: u32 = 1073742012; +pub const AUDIT_ARCH_UNICORE: u32 = 1073741934; +pub const AUDIT_ARCH_X86_64: u32 = 3221225534; +pub const AUDIT_ARCH_XTENSA: u32 = 94; +pub const AUDIT_ARCH_LOONGARCH32: u32 = 1073742082; +pub const AUDIT_ARCH_LOONGARCH64: u32 = 3221225730; diff --git a/src/arch/generate.sh b/src/arch/generate.sh new file mode 100755 index 00000000..be0d0de7 --- /dev/null +++ b/src/arch/generate.sh @@ -0,0 +1,2 @@ +#!/bin/sh +bindgen --allowlist-var 'AUDIT_ARCH_.*' /usr/include/linux/audit.h > audit.rs diff --git a/src/arch/riscv64.rs b/src/arch/riscv64.rs index f277b6b4..5cdbe8c3 100644 --- a/src/arch/riscv64.rs +++ b/src/arch/riscv64.rs @@ -1,40 +1,26 @@ +use super::RegsExt; use nix::libc::user_regs_struct; -pub type PtraceRegisters = user_regs_struct; +pub const NATIVE_AUDIT_ARCH: u32 = super::AUDIT_ARCH_RISCV64; +pub const HAS_32BIT: bool = false; -macro_rules! syscall_no_from_regs { - ($regs:ident) => { - $regs.a7 as i64 - }; +pub type Regs = user_regs_struct; +pub type RegsPayload = Regs; +#[repr(transparent)] +pub struct RegsRepr { + pub payload: RegsPayload, } -macro_rules! syscall_res_from_regs { - ($regs:ident) => { - $regs.a0 as i64 - }; +impl RegsExt for Regs { + fn syscall_arg(&self, idx: usize, _is_32bit: bool) -> usize { + (match idx { + 0 => self.a0, + 1 => self.a1, + 2 => self.a2, + 3 => self.a3, + 4 => self.a4, + 5 => self.a5, + _ => unimplemented!(), + } as usize) + } } - -macro_rules! syscall_arg { - ($regs:ident, 0) => { - $regs.a0 - }; - ($regs:ident, 1) => { - $regs.a1 - }; - ($regs:ident, 2) => { - $regs.a2 - }; - ($regs:ident, 3) => { - $regs.a3 - }; - ($regs:ident, 4) => { - $regs.a4 - }; - ($regs:ident, 5) => { - $regs.a5 - }; -} - -pub(crate) use syscall_arg; -pub(crate) use syscall_no_from_regs; -pub(crate) use syscall_res_from_regs; diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index 6fedd8f6..95ca2f67 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -1,40 +1,101 @@ use nix::libc::user_regs_struct; -pub type PtraceRegisters = user_regs_struct; +pub const NATIVE_AUDIT_ARCH: u32 = super::AUDIT_ARCH_X86_64; +pub const SYS_EXECVE_32: i32 = 11; +pub const SYS_EXECVEAT_32: i32 = 358; +pub const HAS_32BIT: bool = true; -macro_rules! syscall_no_from_regs { - ($regs:ident) => { - $regs.orig_rax as i64 - }; +// https://github.com/rust-lang/rfcs/blob/master/text/2195-really-tagged-unions.md +#[repr(C, u32)] +#[derive(Debug)] +#[expect(dead_code)] // Variants are constructed in unsafe code +pub enum Regs { + X86(PtraceRegisters32), + X64(PtraceRegisters64), } -macro_rules! syscall_res_from_regs { - ($regs:ident) => { - $regs.rax as i64 - }; +#[repr(u32)] +pub enum RegsTag { + X86, + X64, } -macro_rules! syscall_arg { - ($regs:ident, 0) => { - $regs.rdi - }; - ($regs:ident, 1) => { - $regs.rsi - }; - ($regs:ident, 2) => { - $regs.rdx - }; - ($regs:ident, 3) => { - $regs.r10 - }; - ($regs:ident, 4) => { - $regs.r8 - }; - ($regs:ident, 5) => { - $regs.r9 - }; +#[repr(C)] +pub union RegsPayload { + x86: PtraceRegisters32, + x64: PtraceRegisters64, } -pub(crate) use syscall_arg; -pub(crate) use syscall_no_from_regs; -pub(crate) use syscall_res_from_regs; +#[repr(C)] +pub struct RegsRepr { + pub tag: RegsTag, + pub payload: RegsPayload, +} + +pub type PtraceRegisters64 = user_regs_struct; + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct PtraceRegisters32 { + ebx: u32, + ecx: u32, + edx: u32, + esi: u32, + edi: u32, + ebp: u32, + eax: u32, + xds: u32, + xes: u32, + xfs: u32, + xgs: u32, + orig_eax: u32, + eip: u32, + xcs: u32, + eflags: u32, + esp: u32, + xss: u32, +} + +use super::RegsExt; + +impl RegsExt for Regs { + fn syscall_arg(&self, idx: usize, is_32bit: bool) -> usize { + match self { + Self::X86(regs) => { + debug_assert!(is_32bit); + (match idx { + 0 => regs.ebx, + 1 => regs.ecx, + 2 => regs.edx, + 3 => regs.esi, + 4 => regs.edi, + 5 => unimplemented!(), + _ => unreachable!(), + } as usize) + } + Self::X64(regs) => { + if is_32bit { + (match idx { + 0 => regs.rbx, + 1 => regs.rcx, + 2 => regs.rdx, + 3 => regs.rsi, + 4 => regs.rdi, + 5 => unimplemented!(), + _ => unreachable!(), + } as u32 as usize) + } else { + (match idx { + 0 => regs.rdi, + 1 => regs.rsi, + 2 => regs.rdx, + 3 => regs.r10, + 4 => regs.r8, + 5 => regs.r9, + _ => unreachable!(), + } as usize) + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index ff8c6017..93bf48fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,14 +90,19 @@ async fn main() -> color_eyre::Result<()> { } else { None }; - // Seccomp-bpf ptrace behavior is changed on 4.8. I haven't tested on older kernels. - let min_support_kver = (4, 8); - if !is_current_kernel_greater_than(min_support_kver)? { + // PTRACE_GET_SYSCALL_INFO requires at least linux 5.3. + let min_support_kver = (5, 3); + if !is_current_kernel_ge(min_support_kver)? { log::warn!( "Current kernel version is not supported! Minimum supported kernel version is {}.{}.", min_support_kver.0, min_support_kver.1 ); + eprintln!( + "Current kernel version is not supported! Minimum supported kernel version is {}.{}.", + min_support_kver.0, + min_support_kver.1 + ); } if !cli.no_profile { match Config::load(cli.profile.clone()) { @@ -353,7 +358,7 @@ async fn main() -> color_eyre::Result<()> { Ok(()) } -fn is_current_kernel_greater_than(min_support: (u32, u32)) -> color_eyre::Result { +fn is_current_kernel_ge(min_support: (u32, u32)) -> color_eyre::Result { let utsname = nix::sys::utsname::uname()?; let kstr = utsname.release().as_bytes(); let pos = kstr.iter().position(|&c| c != b'.' && !c.is_ascii_digit()); diff --git a/src/seccomp.rs b/src/seccomp.rs index 10a2dfa3..17af9ae5 100644 --- a/src/seccomp.rs +++ b/src/seccomp.rs @@ -1,8 +1,20 @@ -use libseccomp::{ScmpAction, ScmpFilterContext}; +use libseccomp::{ScmpAction, ScmpArch, ScmpFilterContext}; -pub fn create_seccomp_filters() -> color_eyre::Result { + +pub fn load_seccomp_filters() -> color_eyre::Result<()> { + libseccomp::reset_global_state()?; let mut filter = ScmpFilterContext::new_filter(ScmpAction::Allow)?; filter.add_rule(ScmpAction::Trace(0), nix::libc::SYS_execve as i32)?; filter.add_rule(ScmpAction::Trace(0), nix::libc::SYS_execveat as i32)?; - Ok(filter) + if cfg!(target_arch = "x86_64") { + let mut filter32 = ScmpFilterContext::new_filter(ScmpAction::Allow)?; + filter32.remove_arch(ScmpArch::native())?; + filter32.add_arch(ScmpArch::X86)?; + // libseccomp translates the syscall number for us. + filter32.add_rule(ScmpAction::Trace(0), nix::libc::SYS_execve as i32)?; + filter32.add_rule(ScmpAction::Trace(0), nix::libc::SYS_execveat as i32)?; + filter.merge(filter32)?; + } + filter.load()?; + Ok(()) } diff --git a/src/tracer.rs b/src/tracer.rs index 5930a74b..9fdeeeb7 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -17,8 +17,8 @@ use inspect::{read_arcstr, read_output_msg_array}; use nix::{ errno::Errno, libc::{ - self, dup2, pid_t, pthread_self, pthread_setname_np, raise, SYS_clone, SYS_clone3, - AT_EMPTY_PATH, SIGSTOP, S_ISGID, S_ISUID, + self, dup2, pid_t, pthread_self, pthread_setname_np, raise, AT_EMPTY_PATH, SIGSTOP, S_ISGID, + S_ISUID, }, sys::{ signal::{kill, Signal}, @@ -29,7 +29,7 @@ use nix::{ getpid, initgroups, setpgid, setresgid, setresuid, setsid, tcsetpgrp, Gid, Pid, Uid, User, }, }; -use state::PendingDetach; +use state::{PendingDetach, Syscall}; use tokio::{ select, sync::mpsc::{error::SendError, UnboundedReceiver, UnboundedSender}, @@ -37,7 +37,7 @@ use tokio::{ use tracing::{debug, error, info, trace, warn}; use crate::{ - arch::{syscall_arg, syscall_no_from_regs, syscall_res_from_regs}, + arch::RegsExt, cli::args::{LogModeArgs, ModifierArgs, PtraceArgs, TracerEventArgs}, cmdbuilder::CommandBuilder, event::{ @@ -255,8 +255,7 @@ impl Tracer { move |program_path| { #[cfg(feature = "seccomp-bpf")] if seccomp_bpf == SeccompBpf::On { - let filter = seccomp::create_seccomp_filters()?; - filter.load()?; + seccomp::load_seccomp_filters()?; } if !with_tty { @@ -655,6 +654,19 @@ impl Tracer { let p = store.get_current_mut(pid).unwrap(); p.presyscall = !p.presyscall; // SYSCALL ENTRY + let info = match ptrace::syscall_entry_info(pid) { + Ok(info) => info, + Err(Errno::ESRCH) => { + filterable_event!(Info(TracerEventMessage { + msg: "Failed to get syscall info: ESRCH (child probably gone!)".to_string(), + pid: Some(pid), + })) + .send_if_match(&self.msg_tx, self.filter)?; + info!("ptrace get_syscall_info failed: {pid}, ESRCH, child probably gone!"); + return Ok(()); + } + e => e?, + }; let regs = match ptrace_getregs(pid) { Ok(regs) => regs, Err(Errno::ESRCH) => { @@ -668,18 +680,19 @@ impl Tracer { } e => e?, }; - let syscallno = syscall_no_from_regs!(regs); - p.syscall = syscallno; + let syscallno = info.number; + let is_32bit = info.is_32bit(); // trace!("pre syscall: {syscallno}"); - if syscallno == nix::libc::SYS_execveat { + if info.is_execveat() { + p.syscall = Syscall::Execveat; trace!("pre execveat {syscallno}"); // int execveat(int dirfd, const char *pathname, // char *const _Nullable argv[], // char *const _Nullable envp[], // int flags); - let dirfd = syscall_arg!(regs, 0) as i32; - let flags = syscall_arg!(regs, 4) as i32; - let filename = match read_string(pid, syscall_arg!(regs, 1) as AddressType) { + let dirfd = regs.syscall_arg(0, is_32bit) as i32; + let flags = regs.syscall_arg(4, is_32bit) as i32; + let filename = match read_string(pid, regs.syscall_arg(1, is_32bit) as AddressType) { Ok(pathname) => { let pathname = cached_string(pathname); let pathname_is_empty = pathname.is_empty(); @@ -708,9 +721,9 @@ impl Tracer { }; let filename = self.get_filename_for_display(pid, filename)?; self.warn_for_filename(&filename, pid)?; - let argv = read_output_msg_array(pid, syscall_arg!(regs, 2) as AddressType); + let argv = read_output_msg_array(pid, regs.syscall_arg(2, is_32bit) as AddressType, is_32bit); self.warn_for_argv(&argv, pid)?; - let envp = read_env(pid, syscall_arg!(regs, 3) as AddressType); + let envp = read_env(pid, regs.syscall_arg(3, is_32bit) as AddressType, is_32bit); self.warn_for_envp(&envp, pid)?; let interpreters = if self.printer.args.trace_interpreter && filename.is_ok() { @@ -730,14 +743,16 @@ impl Tracer { Some(interpreters), read_fds(pid)?, )); - } else if syscallno == nix::libc::SYS_execve { + } else if info.is_execve() { + p.syscall = Syscall::Execve; trace!("pre execve {syscallno}",); - let filename = read_arcstr(pid, syscall_arg!(regs, 0) as AddressType); + let filename = read_arcstr(pid, regs.syscall_arg(0, is_32bit) as AddressType); let filename = self.get_filename_for_display(pid, filename)?; self.warn_for_filename(&filename, pid)?; - let argv = read_output_msg_array(pid, syscall_arg!(regs, 1) as AddressType); + let argv = read_output_msg_array(pid, regs.syscall_arg(1, is_32bit) as AddressType, is_32bit); self.warn_for_argv(&argv, pid)?; - let envp = read_string_array(pid, syscall_arg!(regs, 2) as AddressType).map(parse_envp); + let envp = read_string_array(pid, regs.syscall_arg(2, is_32bit) as AddressType, is_32bit) + .map(parse_envp); self.warn_for_envp(&envp, pid)?; let interpreters = if self.printer.args.trace_interpreter && filename.is_ok() { read_interpreter_recursive(filename.as_deref().unwrap()) @@ -756,7 +771,8 @@ impl Tracer { Some(interpreters), read_fds(pid)?, )); - } else if syscallno == SYS_clone || syscallno == SYS_clone3 { + } else { + p.syscall = Syscall::Other; } if let Some(exec_data) = &p.exec_data { let mut hit = None; @@ -801,20 +817,20 @@ impl Tracer { let mut store = self.store.write().unwrap(); let p = store.get_current_mut(pid).unwrap(); p.presyscall = !p.presyscall; - let regs = match ptrace_getregs(pid) { - Ok(regs) => regs, + let result = match ptrace::syscall_exit_result(pid) { + Ok(r) => r, Err(Errno::ESRCH) => { - info!("ptrace getregs failed: {pid}, ESRCH, child probably gone!"); + info!("ptrace get_syscall_info failed: {pid}, ESRCH, child probably gone!"); return Ok(()); } e => e?, }; - let result = syscall_res_from_regs!(regs); // If exec is successful, the register value might be clobbered. - let exec_result = if p.is_exec_successful { 0 } else { result }; + // TODO: would the value in ptrace_syscall_info be clobbered? + let exec_result = if p.is_exec_successful { 0 } else { result } as i64; match p.syscall { - nix::libc::SYS_execve | nix::libc::SYS_execveat => { - trace!("post execve in exec"); + Syscall::Execve | Syscall::Execveat => { + trace!("post execve(at) in exec"); if self.printer.args.successful_only && !p.is_exec_successful { p.exec_data = None; self.seccomp_aware_cont(pid)?; diff --git a/src/tracer/inspect.rs b/src/tracer/inspect.rs index 798b1009..fb5a8bf3 100644 --- a/src/tracer/inspect.rs +++ b/src/tracer/inspect.rs @@ -59,39 +59,59 @@ pub fn read_string(pid: Pid, address: AddressType) -> Result( pid: Pid, mut address: AddressType, + is_32bit: bool, reader: impl Fn(Pid, AddressType) -> Result, ) -> Result, InspectError> { let mut res = Vec::new(); - const WORD_SIZE: usize = 8; // FIXME + // FIXME: alignment + let word_size = if is_32bit { 4 } else { 8 }; loop { let ptr = match ptrace::read(pid, address) { Err(e) => { warn!("Cannot read tracee {pid} memory {address:?}: {e}"); return Err(e); } - Ok(ptr) => ptr, + Ok(ptr) => { + if is_32bit { + ptr as u32 as i64 + } else { + ptr + } + } }; if ptr == 0 { return Ok(res); } else { res.push(reader(pid, ptr as AddressType)?); } - address = unsafe { address.add(WORD_SIZE) }; + address = unsafe { address.add(word_size) }; } } #[allow(unused)] -pub fn read_cstring_array(pid: Pid, address: AddressType) -> Result, InspectError> { - read_null_ended_array(pid, address, read_cstring) +pub fn read_cstring_array( + pid: Pid, + address: AddressType, + is_32bit: bool, +) -> Result, InspectError> { + read_null_ended_array(pid, address, is_32bit, read_cstring) } -pub fn read_string_array(pid: Pid, address: AddressType) -> Result, InspectError> { - read_null_ended_array(pid, address, read_string) +pub fn read_string_array( + pid: Pid, + address: AddressType, + is_32bit: bool, +) -> Result, InspectError> { + read_null_ended_array(pid, address, is_32bit, read_string) } #[allow(unused)] -pub fn read_arcstr_array(pid: Pid, address: AddressType) -> Result, InspectError> { - read_null_ended_array(pid, address, |pid, address| { +pub fn read_arcstr_array( + pid: Pid, + address: AddressType, + is_32bit: bool, +) -> Result, InspectError> { + read_null_ended_array(pid, address, is_32bit, |pid, address| { read_string(pid, address).map(cached_string) }) } @@ -99,8 +119,9 @@ pub fn read_arcstr_array(pid: Pid, address: AddressType) -> Result, pub fn read_output_msg_array( pid: Pid, address: AddressType, + is_32bit: bool, ) -> Result, InspectError> { - read_null_ended_array(pid, address, |pid, address| { + read_null_ended_array(pid, address, is_32bit, |pid, address| { read_string(pid, address) .map(cached_string) .map(OutputMsg::Ok) @@ -127,16 +148,24 @@ fn read_single_env_entry(pid: Pid, address: AddressType) -> (OutputMsg, OutputMs pub fn read_env( pid: Pid, mut address: AddressType, + is_32bit: bool, ) -> Result, InspectError> { let mut res = BTreeMap::new(); - const WORD_SIZE: usize = 8; // FIXME + // FIXME: alignment + let word_size = if is_32bit { 4 } else { 8 }; loop { let ptr = match ptrace::read(pid, address) { Err(e) => { warn!("Cannot read tracee {pid} memory {address:?}: {e}"); return Err(e); } - Ok(ptr) => ptr, + Ok(ptr) => { + if is_32bit { + ptr as u32 as i64 + } else { + ptr + } + } }; if ptr == 0 { return Ok(res); @@ -144,6 +173,6 @@ pub fn read_env( let (k, v) = read_single_env_entry(pid, ptr as AddressType); res.insert(k, v); } - address = unsafe { address.add(WORD_SIZE) }; + address = unsafe { address.add(word_size) }; } } diff --git a/src/tracer/ptrace.rs b/src/tracer/ptrace.rs index 4ddf9de1..bb17739a 100644 --- a/src/tracer/ptrace.rs +++ b/src/tracer/ptrace.rs @@ -1,8 +1,18 @@ +use std::{mem::MaybeUninit, ptr::addr_of_mut}; + use cfg_if::cfg_if; -use nix::{errno::Errno, sys::ptrace, sys::signal::Signal, unistd::Pid}; +use nix::{ + errno::Errno, + libc::{ + ptrace_syscall_info, SYS_execve, SYS_execveat, PTRACE_GET_SYSCALL_INFO, + PTRACE_SYSCALL_INFO_ENTRY, PTRACE_SYSCALL_INFO_EXIT, PTRACE_SYSCALL_INFO_SECCOMP, + }, + sys::{ptrace, signal::Signal}, + unistd::Pid, +}; use tracing::info; -use crate::arch::PtraceRegisters; +use crate::arch::{Regs, RegsPayload, RegsRepr, HAS_32BIT, NATIVE_AUDIT_ARCH}; pub use nix::sys::ptrace::*; @@ -27,41 +37,150 @@ pub fn ptrace_cont(pid: Pid, sig: Option) -> Result<(), Errno> { } } -pub fn ptrace_getregs(pid: Pid) -> Result { - // Don't use GETREGSET on x86_64. - // In some cases(it usually happens several times at and after exec syscall exit), - // we only got 68/216 bytes into `regs`, which seems unreasonable. Not sure why. - cfg_if! { +pub struct SyscallInfo { + pub arch: u32, + pub number: i64, +} + +impl SyscallInfo { + /// Returns true if this syscall is 32bit. + /// + /// It is possible for a 64bit process to make a 32bit syscall, + /// resulting in X64 ptregs but with 32bit semantics + pub fn is_32bit(&self) -> bool { + if HAS_32BIT { + // FIXME: x32 ABI + NATIVE_AUDIT_ARCH != self.arch + } else { + false + } + } + + pub fn is_execve(&self) -> bool { + cfg_if! { if #[cfg(target_arch = "x86_64")] { - ptrace::getregs(pid) + use crate::arch; + (self.arch == arch::AUDIT_ARCH_X86_64 && self.number == SYS_execve) || + (self.arch == arch::AUDIT_ARCH_I386 && self.number == arch::SYS_EXECVE_32 as i64) } else { - // https://github.com/torvalds/linux/blob/v6.9/include/uapi/linux/elf.h#L378 - // libc crate doesn't provide this constant when using musl libc. - const NT_PRSTATUS: std::ffi::c_int = 1; - - use nix::sys::ptrace::AddressType; + self.arch == NATIVE_AUDIT_ARCH && self.number == SYS_execve + } + } + } - let mut regs = std::mem::MaybeUninit::::uninit(); - let iovec = nix::libc::iovec { - iov_base: regs.as_mut_ptr() as AddressType, - iov_len: std::mem::size_of::(), - }; - let ptrace_result = unsafe { - nix::libc::ptrace( - nix::libc::PTRACE_GETREGSET, - pid.as_raw(), - NT_PRSTATUS, - &iovec as *const _ as *const nix::libc::c_void, - ) - }; - let regs = if -1 == ptrace_result { - let errno = nix::errno::Errno::last(); - return Err(errno); - } else { - assert_eq!(iovec.iov_len, std::mem::size_of::()); - unsafe { regs.assume_init() } - }; - Ok(regs) + pub fn is_execveat(&self) -> bool { + cfg_if! { + if #[cfg(target_arch = "x86_64")] { + use crate::arch; + (self.arch == arch::AUDIT_ARCH_X86_64 && self.number == SYS_execveat) || + (self.arch == arch::AUDIT_ARCH_I386 && self.number == arch::SYS_EXECVEAT_32 as i64) + } else { + self.arch == NATIVE_AUDIT_ARCH && self.number == SYS_execveat } + } + } +} + +/// Get [`SyscallInfo`] on ptrace syscall entry/seccomp stop +/// +/// # Precondition +/// +/// The caller is the tracer thread and at the syscall entry/seccomp stop. +pub fn syscall_entry_info(pid: Pid) -> Result { + let mut info = MaybeUninit::::uninit(); + let info = unsafe { + let ret = nix::libc::ptrace( + PTRACE_GET_SYSCALL_INFO, + pid.as_raw(), + size_of::(), + info.as_mut_ptr(), + ); + if ret < 0 { + return Err(Errno::last()); + } else { + info.assume_init() + } + }; + let arch = info.arch; + let number = if info.op == PTRACE_SYSCALL_INFO_ENTRY { + unsafe { info.u.entry.nr } + } else if info.op == PTRACE_SYSCALL_INFO_SECCOMP { + unsafe { info.u.seccomp.nr } + } else { + // Not syscall entry/seccomp stop + return Err(Errno::EINVAL); + } as i64; + Ok(SyscallInfo { arch, number }) +} + +/// Get syscall result on ptrace syscall exit stop +/// +/// # Precondition +/// +/// The caller is the tracer thread and at the syscall exit stop. +pub fn syscall_exit_result(pid: Pid) -> Result { + let mut info = MaybeUninit::::uninit(); + let info = unsafe { + let ret = nix::libc::ptrace( + PTRACE_GET_SYSCALL_INFO, + pid.as_raw(), + size_of::(), + info.as_mut_ptr(), + ); + if ret < 0 { + return Err(Errno::last()); + } else { + info.assume_init() + } + }; + if info.op == PTRACE_SYSCALL_INFO_EXIT { + Ok(unsafe { info.u.exit.sval } as isize) + } else { + Err(Errno::EINVAL) } } + +pub fn ptrace_getregs(pid: Pid) -> Result { + // https://github.com/torvalds/linux/blob/v6.9/include/uapi/linux/elf.h#L378 + // libc crate doesn't provide this constant when using musl libc. + const NT_PRSTATUS: std::ffi::c_int = 1; + + use nix::sys::ptrace::AddressType; + + let mut regs = std::mem::MaybeUninit::::uninit(); + let dest: *mut RegsRepr = unsafe { std::mem::transmute(regs.as_mut_ptr()) }; + let mut iovec = nix::libc::iovec { + iov_base: unsafe { addr_of_mut!((*dest).payload) } as AddressType, + iov_len: std::mem::size_of::(), + }; + let ptrace_result = unsafe { + nix::libc::ptrace( + nix::libc::PTRACE_GETREGSET, + pid.as_raw(), + NT_PRSTATUS, + &mut iovec, + ) + }; + let regs = if ptrace_result < 0 { + let errno = nix::errno::Errno::last(); + return Err(errno); + } else { + cfg_if! { + if #[cfg(target_arch = "x86_64")] { + const SIZE_OF_REGS64: usize = size_of::(); + const SIZE_OF_REGS32: usize = size_of::(); + match iovec.iov_len { + SIZE_OF_REGS32 => unsafe { addr_of_mut!((*dest).tag).write(crate::arch::RegsTag::X86); } + SIZE_OF_REGS64 => unsafe { addr_of_mut!((*dest).tag).write(crate::arch::RegsTag::X64); } + size => panic!("Invalid length {size} of user_regs_struct!") + } + } else if #[cfg(any(target_arch = "riscv64", target_arch = "aarch64"))] { + assert_eq!(iovec.iov_len, std::mem::size_of::()); + } else { + compile_error!("Please update the code for your architecture!"); + } + } + unsafe { regs.assume_init() } + }; + Ok(regs) +} diff --git a/src/tracer/state.rs b/src/tracer/state.rs index 2274a075..751704d6 100644 --- a/src/tracer/state.rs +++ b/src/tracer/state.rs @@ -30,6 +30,13 @@ pub struct PendingDetach { pub signal: Signal, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Syscall { + Execve, + Execveat, + Other, +} + #[derive(Debug)] pub struct ProcessState { pub pid: Pid, @@ -39,7 +46,7 @@ pub struct ProcessState { pub comm: ArcStr, pub presyscall: bool, pub is_exec_successful: bool, - pub syscall: i64, + pub syscall: Syscall, pub exec_data: Option, pub associated_events: Vec, /// A pending detach request with a signal to send to the process @@ -127,7 +134,7 @@ impl ProcessState { start_time, presyscall: true, is_exec_successful: false, - syscall: -1, + syscall: Syscall::Other, exec_data: None, associated_events: Vec::new(), pending_detach: None,