Skip to content

Commit 6ccaf1c

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

File tree

4 files changed

+49
-3
lines changed

4 files changed

+49
-3
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

+3
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)]
@@ -22,6 +24,7 @@ pub struct RawSyscall {
2224
pub syscall_id: u64,
2325
pub args: [u64; 6],
2426
pub ret: u64,
27+
pub is_spoiled: bool,
2528
}
2629

2730
#[derive(Debug, Serialize, Deserialize)]

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

+37-3
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,46 @@ 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,
1921
}
2022

21-
fn decode_syscall_args(regs: libc::user_regs_struct) -> RawSyscall {
23+
fn decode_syscall_args(regs: &libc::user_regs_struct) -> RawSyscall {
2224
let mut out = RawSyscall {
2325
syscall_id: 0,
2426
args: [0; 6],
2527
ret: 0,
28+
is_spoiled: false
2629
};
2730
out.ret = regs.rax;
28-
out.syscall_id = regs.orig_rax;
31+
out.syscall_id = regs.orig_rax & 0xffffffu64;
2932
out.args[0] = regs.rdi;
3033
out.args[1] = regs.rsi;
3134
out.args[2] = regs.rdx;
3235
out.args[3] = regs.r10;
3336
out.args[4] = regs.r8;
3437
out.args[5] = regs.r9;
38+
out.is_spoiled = (regs.orig_rax & 0x1000000u64) != 0;
3539
out
3640
}
3741

42+
fn spoil(pid: Pid, regs: libc::user_regs_struct) -> Result<(), nix::Error> {
43+
let mut regs = regs;
44+
regs.rax = regs.orig_rax | 0x1000000u64;
45+
regs.orig_rax = regs.rax;
46+
nix::sys::ptrace::setregs(pid, regs)
47+
}
48+
49+
fn return_eio(pid: Pid, regs: libc::user_regs_struct) -> Result<(), nix::Error> {
50+
let mut regs = regs;
51+
regs.rax = -(nix::errno::Errno::EIO as i32 as i64) as u64;
52+
nix::sys::ptrace::setregs(pid, regs)
53+
}
54+
3855
fn process_syscall(
3956
raw: &RawSyscall,
4057
proc: Pid,
@@ -154,13 +171,30 @@ pub(crate) unsafe fn parent(
154171
children.insert(pid, new_info);
155172
let regs = nix::sys::ptrace::getregs(Pid::from_raw(pid as i32))
156173
.context("ptrace getregs failed")?;
157-
let params = decode_syscall_args(regs);
174+
let params = decode_syscall_args(&regs);
158175
let def = magic.lookup_syscall_by_id(SyscallId(params.syscall_id as u32));
159176
let child_pid = Pid::from_raw(pid as i32);
160177
let mut decoded_params = match def {
161178
Some(def) => process_syscall(&params, child_pid, magic, def),
162179
None => None,
163180
};
181+
if decoded_params.is_some() && (params.syscall_id == 2 /*open*/ || params.syscall_id == 257 /*openat*/) { //TODO: x86_64 only
182+
let decoded_params = decoded_params.as_ref().unwrap();
183+
let arg_id = if params.syscall_id == 2 {0} else {1};
184+
let path: PathBuf = match &decoded_params.args[arg_id] {
185+
crate::magic::ty::Value::String(s) => s,
186+
_ => panic!("open/openat with non-string argument")
187+
}.into();
188+
if started_syscall {
189+
if settings.fail_path.is_some() && path.starts_with(settings.fail_path.as_ref().unwrap()) && rand::thread_rng().gen_range(0, 100) == 0 {
190+
spoil(Pid::from_raw(pid as i32), regs).context("spoil failed")?;
191+
}
192+
} else {
193+
if params.is_spoiled {
194+
return_eio(Pid::from_raw(pid as i32), regs).context("return -EIO failed")?;
195+
}
196+
}
197+
}
164198
decoded_params.as_mut().map(|p| {
165199
// attach backtrace if requested
166200
if settings.capture_backtrace {

0 commit comments

Comments
 (0)