in reverie-ptrace/src/tracer.rs [140:209]
fn init_tracee(intercept_rdtsc: bool) -> Result<(), Errno> {
// NOTE: There should be *NO* allocations along the happy path here.
// Allocating between a fork() and execve() can cause deadlocks in glibc
// when using jemalloc.
// hardcoded because `libc` does not export these.
const PER_LINUX: u64 = 0x0;
const ADDR_NO_RANDOMIZE: u64 = 0x0004_0000;
if intercept_rdtsc {
unsafe {
assert_eq!(
libc::prctl(libc::PR_SET_TSC, libc::PR_TSC_SIGSEGV, 0, 0, 0),
0
)
};
}
unsafe {
assert!(libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0);
assert!(libc::personality(PER_LINUX | ADDR_NO_RANDOMIZE) != -1);
}
// FIXME: This is a hacky workaround for `std::process::Command::spawn`
// getting stuck in a deadlock because of the SIGSTOP below.
// `Command::spawn` uses a pipe to communicate the error code to the parent
// process if the `execve` fails. The idea is that the write end of the pipe
// will be closed upon a successful call to `execve` and the parent will
// abort the blocking read on the read end of the pipe. We don't know
// exactly which file descriptor the pipe uses, so we attempt to close the
// first N file descriptors hoping it is among those. Unfortunately, in
// doing so, we lose the ability to capture `execve` failures.
//
// There are a couple options for a better implementation:
// 1. Recreate the entire `std::process` module to provide better ptrace
// support. (A lot of work!)
// 2. Don't raise a SIGSTOP, but instead let the ptracer stop on the call to
// `execve` and have the parent set the ptrace options at that point.
for i in 3..256 {
unsafe {
libc::close(i);
}
}
trace::traceme_and_stop()?;
unsafe {
signal::sigaction(
signal::SIGTTIN,
&signal::SigAction::new(
signal::SigHandler::SigIgn,
signal::SaFlags::SA_RESTART,
signal::SigSet::empty(),
),
)
.map_err(from_nix_error)?;
signal::sigaction(
signal::SIGTTOU,
&signal::SigAction::new(
signal::SigHandler::SigIgn,
signal::SaFlags::SA_RESTART,
signal::SigSet::empty(),
),
)
.map_err(from_nix_error)?;
}
Ok(())
}