From 82fe818a0d56997ba8b9607ef598e5d93d6ab3fd Mon Sep 17 00:00:00 2001 From: Tamme Dittrich Date: Thu, 30 Nov 2023 14:55:54 +0100 Subject: [PATCH] Add ptrace experiment --- Cargo.lock | 103 ++++++++++++++++ Cargo.toml | 4 + src/bin/traced-ntpd.rs | 269 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 src/bin/traced-ntpd.rs diff --git a/Cargo.lock b/Cargo.lock index ab6667c..ef2e42c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,24 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" version = "0.10.4" @@ -197,13 +215,59 @@ dependencies = [ "digest", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "network-time-pester" version = "0.1.0" dependencies = [ "anyhow", "hex", + "libc", + "nix 0.27.1", "ntp-proto", + "pete", + "syscalls", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", ] [[package]] @@ -228,12 +292,30 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "pete" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f09c1c1ad40df294ff8643fe88a3dc64fff3293b6bc0ed9f71aff71f7086cbd" +dependencies = [ + "libc", + "memoffset 0.8.0", + "nix 0.26.4", + "thiserror", +] + [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -354,6 +436,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "spin" version = "0.9.8" @@ -377,6 +470,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syscalls" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b389b38331a454883a34fd19f25cbd1510b3510ff7aa28cb8d6de85d888439" +dependencies = [ + "serde", + "serde_repr", +] + [[package]] name = "thiserror" version = "1.0.50" diff --git a/Cargo.toml b/Cargo.toml index b2173e2..4cf4b13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,11 @@ edition = "2021" [dependencies] anyhow = "1.0.75" hex = "0.4.3" +libc = "0.2.150" +nix = "0.27.1" ntp-proto = { version = "1.1.0-alpha.20231123", features = ["__internal-api", "__internal-test", "ntpv5"] } +pete = "0.12.0" +syscalls = "0.6.15" [patch.crates-io] ntp-proto = { git = "https://github.com/pendulum-project/ntpd-rs", branch = "pester" } diff --git a/src/bin/traced-ntpd.rs b/src/bin/traced-ntpd.rs new file mode 100644 index 0000000..e4712a7 --- /dev/null +++ b/src/bin/traced-ntpd.rs @@ -0,0 +1,269 @@ +use libc::c_int; +use pete::{Ptracer, Registers, Restart, Stop, Tracee}; +use std::fmt::{Debug, Formatter}; +use std::mem::{size_of, MaybeUninit}; +use std::os::fd::RawFd; +use std::process::Command; +use std::time::Duration; +use syscalls::{SyscallArgs, Sysno}; + +fn main() { + let mut tracer = Ptracer::new(); + *tracer.poll_delay_mut() = Duration::from_millis(1); + + let mut cmd = Command::new("/home/tamme/Projects/ntpd-rs/target/release/ntp-daemon"); + cmd.args(["-c", "/home/tamme/Projects/ntpd-rs/ntp.server.toml"]); + + tracer.spawn(cmd).expect("Can spawn process"); + + while let Some(mut tracee) = tracer.wait().expect("wait never fails") { + if matches!(tracee.stop, Stop::SyscallExit) { + let regs = tracee.registers().unwrap(); + let sysno = Sysno::new(regs.orig_rax as usize).unwrap(); + + match sysno { + Sysno::recvmsg => handle_recvmsg(&mut tracee), + Sysno::clock_adjtime => handle_adjtime(&mut tracee), + Sysno::fcntl + | Sysno::sigaltstack + | Sysno::unlink + | Sysno::mprotect + | Sysno::getrandom + | Sysno::rt_sigprocmask + | Sysno::set_robust_list + | Sysno::execve + | Sysno::poll + | Sysno::clone3 + | Sysno::pread64 + | Sysno::mmap + | Sysno::munmap + | Sysno::bind + | Sysno::statx + | Sysno::epoll_ctl + | Sysno::prlimit64 + | Sysno::epoll_create1 + | Sysno::eventfd2 + | Sysno::prctl + | Sysno::set_tid_address + | Sysno::futex + | Sysno::arch_prctl + | Sysno::access + | Sysno::newfstatat + | Sysno::rt_sigaction + | Sysno::write + | Sysno::setsockopt + | Sysno::openat + | Sysno::socket + | Sysno::brk + | Sysno::close + | Sysno::rseq + | Sysno::sched_getaffinity + | Sysno::read + | Sysno::epoll_wait + | Sysno::sendto => {} + other => { + panic!("don't know what to do with syscall: {other}") + } + } + } + + tracer.restart(tracee, Restart::Syscall).unwrap(); + } +} + +fn handle_adjtime(tracee: &mut Tracee) { + let pid = tracee.pid; + let adj_time = AdjTime::from_tracee(tracee).unwrap(); + println!("[{pid}] {adj_time:?} = {:?}", adj_time.result); +} + +fn handle_recvmsg(tracee: &mut Tracee) { + let pid = tracee.pid; + let recvmsg = RecvMsg::from_tracee(tracee).unwrap(); + + println!("[{pid}] {recvmsg:?} = {:?}", recvmsg.result); + if recvmsg.result.is_ok() && recvmsg.ctrl.is_some() { + let ctrl = recvmsg.ctrl.unwrap(); + assert!(matches!(ctrl, ControlMsg::ScmTimeStamping(_))); + let time = libc::timespec { + tv_sec: -86400 * (70 * 365 + 17), // NTP era start + tv_nsec: 0, + }; + let buf: [u8; size_of::()] = unsafe { std::mem::transmute(time) }; + tracee + .write_memory( + recvmsg.header.msg_control as u64 + size_of::() as u64, + buf.as_slice(), + ) + .unwrap(); + } +} + +struct SysCall { + pub no: Sysno, + pub args: SyscallArgs, + pub result: SysCallResult, +} + +pub type SysCallResult = Result; + +impl SysCall { + pub fn new(regs: Registers) -> Option { + let result = match regs.rax as isize { + i @ 0.. => Ok(i as usize), + i @ ..=-1 => Err(nix::errno::from_i32(-i as i32)), + }; + + Some(Self { + no: Sysno::new(regs.orig_rax as usize)?, + args: Self::regs_to_args(regs), + result, + }) + } + + fn regs_to_args(regs: Registers) -> SyscallArgs { + SyscallArgs { + arg0: regs.rdi as usize, + arg1: regs.rsi as usize, + arg2: regs.rdx as usize, + arg3: regs.r10 as usize, + arg4: regs.r8 as usize, + arg5: regs.r9 as usize, + } + } +} + +unsafe fn read_from_tracee(tracee: &mut Tracee, addr: usize) -> T { + let mut val: MaybeUninit = MaybeUninit::zeroed(); + tracee + .read_memory_mut( + addr as u64, + std::slice::from_raw_parts_mut(val.as_mut_ptr() as *mut u8, size_of::()), + ) + .unwrap(); + val.assume_init() +} + +#[derive(Debug)] +struct AdjTime { + clock_id: ClockId, + timex: libc::timex, + result: SysCallResult, +} + +impl AdjTime { + pub fn from_tracee(tracee: &mut Tracee) -> Option { + let syscall = SysCall::new(tracee.registers().ok()?)?; + if syscall.no != Sysno::clock_adjtime { + return None; + } + + Some(Self { + clock_id: ClockId::from(syscall.args.arg0), + timex: unsafe { read_from_tracee(tracee, syscall.args.arg1) }, + result: syscall.result, + }) + } +} + +#[derive(Debug)] +enum ClockId { + Other(usize), +} + +impl From for ClockId { + fn from(value: usize) -> Self { + Self::Other(value) + } +} + +struct RecvMsg { + fd: RawFd, + header: libc::msghdr, + flags: c_int, + result: SysCallResult, + + msg_control: Vec, + ctrl: Option, +} + +impl RecvMsg { + fn from_tracee(tracee: &mut Tracee) -> Option { + let syscall = SysCall::new(tracee.registers().ok()?)?; + if syscall.no != Sysno::recvmsg { + return None; + } + + let header: libc::msghdr = unsafe { read_from_tracee(tracee, syscall.args.arg1) }; + + let msg_control = tracee + .read_memory(header.msg_control as _, header.msg_controllen as _) + .unwrap(); + + let ctrl = ControlMsg::parse(msg_control.as_slice()); + + Some(Self { + fd: syscall.args.arg0 as _, + header, + flags: syscall.args.arg2 as _, + result: syscall.result, + msg_control, + ctrl, + }) + } +} + +impl Debug for RecvMsg { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RecvMsg") + .field("fd", &self.fd) + .field("result", &self.result) + .field("ctrl", &self.ctrl) + .finish() + } +} + +#[derive(Debug)] +enum ControlMsg { + ScmTimeStamping([libc::timespec; 3]), + ScmTimeStampNs(libc::timespec), + ScmTimeStamp(libc::timeval), + Other(libc::cmsghdr), +} + +impl ControlMsg { + fn parse(data: &[u8]) -> Option { + assert!(data.len() >= size_of::()); + let ctrl_hdr = unsafe { std::ptr::read_unaligned(data.as_ptr() as *const libc::cmsghdr) }; + let hdr_size = size_of::(); + if ctrl_hdr.cmsg_len == 0 { + return None; + } + let ctrl_data = &data[hdr_size..ctrl_hdr.cmsg_len]; + + Some(match (ctrl_hdr.cmsg_level, ctrl_hdr.cmsg_type) { + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { + type Record = [libc::timespec; 3]; + assert_eq!(ctrl_data.len(), size_of::()); + let record = + unsafe { std::ptr::read_unaligned(ctrl_data.as_ptr() as *const Record) }; + Self::ScmTimeStamping(record) + } + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => { + type Record = libc::timespec; + assert_eq!(ctrl_data.len(), size_of::()); + let record = + unsafe { std::ptr::read_unaligned(ctrl_data.as_ptr() as *const Record) }; + Self::ScmTimeStampNs(record) + } + (libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => { + type Record = libc::timeval; + assert_eq!(ctrl_data.len(), size_of::()); + let record = + unsafe { std::ptr::read_unaligned(ctrl_data.as_ptr() as *const Record) }; + Self::ScmTimeStamp(record) + } + _ => Self::Other(ctrl_hdr), + }) + } +}