Skip to content

Commit bc348c1

Browse files
committed
Add --fail-path
1 parent 4fa9e73 commit bc348c1

File tree

4 files changed

+58
-5
lines changed

4 files changed

+58
-5
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ anyhow = "1.0.14"
2121
rstack = {version = "0.3.1", default-features = false, features = ["unwind"]}
2222
rustc-demangle = "0.1.16"
2323
cpp_demangle = "0.2.13"
24+
rand = ""

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ use magic::Magic;
1111
use serde::{Deserialize, Serialize};
1212
use std::{mem, os::unix::io::RawFd};
1313
use tiny_nix_ipc::Socket;
14+
use std::path::PathBuf;
1415

1516
pub struct Settings {
1617
pub capture_backtrace: bool,
18+
pub fail_path: Option<PathBuf>,
1719
}
1820

1921
#[repr(C)]

src/main.rs

+8
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ struct Opt {
187187
/// Child will inherit all environment vars visible to lxtrace
188188
#[structopt(long)]
189189
inherit_env: bool,
190+
/// Accesses to this path will fail with some probability
191+
#[structopt(long)]
192+
fail_path: Option<PathBuf>,
190193
}
191194

192195
fn main() -> anyhow::Result<()> {
@@ -196,6 +199,10 @@ fn main() -> anyhow::Result<()> {
196199
eprintln!("executable not provided");
197200
exit(1);
198201
}
202+
opt.fail_path = match opt.fail_path {
203+
Some(path) => std::fs::canonicalize(path).ok(),
204+
None => None
205+
};
199206
if opt.inherit_env {
200207
// TODO: probably this doesn't interact well with --env
201208
for (k, v) in std::env::vars_os() {
@@ -233,6 +240,7 @@ fn main() -> anyhow::Result<()> {
233240
let payload = lxtrace::Payload::Cmd(cmd_args);
234241
let settings = lxtrace::Settings {
235242
capture_backtrace: opt.backtrace,
243+
fail_path: opt.fail_path,
236244
};
237245

238246
if let Err(e) = lxtrace::run(payload, settings, sender) {

src/tracer.rs

+47-5
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,24 @@ use nix::{
1212
unistd::Pid,
1313
};
1414
use std::collections::HashMap;
15+
use std::path::PathBuf;
1516
use tiny_nix_ipc::Socket;
17+
use rand::Rng;
1618

1719
struct ChildInfo {
1820
in_syscall: bool,
21+
in_spoiled: bool,
22+
spoiled_syscall: u64,
1923
}
2024

21-
fn decode_syscall_args(regs: libc::user_regs_struct) -> RawSyscall {
25+
fn decode_syscall_args(regs: &libc::user_regs_struct) -> RawSyscall {
2226
let mut out = RawSyscall {
2327
syscall_id: 0,
2428
args: [0; 6],
2529
ret: 0,
2630
};
2731
out.ret = regs.rax;
28-
out.syscall_id = regs.orig_rax;
32+
out.syscall_id = regs.orig_rax & 0xffffffu64;
2933
out.args[0] = regs.rdi;
3034
out.args[1] = regs.rsi;
3135
out.args[2] = regs.rdx;
@@ -35,6 +39,26 @@ fn decode_syscall_args(regs: libc::user_regs_struct) -> RawSyscall {
3539
out
3640
}
3741

42+
fn spoil(pid: Pid, children: &mut HashMap<u32, ChildInfo>, regs: libc::user_regs_struct) -> Result<(), nix::Error> {
43+
let ci1 = children.get(&(pid.as_raw() as u32)).unwrap();
44+
let ci2 = ChildInfo {
45+
in_syscall: ci1.in_syscall,
46+
in_spoiled: true,
47+
spoiled_syscall: regs.orig_rax,
48+
};
49+
children.insert(pid.as_raw() as u32, ci2);
50+
let mut regs = regs;
51+
regs.rax = 39 /*getpid*/;
52+
regs.orig_rax = 39 /*getpid*/;
53+
nix::sys::ptrace::setregs(pid, regs)
54+
}
55+
56+
fn return_eio(pid: Pid, regs: &mut libc::user_regs_struct, syscall: u64) -> Result<(), nix::Error> {
57+
regs.orig_rax = syscall;
58+
regs.rax = -(nix::errno::Errno::EIO as i32 as i64) as u64;
59+
nix::sys::ptrace::setregs(pid, *regs)
60+
}
61+
3862
fn process_syscall(
3963
raw: &RawSyscall,
4064
proc: Pid,
@@ -125,7 +149,7 @@ pub(crate) unsafe fn parent(
125149
)
126150
.context("ptrace setoptions failed")?;
127151

128-
let new_child_info = ChildInfo { in_syscall: false };
152+
let new_child_info = ChildInfo { in_syscall: false, in_spoiled: false, spoiled_syscall: 0 };
129153
children.insert(pid, new_child_info);
130154

131155
let event = Event {
@@ -148,19 +172,37 @@ pub(crate) unsafe fn parent(
148172
(true, WaitStatus::PtraceSyscall(_)) => {
149173
let cur_info = children.get(&pid).unwrap(); // it's guaranteed here that get() returns Some
150174
let started_syscall = !cur_info.in_syscall;
175+
let is_spoiled = cur_info.in_spoiled;
176+
let spoiled_syscall = cur_info.spoiled_syscall;
151177
let new_info = ChildInfo {
152178
in_syscall: started_syscall,
179+
in_spoiled: false,
180+
spoiled_syscall: 0,
153181
};
154182
children.insert(pid, new_info);
155-
let regs = nix::sys::ptrace::getregs(Pid::from_raw(pid as i32))
183+
let mut regs = nix::sys::ptrace::getregs(Pid::from_raw(pid as i32))
156184
.context("ptrace getregs failed")?;
157-
let params = decode_syscall_args(regs);
185+
if is_spoiled {
186+
return_eio(Pid::from_raw(pid as i32), &mut regs, spoiled_syscall).context("return -EIO failed")?;
187+
}
188+
let params = decode_syscall_args(&regs);
158189
let def = magic.lookup_syscall_by_id(SyscallId(params.syscall_id as u32));
159190
let child_pid = Pid::from_raw(pid as i32);
160191
let mut decoded_params = match def {
161192
Some(def) => process_syscall(&params, child_pid, magic, def),
162193
None => None,
163194
};
195+
if started_syscall && decoded_params.is_some() && (params.syscall_id == 2 /*open*/ || params.syscall_id == 257 /*openat*/) { //TODO: x86_64 only
196+
let decoded_params = decoded_params.as_ref().unwrap();
197+
let arg_id = if params.syscall_id == 2 {0} else {1};
198+
let path: PathBuf = match &decoded_params.args[arg_id] {
199+
crate::magic::ty::Value::String(s) => s,
200+
_ => panic!("open/openat with non-string argument")
201+
}.into();
202+
if settings.fail_path.is_some() && path.starts_with(settings.fail_path.as_ref().unwrap()) && rand::thread_rng().gen_range(0, 100) == 0 {
203+
spoil(Pid::from_raw(pid as i32), &mut children, regs).context("spoil failed")?;
204+
}
205+
}
164206
decoded_params.as_mut().map(|p| {
165207
// attach backtrace if requested
166208
if settings.capture_backtrace {

0 commit comments

Comments
 (0)