jail/
fork.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Provides [fork_process] to fork a process.
6
7#![deny(missing_docs)]
8
9use std::ffi::CString;
10use std::mem::ManuallyDrop;
11use std::os::unix::process::ExitStatusExt;
12use std::process;
13
14use base::error;
15use base::linux::wait_for_pid;
16use base::Pid;
17use base::RawDescriptor;
18#[cfg(feature = "seccomp_trace")]
19use log::debug;
20use log::warn;
21use minijail::Minijail;
22
23/// Child represents the forked process.
24pub struct Child {
25    /// The pid of the child process.
26    pub pid: Pid,
27}
28
29impl Child {
30    /// Wait for the child process exit using `waitpid(2)`.
31    pub fn wait(self) -> base::Result<u8> {
32        // Suppress warning from the drop().
33        let pid = self.into_pid();
34        let (_, status) = wait_for_pid(pid, 0)?;
35        if let Some(exit_code) = status.code() {
36            Ok(exit_code as u8)
37        } else if let Some(signal) = status.signal() {
38            let exit_code = if signal >= 128 {
39                warn!("wait for child: unexpected signal({:?})", signal);
40                255
41            } else {
42                128 + signal as u8
43            };
44            Ok(exit_code)
45        } else {
46            unreachable!("waitpid with option 0 only waits for exited and signaled status");
47        }
48    }
49
50    /// Convert [Child] into [Pid].
51    ///
52    /// If [Child] is dropped without `Child::wait()`, it logs warning message. Users who wait
53    /// processes in other ways should suppress the warning by unwrapping [Child] into [Pid].
54    ///
55    /// The caller of this method now owns the process and is responsible for managing the
56    /// termination of the process.
57    pub fn into_pid(self) -> Pid {
58        let pid = self.pid;
59        // Suppress warning from the drop().
60        let _ = ManuallyDrop::new(self);
61        pid
62    }
63}
64
65impl Drop for Child {
66    fn drop(&mut self) {
67        warn!("the child process have not been waited.: {}", self.pid);
68    }
69}
70
71/// Forks this process using [Minijail] and calls a closure in the new process.
72///
73/// After `post_fork_cb` returns, the new process exits with `0` code. If `post_fork_cb` panics, the
74/// new process exits with `101` code.
75///
76/// This function never returns in the forked process.
77///
78/// # Arguments
79///
80/// * `jail` - [Minijail] instance to fork.
81/// * `keep_rds` - [RawDescriptor]s to be kept in the forked process. other file descriptors will be
82///   closed by [Minijail] in the forked process.
83/// * `debug_label` - (optional) thread name. this will be trimmed to 15 charactors.
84/// * `post_fork_cb` - Callback to run in the new process.
85pub fn fork_process<F>(
86    jail: Minijail,
87    mut keep_rds: Vec<RawDescriptor>,
88    debug_label: Option<String>,
89    post_fork_cb: F,
90) -> minijail::Result<Child>
91where
92    F: FnOnce(),
93{
94    // Deduplicate the FDs since minijail expects this.
95    keep_rds.sort_unstable();
96    keep_rds.dedup();
97
98    // SAFETY:
99    // Safe because the program is still single threaded.
100    // We own the jail object and nobody else will try to reuse it.
101    let pid = match unsafe { jail.fork(Some(&keep_rds)) }? {
102        0 => {
103            struct ExitGuard;
104            impl Drop for ExitGuard {
105                fn drop(&mut self) {
106                    // Rust exits with 101 when panics.
107                    process::exit(101);
108                }
109            }
110            // Prevents a panic in post_fork_cb from bypassing the process::exit.
111            let _exit_guard = ExitGuard {};
112
113            if let Some(debug_label) = debug_label {
114                // pthread_setname_np() limit on Linux
115                const MAX_THREAD_LABEL_LEN: usize = 15;
116                let debug_label_trimmed = &debug_label.as_bytes()
117                    [..std::cmp::min(MAX_THREAD_LABEL_LEN, debug_label.len())];
118                match CString::new(debug_label_trimmed) {
119                    Ok(thread_name) => {
120                        // SAFETY:
121                        // Safe because thread_name is a valid pointer and setting name of this
122                        // thread should be safe.
123                        let _ = unsafe {
124                            libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr())
125                        };
126                    }
127                    Err(e) => {
128                        error!("failed to compile thread name: {:?}", e);
129                    }
130                }
131            }
132
133            post_fork_cb();
134            // ! Never returns
135            process::exit(0);
136        }
137        pid => pid,
138    };
139    #[cfg(feature = "seccomp_trace")]
140    debug!(
141            // Proxy and swap devices fork here
142            "seccomp_trace {{\"event\": \"minijail_fork\", \"pid\": \"{}\", \"name\": \"{}\", \"jail_addr\": \"0x{:x}\"}}",
143            pid,
144            match debug_label {
145                Some(debug_label) => debug_label,
146                None => "process.rs: no debug label".to_owned(),
147            },
148            // Can't use safe wrapper because jail crate depends on base
149            // SAFETY:
150            // Safe because it's only doing a read within bound checked by static assert
151            unsafe {*(&jail as *const Minijail as *const usize)}
152        );
153    Ok(Child { pid })
154}