Skip to content

Commit

Permalink
Merge #695
Browse files Browse the repository at this point in the history
695: Document safety of `fork()` and fix tests r=asomers

Some tests were invoking non-async-signal-safe functions from the child
process after a `fork`. Since they might be invoked in parallel, this
could lead to problems.

cc #586
  • Loading branch information
bors[bot] committed Jul 23, 2017
2 parents 59d6b3c + 91fa117 commit 4b686d8
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 12 deletions.
16 changes: 14 additions & 2 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ impl ForkResult {
/// Continuing execution in parent process, new child has pid: 1234
/// I'm a new child process
/// ```
///
/// # Safety
///
/// In a multithreaded program, only [async-signal-safe] functions like `pause`
/// and `_exit` may be called by the child (the parent isn't restricted). Note
/// that memory allocation may **not** be async-signal-safe and thus must be
/// prevented.
///
/// Those functions are only a small subset of your operating system's API, so
/// special care must be taken to only invoke code you can control and audit.
///
/// [async-signal-safe]: http://man7.org/linux/man-pages/man7/signal-safety.7.html
#[inline]
pub fn fork() -> Result<ForkResult> {
use self::ForkResult::*;
Expand Down Expand Up @@ -687,7 +699,7 @@ pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr> {
/// use nix::unistd::close;
///
/// fn main() {
/// let mut f = tempfile::tempfile().unwrap();
/// let f = tempfile::tempfile().unwrap();
/// close(f.as_raw_fd()).unwrap(); // Bad! f will also close on drop!
/// }
/// ```
Expand All @@ -700,7 +712,7 @@ pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr> {
/// use nix::unistd::close;
///
/// fn main() {
/// let mut f = tempfile::tempfile().unwrap();
/// let f = tempfile::tempfile().unwrap();
/// close(f.into_raw_fd()).unwrap(); // Good. into_raw_fd consumes f
/// }
/// ```
Expand Down
11 changes: 7 additions & 4 deletions test/sys/test_wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use nix::unistd::*;
use nix::unistd::ForkResult::*;
use nix::sys::signal::*;
use nix::sys::wait::*;
use libc::exit;
use libc::_exit;

#[test]
fn test_wait_signal() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
match fork() {
Ok(Child) => pause().unwrap_or(()),
Ok(Child) => pause().unwrap_or_else(|_| unsafe { _exit(123) }),
Ok(Parent { child }) => {
kill(child, Some(SIGKILL)).ok().expect("Error: Kill Failed");
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, SIGKILL, false)));
Expand All @@ -25,8 +26,9 @@ fn test_wait_exit() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Safe: Child only calls `_exit`, which is async-signal-safe.
match fork() {
Ok(Child) => unsafe { exit(12); },
Ok(Child) => unsafe { _exit(12); },
Ok(Parent { child }) => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12)));
},
Expand All @@ -46,13 +48,14 @@ mod ptrace {
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use std::{ptr, process};
use libc::_exit;

fn ptrace_child() -> ! {
let _ = ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut());
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
let _ = raise(SIGTRAP);
process::exit(0)
unsafe { _exit(0) }
}

fn ptrace_parent(child: Pid) {
Expand Down
17 changes: 11 additions & 6 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ use std::io::Write;
use std::os::unix::prelude::*;
use tempfile::tempfile;
use tempdir::TempDir;
use libc::off_t;
use std::process::exit;
use libc::{_exit, off_t};

#[test]
fn test_fork_and_waitpid() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
let pid = fork();
match pid {
Ok(Child) => exit(0),

// Safe: Child only calls `_exit`, which is signal-safe
match fork() {
Ok(Child) => unsafe { _exit(0) },
Ok(Parent { child }) => {
// assert that child was created and pid > 0
let child_raw: ::libc::pid_t = child.into();
Expand Down Expand Up @@ -48,9 +48,11 @@ fn test_wait() {
// Grab FORK_MTX so wait doesn't reap a different test's child process
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Safe: Child only calls `_exit`, which is signal-safe
let pid = fork();
match pid {
Ok(Child) => exit(0),
Ok(Child) => unsafe { _exit(0) },
Ok(Parent { child }) => {
let wait_status = wait();

Expand Down Expand Up @@ -111,6 +113,9 @@ macro_rules! execve_test_factory(
// data from `reader`.
let (reader, writer) = pipe().unwrap();

// Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
// NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
// The tests make sure not to do that, though.
match fork().unwrap() {
Child => {
#[cfg(not(target_os = "android"))]
Expand Down

0 comments on commit 4b686d8

Please sign in to comment.