diff --git a/Cargo.lock b/Cargo.lock index b6d66d6..04fd227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,8 @@ name = "acid" version = "0.1.0" dependencies = [ + "redox_syscall 0.1.56 (git+https://gitlab.redox-os.org/redox-os/syscall.git)", + "strace 0.1.0 (git+https://gitlab.redox-os.org/redox-os/strace-redox)", "x86 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -14,7 +16,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -27,7 +29,7 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -44,7 +46,7 @@ name = "fuchsia-zircon" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -201,6 +203,14 @@ dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "git+https://gitlab.redox-os.org/redox-os/syscall.git#8d0015be8693a81c2a4459f3c09fb47b98ff07b1" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-serialize" version = "0.3.24" @@ -228,6 +238,15 @@ name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "strace" +version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/strace-redox#d7f7921f50bb03fe855f27a439d2d9c2eac45646" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (git+https://gitlab.redox-os.org/redox-os/syscall.git)", +] + [[package]] name = "winapi" version = "0.3.6" @@ -261,7 +280,7 @@ dependencies = [ [metadata] "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum csv 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)" = "266c1815d7ca63a5bd86284043faf91e8c95e943e55ce05dc0ae08e952de18bc" @@ -284,10 +303,12 @@ dependencies = [ "checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" "checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" "checksum raw-cpuid 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13b844e4049605ff38fed943f5c7b2c691fad68d9d5bf074d2720554c4e48246" +"checksum redox_syscall 0.1.56 (git+https://gitlab.redox-os.org/redox-os/syscall.git)" = "" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97b18e9e53de541f11e497357d6c5eaeb39f0cb9c8734e274abe4935f6991fa" "checksum serde_json 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5aaee47e038bf9552d30380d3973fff2593ee0a76d81ad4c581f267cdcadf36" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +"checksum strace 0.1.0 (git+https://gitlab.redox-os.org/redox-os/strace-redox)" = "" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index f57ab6d..789cad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "acid" version = "0.1.0" authors = ["Jeremy Soller "] +edition = "2018" [dependencies] x86 = "0.7" +redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git" } +strace = { git = "https://gitlab.redox-os.org/redox-os/strace-redox", default-features = false } +#strace = { path = "../../strace/source", default-features = false } diff --git a/src/main.rs b/src/main.rs index 7e7ef7d..f5eb681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ //!Acid testing program -#![feature(thread_local)] +#![feature(thread_local, asm)] -extern crate x86; +fn e(error: Result) -> Result { + error.map_err(|e| e.to_string()) +} fn create_test() -> Result<(), String> { use std::fs; @@ -51,6 +53,446 @@ fn page_fault_test() -> Result<(), String> { Ok(()) } +pub fn ptrace() -> Result<(), String> { + use std::{ + fs::{File, OpenOptions}, + io::{self, prelude::*}, + mem, + os::unix::{ + fs::OpenOptionsExt, + io::{AsRawFd, FromRawFd, RawFd}, + }, + thread, + time::Duration, + }; + use strace::*; + + let pid = e(unsafe { syscall::clone(syscall::CloneFlags::empty()) })?; + if pid == 0 { + extern "C" fn sighandler(_: usize) { + unsafe { + asm!(" + mov rax, 158 // SYS_YIELD + syscall + " + : : : : "intel", "volatile"); + } + } + extern "C" fn sigreturn() { + unsafe { + asm!(" + mov rax, 119 // SYS_SIGRETURN + syscall + ud2 + " + : : : : "intel", "volatile"); + } + } + unsafe { + asm!(" + // Push any arguments from rust to the stack so we're + // free to use whatever registers we want + push $1 + push $0 + mov rbp, rsp + + // Wait until tracer is started + mov rax, 20 // SYS_GETPID + syscall + + mov rdi, rax + + mov rax, 37 // SYS_KILL + mov rsi, 19 // SIGSTOP + syscall + + // Start of body: + + // Test basic singlestepping + mov rax, 1 + push rax + mov rax, 2 + push rax + mov rax, 3 + pop rax + pop rax + + // Test memory access + push 3 + push 2 + push 1 + add rsp, 8*3 // pop 3 items, ignore values + + // Testing floating point + push 32 + fild QWORD PTR [rsp] + fsqrt + add rsp, 8 // pop 1 item, ignore value + + // Make sure event is raised when child forks + mov rax, 120 // SYS_CLONE + xor rdi, rdi + syscall + test rax, rax + je exit + + // Wait for child process, to make sure an ignored process is continued + mov rdi, rax + mov rax, 7 + push 0 + mov rsi, rsp + xor rdx, rdx + syscall + add rsp, 8 + + // Another fork attempt, but test what happens when not ignored + mov rax, 120 // SYS_CLONE + xor rdi, rdi + syscall + test rax, rax + je exit + + // Test behavior of signals + mov rax, 67 // SYS_SIGACTION + mov rdi, 10 // SIGUSR1 + push 0 // sa_flags + push 0 // sa_mask[1] + push 0 // sa_mask[0] + push [rbp] // sa_handler + mov rsi, rsp + xor rdx, rdx + mov r10, [rbp+0x8] + syscall + add rsp, 8*4 + + mov rax, 20 // SYS_GETPID + syscall + + mov rdi, rax + mov rax, 37 // SYS_KILL + mov rsi, 10 // SIGUSR1 + syscall + + // and again + mov rax, 37 // SYS_KILL + syscall + // aaaaand yet again... + mov rax, 37 // SYS_KILL + syscall + // test int3 + int3 + + mov rax, 200 // SYS_GETGID + syscall + + // Test behavior if tracer aborts a breakpoint before it's reached + call wait_for_a_while + + mov rax, 158 // SYS_YIELD + syscall + + mov rax, 20 // SYS_GETPID + syscall + + mov rdi, rax + mov rax, 37 // SYS_KILL + mov rsi, 19 // SIGSTOP + syscall + + // Test nonblock & sysemu + call wait_for_a_while + + exit: + mov rax, 20 // SYS_GETPID + syscall + + mov rdi, rax + mov rax, 1 // SYS_EXIT + syscall + ud2 + + // Without a jump, this code is unreachable. Therefore function definitions go here. + + wait_for_a_while: + mov rax, 4294967295 + wait_for_a_while_loop: + sub rax, 1 + jne wait_for_a_while_loop + ret + " + : // no outputs + : "r"(sighandler as usize), "r"(sigreturn as usize) + : // no clobbers + : "intel", "volatile" + ); + } + } + + println!("My PID: {}", e(syscall::getpid())?); + println!("Waiting until child (pid {}) is ready to be traced...", pid); + let mut status = 0; + e(syscall::waitpid(pid, &mut status, syscall::WUNTRACED))?; + + println!("The process is stopped, of course, so it shouldn't mind if we sleep here"); + thread::sleep(Duration::from_secs(1)); + + println!("Done! Attaching tracer..."); + + // Stop and attach process + get handle to registers. This also + // tests the behavior of dup(...) + let proc_file = e( + OpenOptions::new() + .read(true) + .write(true) + .truncate(true) + .custom_flags(syscall::O_EXCL as i32) + .open(format!("proc:{}/trace", pid)))?; + let regs_file = unsafe { + File::from_raw_fd(e(syscall::dup(proc_file.as_raw_fd() as usize, b"regs/int"))? as RawFd) + }; + let regs_file_float = unsafe { + File::from_raw_fd(e(syscall::dup(regs_file.as_raw_fd() as usize, b"regs/float"))? as RawFd) + }; + + let mut tracer = Tracer { + file: proc_file, + regs: Registers { + float: regs_file_float, + int: regs_file + }, + mem: e(Memory::attach(pid))? + }; + + fn next(tracer: &mut Tracer, flags: Flags) -> io::Result<&mut Tracer> { + let event = tracer.next(flags)?; + assert_eq!(event.cause & flags, event.cause); + Ok(tracer) + } + + println!("Schedule restart of process when resumed..."); + e(syscall::kill(pid, syscall::SIGCONT))?; + + println!("But the process won't be restarted until then"); + thread::sleep(Duration::from_secs(1)); + + println!("Stepping away from the syscall instruction..."); + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + + println!("Testing basic singlestepping..."); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 1); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 2); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 2); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 3); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 2); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SINGLESTEP))?.regs.get_int())?.rax, 1); + + println!("Testing memory access..."); + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + let regs = e(tracer.regs.get_int())?; + + unsafe { + union Stack { + words: [usize; 3], + bytes: [u8; 3 * mem::size_of::()] + } + let mut out = Stack { words: [0; 3] }; + e(tracer.mem.read(regs.rsp as *const _, &mut out.bytes))?; + assert_eq!(out.words, [1, 2, 3]); + assert_eq!(e(tracer.mem.cursor())? as usize, regs.rsp + out.bytes.len()); + } + + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + + println!("Testing floating point..."); + for _ in 0..3 { + e(next(&mut tracer, Flags::STOP_SINGLESTEP))?; + } + let regs = e(tracer.regs.get_float())?; + let f = regs.st_space_nth(0); + let fs = regs.st_space(); + assert_eq!(fs, [f, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + assert!((f - 5.65685424949238).abs() < std::f64::EPSILON); + + println!("Testing fork event"); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL | Flags::EVENT_CLONE))?.regs.get_int())?.rax, syscall::SYS_CLONE); + let mut handler = e(tracer.next_event(Flags::STOP_POST_SYSCALL | Flags::EVENT_CLONE))?; + + let event = e(e(handler.pop_one())?.ok_or("Expected event but none occured"))?; + let clone_pid = match event.data { + EventData::EventClone(pid) => { + println!("Obtained fork (PID {})", pid); + pid + }, + ref e => return Err(format!("Wrong event type: {:?}", e)) + }; + + let event = e(e(handler.pop_one())?.ok_or("Expected event but none occured"))?; + assert_eq!(event.cause, Flags::STOP_POST_SYSCALL); + assert_eq!(e(tracer.regs.get_int())?.rax, clone_pid); + + println!("Testing fork event - but actually handling the fork"); + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL | Flags::EVENT_CLONE))?.regs.get_int())?.rax, syscall::SYS_WAITPID); + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_CLONE); + let mut handler = e(tracer.next_event(Flags::STOP_PRE_SYSCALL | Flags::EVENT_CLONE))?; + + let event = e(e(handler.pop_one())?.ok_or("Expected event but none occured"))?; + match event.data { + EventData::EventClone(pid) => { + let mut child = e(Tracer::attach(pid))?; + println!("-> Fork attached (PID {})", pid); + + assert_eq!(e(e(next(&mut child, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_GETPID); + e(child.next(Flags::STOP_POST_SYSCALL))?; + println!("-> Fork executed GETPID"); + + assert_eq!(e(e(next(&mut child, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_EXIT); + assert_eq!(child.next(Flags::empty()).unwrap_err().raw_os_error(), Some(syscall::ESRCH)); + println!("-> Fork executed EXIT"); + }, + ref e => return Err(format!("Wrong event type: {:?}", e)) + } + e(e(handler.pop_one())?.ok_or("Expected event but none occured"))?; + + println!("Testing signals"); + assert_eq!(e(tracer.regs.get_int())?.rax, syscall::SYS_SIGACTION); + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_GETPID); + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_KILL); + + let event = e(tracer.next(Flags::STOP_PRE_SYSCALL | Flags::STOP_SIGNAL))?; + + assert_eq!(event.cause, Flags::STOP_SIGNAL); + match event.data { + EventData::StopSignal(signal, handler) => { + assert_eq!(signal, syscall::SIGUSR1); + assert_ne!(handler, syscall::SIG_DFL); + assert_ne!(handler, syscall::SIG_IGN); + }, + ref e => return Err(format!("Wrong event type: {:?}", e)) + } + + for i in 0..2 { + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_YIELD); + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; + assert_eq!(e(e(next(&mut tracer, Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_SIGRETURN); + // sigreturn doesn't return + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; // post-syscall kill! + + if i == 0 { + assert_eq!(e(e(next(&mut tracer, Flags::STOP_SIGNAL))?.regs.get_int())?.rax, syscall::SYS_KILL); + } + } + + println!("Test ignoring signal"); + let event = e(tracer.next(Flags::STOP_SIGNAL | Flags::STOP_POST_SYSCALL))?; + assert_eq!(e(tracer.regs.get_int())?.rax, syscall::SYS_KILL); + assert_eq!(event.cause, Flags::STOP_SIGNAL); + match event.data { + EventData::StopSignal(signal, _) => assert_eq!(signal, syscall::SIGUSR1), + ref e => return Err(format!("Wrong event type: {:?}", e)) + } + + println!("Test ignoring int3"); + let event = e(tracer.next(Flags::FLAG_IGNORE | Flags::STOP_BREAKPOINT))?; + assert_eq!(event.cause, Flags::STOP_BREAKPOINT); + assert_eq!(e(e(next(&mut tracer, Flags::FLAG_IGNORE | Flags::STOP_PRE_SYSCALL))?.regs.get_int())?.rax, syscall::SYS_GETGID); + e(next(&mut tracer, Flags::STOP_POST_SYSCALL))?; + + // Activate nonblock + let mut tracer = e(tracer.nonblocking())?; + + println!("Testing behavior of obsolete breakpoints..."); + e(tracer.next(Flags::STOP_PRE_SYSCALL | Flags::STOP_POST_SYSCALL))?; + e(tracer.next(Flags::empty()))?; + + // also, we're nonblocking, can't wait for next event like that + assert_eq!(e(tracer.events())?.next().unwrap().unwrap_err().kind(), io::ErrorKind::WouldBlock); + + println!("Tracee RAX: {}", e(tracer.regs.get_int())?.rax); + + println!("Waiting for next signal from tracee that it's ready to be traced again..."); + e(syscall::waitpid(pid, &mut status, syscall::WUNTRACED))?; + + println!("Preparing event scheme"); + let mut eventfd = e(File::open("event:"))?; + e(eventfd.write(&syscall::Event { + id: tracer.file.as_raw_fd() as usize, + flags: syscall::EVENT_READ, + data: 0, + }))?; + + println!("Setting sysemu breakpoint..."); + e(tracer.next(Flags::STOP_PRE_SYSCALL))?; + + println!("Schedule restart of process after breakpoint is set..."); + e(syscall::kill(pid, syscall::SIGCONT))?; + + println!("After non-blocking ptrace, execution continues as normal:"); + for _ in 0..5 { + println!("Tracee RAX: {}", e(tracer.regs.get_int())?.rax); + } + + println!("Waiting using event scheme"); + let mut event = syscall::Event::default(); + e(eventfd.read(&mut event))?; + + println!("Consuming events"); + + e(tracer.events())?.for_each(|_| ()); + + println!("Overriding GETPID call..."); + let mut regs = e(tracer.regs.get_int())?; + assert_eq!(regs.rax, syscall::SYS_GETPID); + regs.rax = 123; + e(tracer.regs.set_int(®s))?; + + let mut tracer = e(tracer.blocking())?; + + println!("Checking exit syscall..."); + e(next(&mut tracer, Flags::STOP_PRE_SYSCALL | Flags::STOP_EXIT | Flags::FLAG_IGNORE))?; + let regs = e(tracer.regs.get_int())?; + assert_eq!(regs.rax, syscall::SYS_EXIT); + assert_eq!(regs.rdi, 123); + + println!("Checking exit breakpoint..."); + let event = e(tracer.next(Flags::STOP_POST_SYSCALL | Flags::STOP_EXIT))?; + + assert_eq!(event.cause, Flags::STOP_EXIT); + match event.data { + EventData::StopExit(status) => { + assert!(syscall::wifexited(status)); + assert_eq!(syscall::wexitstatus(status), 123); + }, + ref e => return Err(format!("Wrong event type: {:?}", e)) + } + + println!("Checking exit status (waitpid nohang)..."); + assert_eq!(next(&mut tracer, Flags::STOP_POST_SYSCALL | Flags::STOP_EXIT).unwrap_err().raw_os_error(), Some(syscall::ESRCH)); + + let mut status = 0; + e(syscall::waitpid(pid, &mut status, syscall::WNOHANG))?; + assert!(syscall::wifexited(status)); + assert_eq!(syscall::wexitstatus(status), 123); + + println!("Trying to do illegal things..."); + for id in 0..=1_000_000 { + let err = File::open(format!("proc:{}/regs/int", id)).map(|_| None).unwrap_or_else(|err| err.raw_os_error()); + assert!( + err == Some(syscall::EPERM) || err == Some(syscall::ESRCH), + "The cops ignored that I tried to illegally open PID {}: {:?}", id, err + ); + } + + println!("All done and tested!"); + + Ok(()) +} + fn switch_test() -> Result<(), String> { use std::thread; use x86::time::rdtscp; @@ -181,6 +623,7 @@ fn main() { let mut tests: BTreeMap<&'static str, fn() -> Result<(), String>> = BTreeMap::new(); tests.insert("create_test", create_test); tests.insert("page_fault", page_fault_test); + tests.insert("ptrace", ptrace); tests.insert("switch", switch_test); tests.insert("tcp_fin", tcp_fin_test); tests.insert("thread", thread_test);