-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add --fail-path #4
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,6 +187,9 @@ struct Opt { | |
/// Child will inherit all environment vars visible to lxtrace | ||
#[structopt(long)] | ||
inherit_env: bool, | ||
/// Accesses to this path will fail with some probability | ||
#[structopt(long)] | ||
fail_path: Option<PathBuf>, | ||
} | ||
|
||
fn main() -> anyhow::Result<()> { | ||
|
@@ -196,6 +199,10 @@ fn main() -> anyhow::Result<()> { | |
eprintln!("executable not provided"); | ||
exit(1); | ||
} | ||
opt.fail_path = match opt.fail_path { | ||
Some(path) => std::fs::canonicalize(path).ok(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why error is ignored here? |
||
None => None | ||
}; | ||
if opt.inherit_env { | ||
// TODO: probably this doesn't interact well with --env | ||
for (k, v) in std::env::vars_os() { | ||
|
@@ -233,6 +240,7 @@ fn main() -> anyhow::Result<()> { | |
let payload = lxtrace::Payload::Cmd(cmd_args); | ||
let settings = lxtrace::Settings { | ||
capture_backtrace: opt.backtrace, | ||
fail_path: opt.fail_path, | ||
}; | ||
|
||
if let Err(e) = lxtrace::run(payload, settings, sender) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,20 +12,24 @@ use nix::{ | |
unistd::Pid, | ||
}; | ||
use std::collections::HashMap; | ||
use std::path::PathBuf; | ||
use tiny_nix_ipc::Socket; | ||
use rand::Rng; | ||
|
||
struct ChildInfo { | ||
in_syscall: bool, | ||
in_spoiled: bool, | ||
spoiled_syscall: u64, | ||
} | ||
|
||
fn decode_syscall_args(regs: libc::user_regs_struct) -> RawSyscall { | ||
fn decode_syscall_args(regs: &libc::user_regs_struct) -> RawSyscall { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needless reference here, because this struct implements Copy. |
||
let mut out = RawSyscall { | ||
syscall_id: 0, | ||
args: [0; 6], | ||
ret: 0, | ||
}; | ||
out.ret = regs.rax; | ||
out.syscall_id = regs.orig_rax; | ||
out.syscall_id = regs.orig_rax & 0xffffffu64; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this still needed? |
||
out.args[0] = regs.rdi; | ||
out.args[1] = regs.rsi; | ||
out.args[2] = regs.rdx; | ||
|
@@ -35,6 +39,26 @@ fn decode_syscall_args(regs: libc::user_regs_struct) -> RawSyscall { | |
out | ||
} | ||
|
||
fn spoil(pid: Pid, children: &mut HashMap<u32, ChildInfo>, regs: libc::user_regs_struct) -> Result<(), nix::Error> { | ||
let ci1 = children.get(&(pid.as_raw() as u32)).unwrap(); | ||
let ci2 = ChildInfo { | ||
in_syscall: ci1.in_syscall, | ||
in_spoiled: true, | ||
spoiled_syscall: regs.orig_rax, | ||
}; | ||
children.insert(pid.as_raw() as u32, ci2); | ||
let mut regs = regs; | ||
regs.rax = 39 /*getpid*/; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add comments that describe what's happening here |
||
regs.orig_rax = 39 /*getpid*/; | ||
nix::sys::ptrace::setregs(pid, regs) | ||
} | ||
|
||
fn return_eio(pid: Pid, regs: &mut libc::user_regs_struct, syscall: u64) -> Result<(), nix::Error> { | ||
regs.orig_rax = syscall; | ||
regs.rax = -(nix::errno::Errno::EIO as i32 as i64) as u64; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
nix::sys::ptrace::setregs(pid, *regs) | ||
} | ||
|
||
fn process_syscall( | ||
raw: &RawSyscall, | ||
proc: Pid, | ||
|
@@ -125,7 +149,7 @@ pub(crate) unsafe fn parent( | |
) | ||
.context("ptrace setoptions failed")?; | ||
|
||
let new_child_info = ChildInfo { in_syscall: false }; | ||
let new_child_info = ChildInfo { in_syscall: false, in_spoiled: false, spoiled_syscall: 0 }; | ||
children.insert(pid, new_child_info); | ||
|
||
let event = Event { | ||
|
@@ -148,19 +172,37 @@ pub(crate) unsafe fn parent( | |
(true, WaitStatus::PtraceSyscall(_)) => { | ||
let cur_info = children.get(&pid).unwrap(); // it's guaranteed here that get() returns Some | ||
let started_syscall = !cur_info.in_syscall; | ||
let is_spoiled = cur_info.in_spoiled; | ||
let spoiled_syscall = cur_info.spoiled_syscall; | ||
let new_info = ChildInfo { | ||
in_syscall: started_syscall, | ||
in_spoiled: false, | ||
spoiled_syscall: 0, | ||
}; | ||
children.insert(pid, new_info); | ||
let regs = nix::sys::ptrace::getregs(Pid::from_raw(pid as i32)) | ||
let mut regs = nix::sys::ptrace::getregs(Pid::from_raw(pid as i32)) | ||
.context("ptrace getregs failed")?; | ||
let params = decode_syscall_args(regs); | ||
if is_spoiled { | ||
return_eio(Pid::from_raw(pid as i32), &mut regs, spoiled_syscall).context("return -EIO failed")?; | ||
} | ||
let params = decode_syscall_args(®s); | ||
let def = magic.lookup_syscall_by_id(SyscallId(params.syscall_id as u32)); | ||
let child_pid = Pid::from_raw(pid as i32); | ||
let mut decoded_params = match def { | ||
Some(def) => process_syscall(¶ms, child_pid, magic, def), | ||
None => None, | ||
}; | ||
if started_syscall && decoded_params.is_some() && (params.syscall_id == 2 /*open*/ || params.syscall_id == 257 /*openat*/) { //TODO: x86_64 only | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add some comment that describes what this if does. if let Some(fail_path) = &setting.fail_path {
// ...
} |
||
let decoded_params = decoded_params.as_ref().unwrap(); | ||
let arg_id = if params.syscall_id == 2 {0} else {1}; | ||
let path: PathBuf = match &decoded_params.args[arg_id] { | ||
crate::magic::ty::Value::String(s) => s, | ||
_ => panic!("open/openat with non-string argument") | ||
}.into(); | ||
if settings.fail_path.is_some() && path.starts_with(settings.fail_path.as_ref().unwrap()) && rand::thread_rng().gen_range(0, 100) == 0 { | ||
spoil(Pid::from_raw(pid as i32), &mut children, regs).context("spoil failed")?; | ||
} | ||
} | ||
decoded_params.as_mut().map(|p| { | ||
// attach backtrace if requested | ||
if settings.capture_backtrace { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.