// Copyright 2017 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::fs; use std::io; use std::path::Path; use std::process; use std::result; use libc::{c_long, pid_t, syscall, CLONE_NEWPID, CLONE_NEWUSER, SIGCHLD}; use syscall_defines::linux::LinuxSyscall::SYS_clone; use crate::errno_result; /// Controls what namespace `clone_process` will have. See NAMESPACES(7). #[repr(u32)] pub enum CloneNamespace { /// The new process will inherit the namespace from the old process. Inherit = 0, /// The new process with be in a new user and PID namespace. NewUserPid = CLONE_NEWUSER as u32 | CLONE_NEWPID as u32, } #[derive(Debug)] pub enum CloneError { /// There was an error trying to iterate this process's threads. IterateTasks(io::Error), /// There are multiple threads running. The `usize` indicates how many threads. Multithreaded(usize), /// There was an error while cloning. Sys(crate::Error), } unsafe fn do_clone(flags: i32) -> crate::Result { // Forking is unsafe, this function must be unsafe as there is no way to guarantee safety // without more context about the state of the program. let pid = syscall(SYS_clone as c_long, flags | SIGCHLD as i32, 0); if pid < 0 { errno_result() } else { Ok(pid as pid_t) } } fn count_dir_entries>(path: P) -> io::Result { Ok(fs::read_dir(path)?.count()) } /// Clones this process and calls a closure in the new process. /// /// After `post_clone_cb` returns or panics, the new process exits. Similar to how a `fork` syscall /// works, the new process is the same as the current process with the exception of the namespace /// controlled with the `ns` argument. /// /// # Arguments /// * `ns` - What namespace the new process will have (see NAMESPACES(7)). /// * `post_clone_cb` - Callback to run in the new process pub fn clone_process(ns: CloneNamespace, post_clone_cb: F) -> result::Result where F: FnOnce(), { match count_dir_entries("/proc/self/task") { Ok(1) => {} Ok(thread_count) => { // Test cfg gets a free pass on this because tests generally have multiple independent // test threads going. let _ = thread_count; #[cfg(not(test))] return Err(CloneError::Multithreaded(thread_count)); } Err(e) => return Err(CloneError::IterateTasks(e)), } // Forking is considered unsafe in mutlithreaded programs, but we just checked for other threads // in this process. We also only allow valid flags from CloneNamespace and check the return // result for errors. We also never let the cloned process return from this function. let ret = unsafe { do_clone(ns as i32) }.map_err(CloneError::Sys)?; if ret == 0 { struct ExitGuard; impl Drop for ExitGuard { fn drop(&mut self) { process::exit(101); } } // Prevents a panic in post_clone_cb from bypassing the process::exit. #[allow(unused_variables)] let exit_guard = ExitGuard {}; post_clone_cb(); // ! Never returns process::exit(0); } Ok(ret) } #[cfg(test)] mod tests { use super::*; use crate::{getpid, EventFd}; use libc; fn wait_process(pid: libc::pid_t) -> crate::Result { let mut status: libc::c_int = 0; unsafe { if libc::waitpid(pid, &mut status as *mut libc::c_int, 0) < 0 { errno_result() } else { Ok(libc::WEXITSTATUS(status)) } } } #[test] fn pid_diff() { let evt_fd = EventFd::new().expect("failed to create EventFd"); let evt_fd_fork = evt_fd.try_clone().expect("failed to clone EventFd"); let pid = getpid(); clone_process(CloneNamespace::Inherit, || { // checks that this is a genuine fork with a new PID if pid != getpid() { evt_fd_fork.write(1).unwrap() } else { evt_fd_fork.write(2).unwrap() } }) .expect("failed to clone"); assert_eq!(evt_fd.read(), Ok(1)); } #[test] fn panic_safe() { let pid = getpid(); assert_ne!(pid, 0); let clone_pid = clone_process(CloneNamespace::Inherit, || { assert!(false); }) .expect("failed to clone"); // This should never happen; if pid != getpid() { process::exit(2); } let status = wait_process(clone_pid).expect("wait_process failed"); assert!(status == 101 || status == 0); } }