spawn/
spawn.rs

1//! Spawn subprocesses with more fine-grained control over File Descriptors,
2//! UID/GID, and File Stream handling.
3
4use crate::{clear_capabilities, cond_pipe, dup_null, format_iter, handle::Handle, logger};
5use caps::{Capability, CapsHashSet};
6use dashmap::{DashMap, DashSet, mapref::one::RefMut};
7use log::{trace, warn};
8use nix::{
9    sys::{prctl, signal::Signal::SIGTERM},
10    unistd::{ForkResult, close, dup2_stderr, dup2_stdin, dup2_stdout, execve, fork},
11};
12use parking_lot::Mutex;
13use std::{
14    borrow::Cow,
15    collections::{HashMap, HashSet},
16    ffi::{CString, NulError, OsString},
17    os::fd::OwnedFd,
18    process::exit,
19    str::FromStr,
20    sync::atomic::{AtomicBool, Ordering},
21};
22use thiserror::Error;
23
24#[cfg(feature = "seccomp")]
25use seccomp::filter::Filter;
26
27#[cfg(feature = "fd")]
28use {
29    nix::fcntl::{FcntlArg, FdFlag, fcntl},
30    std::os::fd::AsRawFd,
31};
32
33#[cfg(feature = "cache")]
34use std::{fs, path::Path};
35
36/// Errors related to the Spawner.
37#[derive(Debug, Error)]
38pub enum Error {
39    /// Errors when passed arguments contain Null values.
40    #[error("Invalid string: {0}")]
41    Null(#[from] NulError),
42
43    /// Errors when the cache is improperly accessed.
44    #[cfg(feature = "cache")]
45    #[error("Cache error: {0}")]
46    Cache(&'static str),
47
48    /// Errors reading/writing to the cache.
49    #[error("I/O error: {0}")]
50    Io(#[from] std::io::Error),
51
52    /// Errors to various functions that return `Errno`.
53    #[error("Spawn error within {0:?} failed to {1}: {2}")]
54    Errno(Option<ForkResult>, &'static str, nix::errno::Errno),
55
56    /// Errors resolving binary paths.
57    #[error("Failed to resolve binary: {0}")]
58    Path(#[from] which::Error),
59
60    /// An error when trying to fork.
61    #[error("Fork error: {0}")]
62    Fork(nix::errno::Errno),
63
64    /// An error when the spawner fails to parse the environment.
65    #[error("Failed to parse environment")]
66    Environment,
67
68    #[cfg(feature = "seccomp")]
69    /// An error trying to apply the *SECCOMP* Filter.
70    #[error("SECCOMP error: {0}")]
71    Seccomp(#[from] seccomp::filter::Error),
72}
73
74/// How to handle the standard input/out/error streams
75#[derive(Default)]
76pub enum StreamMode {
77    /// Collect the stream contents in a Stream object via a
78    /// pipe that can be retrieved in the `spawn::Handle`
79    Pipe,
80
81    /// Share STDIN/STDOUT/STDERR with the process, such that it can write
82    /// to the parent. This is the default.
83    #[default]
84    Share,
85
86    /// Send the output to the system logger at the provided level. If the log
87    /// level is below this, output is discarded.
88    Log(log::Level),
89
90    /// Send output to /dev/null.
91    Discard,
92
93    #[cfg(feature = "fd")]
94    /// Send the output to the provided File Descriptor.
95    Fd(OwnedFd),
96}
97
98/// Spawn a child.
99///
100/// ## Thread Safety
101///
102/// This entire object is safe to pass and construct across multiple threads.
103///
104/// ## Examples
105/// Launch bash in a child, inheriting the parent's input/output/error:
106/// ```rust
107/// spawn::Spawner::new("bash").unwrap().spawn().unwrap();
108/// ```
109///
110/// Launch cat, feeding it input from the parent:
111/// ```rust
112/// use std::io::Write;
113/// let mut handle = spawn::Spawner::new("cat").unwrap()
114///     .input(spawn::StreamMode::Pipe)
115///     .output(spawn::StreamMode::Pipe)
116///     .spawn()
117///     .unwrap();
118/// let string = "Hello, World!";
119/// write!(handle, "{}", &string);
120/// handle.close();
121/// let output = handle.output().unwrap().read_all().unwrap();
122/// assert!(output == string);
123/// ```
124pub struct Spawner {
125    /// The binary to run
126    cmd: String,
127
128    /// A unique name for the process, to be used to reference it by the Handle.
129    unique_name: Mutex<Option<String>>,
130
131    /// Arguments
132    args: Mutex<Vec<CString>>,
133
134    /// Whether to pipe **STDIN**. This lets you call `Handle::write()` to
135    /// the process handle to send any Display value to the child.
136    input: Mutex<StreamMode>,
137
138    /// Capture the child's **STDOUT**.
139    output: Mutex<StreamMode>,
140
141    /// Capture the child's **STDERR**.
142    error: Mutex<StreamMode>,
143
144    /// Clear the environment before spawning the child.
145    preserve_env: AtomicBool,
146
147    /// Don't clear privileges.
148    no_new_privileges: AtomicBool,
149
150    /// Whitelisted capabilities.
151    whitelist: DashSet<Capability>,
152
153    /// Environment variables
154    env: DashMap<CString, CString>,
155
156    /// A list of other Pids that the eventual Handle should be responsible for,
157    /// attached to the main child.
158    associated: DashMap<String, Handle>,
159
160    /// An index to cache parts of the command line
161    #[cfg(feature = "cache")]
162    cache_index: Mutex<Option<usize>>,
163
164    /// FD's to pass to the program. These do not include 0,1,2 who's
165    /// logic is controlled via input/capture respectively.
166    #[cfg(feature = "fd")]
167    fds: Mutex<Vec<OwnedFd>>,
168
169    /// The User to run the program under.
170    #[cfg(feature = "user")]
171    mode: Mutex<Option<user::Mode>>,
172
173    /// Use `pkexec` to elevate via *Polkit*.
174    #[cfg(feature = "elevate")]
175    elevate: AtomicBool,
176
177    /// An optional *SECCOMP* policy to load on the child.
178    #[cfg(feature = "seccomp")]
179    seccomp: Mutex<Option<Filter>>,
180}
181impl<'a> Spawner {
182    /// Construct a `Spawner` to spawn *cmd*.
183    /// *cmd* will be resolved from **PATH**.
184    pub fn new(cmd: impl Into<String>) -> Result<Self, Error> {
185        let cmd = cmd.into();
186        let path = which::which(&cmd)?;
187        Ok(Self::abs(path))
188    }
189
190    /// Construct a `Spanwner` to spawn *cmd*.
191    /// This function treats *cmd* as an absolute
192    /// path. No resolution is performed.
193    pub fn abs(cmd: impl Into<String>) -> Self {
194        Self {
195            cmd: cmd.into(),
196            unique_name: Mutex::new(None),
197            args: Mutex::default(),
198
199            input: Mutex::new(StreamMode::Share),
200            output: Mutex::new(StreamMode::Share),
201            error: Mutex::new(StreamMode::Share),
202
203            preserve_env: AtomicBool::new(false),
204            no_new_privileges: AtomicBool::new(true),
205            whitelist: DashSet::new(),
206            env: DashMap::new(),
207
208            associated: DashMap::new(),
209
210            #[cfg(feature = "cache")]
211            cache_index: Mutex::new(None),
212
213            #[cfg(feature = "fd")]
214            fds: Mutex::default(),
215
216            #[cfg(feature = "user")]
217            mode: Mutex::default(),
218
219            #[cfg(feature = "elevate")]
220            elevate: AtomicBool::new(false),
221
222            #[cfg(feature = "seccomp")]
223            seccomp: Mutex::new(None),
224        }
225    }
226
227    /// Control whether to hook the child's standard input.
228    pub fn input(self, input: StreamMode) -> Self {
229        self.input_i(input);
230        self
231    }
232
233    /// Control whether to hook the child's standard output.
234    pub fn output(self, output: StreamMode) -> Self {
235        self.output_i(output);
236        self
237    }
238
239    /// Control whether to hook the child's standard error.
240    pub fn error(self, error: StreamMode) -> Self {
241        self.error_i(error);
242        self
243    }
244
245    /// Give a unique name to the process, so you can refer to the Handle.
246    /// If no name is set, the string passed to Spawn::new() will be used
247    pub fn name(self, name: &str) -> Self {
248        *self.unique_name.lock() = Some(name.to_string());
249        self
250    }
251
252    /// Attach another process that is attached to the main child, and should be killed
253    /// when the eventual Handle goes out of scope.
254    pub fn associate(&self, process: Handle) {
255        self.associated.insert(process.name().to_string(), process);
256    }
257
258    /// Returns a mutable reference to an associate within the Handle, if it exists.
259    /// The associate is another Handle instance.
260    pub fn get_associate<'b>(&'b self, name: &str) -> Option<RefMut<'b, String, Handle>> {
261        self.associated.get_mut(name)
262    }
263
264    /// Drop privilege to the provided user mode on the child,
265    /// immediately after the fork. This does not affected the parent
266    /// process, but prevents the child from changing outside
267    /// of the assigned UID.
268    ///
269    /// If is set to *Original*, the child is launched with the exact
270    /// same operating set as the parent, persisting SetUID privilege.
271    ///
272    /// If mode is not set, or set to *Existing*, it adopts whatever operating
273    /// set the parent is in when spawn() is called. This is ill-advised.
274    ///
275    /// If the parent is not SetUID, this parameter is a no-op
276    #[cfg(feature = "user")]
277    pub fn mode(self, mode: user::Mode) -> Self {
278        self.mode_i(mode);
279        self
280    }
281
282    /// Elevate the child to root privilege by using *PolKit* for authentication.
283    /// `pkexec` must exist, and must be in path.
284    /// The operating set of the child must ensure the real user can
285    /// authorize via *PolKit*.
286    #[cfg(feature = "elevate")]
287    pub fn elevate(self, elevate: bool) -> Self {
288        self.elevate_i(elevate);
289        self
290    }
291
292    /// Preserve the environment of the parent when launching the child.
293    /// `Spawner` defaults to clearing the environment.
294    pub fn preserve_env(self, preserve: bool) -> Self {
295        self.preserve_env_i(preserve);
296        self
297    }
298
299    /// Add a capability to the child's capability set.
300    /// Note that this function cannot grant capability the program
301    /// does not possess, it merely prevents existing capabilities from
302    /// being cleared.
303    pub fn cap(self, cap: Capability) -> Self {
304        self.whitelist.insert(cap);
305        self
306    }
307
308    /// Add capabilities to the child's capability set.
309    /// Note that this function cannot grant capability the program
310    /// does not possess, it merely prevents existing capabilities from
311    /// being cleared.
312    pub fn caps(self, caps: impl IntoIterator<Item = Capability>) -> Self {
313        caps.into_iter().for_each(|cap| {
314            self.whitelist.insert(cap);
315        });
316        self
317    }
318
319    /// Control whether the child is allowed new privileges.
320    /// Note that this function cannot grant privilege the program
321    /// does not already have, but merely allows it access to existing privileges
322    /// not shared by the parent.
323    pub fn new_privileges(self, allow: bool) -> Self {
324        self.new_privileges_i(allow);
325        self
326    }
327
328    /// Sets an environment variable to pass to the process.
329    /// Note that if preserve_env is set to true, this value will
330    /// overwrite the existing value, if it exists.
331    pub fn env(
332        self,
333        key: impl Into<Cow<'a, str>>,
334        var: impl Into<Cow<'a, str>>,
335    ) -> Result<Self, Error> {
336        self.env_i(key, var)?;
337        Ok(self)
338    }
339
340    /// Passes the value of the provided environment variable to the child.
341    /// If preserve_env is true, this is functionally a no-op.
342    pub fn pass_env(self, key: impl Into<Cow<'a, str>>) -> Result<Self, Error> {
343        self.pass_env_i(key)?;
344        Ok(self)
345    }
346
347    #[cfg(feature = "seccomp")]
348    /// Move a *SECCOMP* filter to the `Spawner`, loading in the child after forking.
349    /// *SECCOMP* is the last operation applied. This has several consequences:
350    ///
351    /// 1.  The child will be running under the assigned operating set mode,
352    ///     and said operating set must have permission to load the filter.
353    ///  2.  If using Notify, the path to the monitor socket must
354    ///      be accessible by the operating set mode.
355    ///  3.  Your *SECCOMP* filter must permit `execve` to launch the application.
356    ///      This does not have to be ALLOW. See the caveats to Notify if
357    ///      you are using it.
358    pub fn seccomp(self, seccomp: Filter) -> Self {
359        self.seccomp_i(seccomp);
360        self
361    }
362
363    /// Move a new argument to the argument vector.
364    /// This function is guaranteed to append to the end of the current argument
365    /// vector.
366    pub fn arg(self, arg: impl Into<Cow<'a, str>>) -> Result<Self, Error> {
367        self.arg_i(arg)?;
368        Ok(self)
369    }
370
371    /// Move a new FD to the `Spawner`.
372    /// FD's will be shared to the child under the same value.
373    /// Any FD's in the parent not explicitly passed will be dropped.
374    #[cfg(feature = "fd")]
375    pub fn fd(self, fd: impl Into<OwnedFd>) -> Self {
376        self.fd_i(fd);
377        self
378    }
379
380    /// Move a FD to the `Spawner`, and attach it to an argument to ensure the
381    /// value is identical.
382    ///
383    /// ## Example
384    /// Bubblewrap supports the --file flag, which accepts a FD and destination.
385    /// If you want to ensure you don't accidentally mismatch FDs, you can
386    /// commit both the FD and argument in the same transaction:
387    ///
388    /// ```rust
389    /// let file = std::fs::File::create("file.txt").unwrap();
390    /// spawn::Spawner::new("bwrap").unwrap()
391    ///     .fd_arg("--file", file).unwrap()
392    ///     .arg("/file.txt").unwrap()
393    ///     .spawn().unwrap();
394    /// std::fs::remove_file("file.txt").unwrap();
395    /// ```
396    #[cfg(feature = "fd")]
397    pub fn fd_arg(
398        self,
399        arg: impl Into<Cow<'a, str>>,
400        fd: impl Into<OwnedFd>,
401    ) -> Result<Self, Error> {
402        self.fd_arg_i(arg, fd)?;
403        Ok(self)
404    }
405
406    /// Move an iterator of arguments into the `Spawner`.
407    /// It is guaranteed that the arguments
408    /// in the iterator will appear sequentially, and in the same order.
409    pub fn args<I, S>(self, args: I) -> Result<Self, Error>
410    where
411        I: IntoIterator<Item = S>,
412        S: Into<Cow<'a, str>>,
413    {
414        self.args_i(args)?;
415        Ok(self)
416    }
417
418    /// Move an iterator of FD's to the `Spawner`.
419    #[cfg(feature = "fd")]
420    pub fn fds<I, S>(self, fds: I) -> Self
421    where
422        I: IntoIterator<Item = S>,
423        S: Into<OwnedFd>,
424    {
425        self.fds_i(fds);
426        self
427    }
428
429    /// Set the input flag without consuming the `Spawner`.
430    pub fn input_i(&self, input: StreamMode) {
431        *self.input.lock() = input;
432    }
433
434    /// Set the output flag without consuming the `Spawner`.
435    pub fn output_i(&self, output: StreamMode) {
436        *self.output.lock() = output;
437    }
438
439    /// Set the error flag without consuming the `Spawner`.
440    pub fn error_i(&self, error: StreamMode) {
441        *self.error.lock() = error
442    }
443
444    #[cfg(feature = "elevate")]
445    /// Set the elevate flag without consuming the `Spawner`.
446    pub fn elevate_i(&self, elevate: bool) {
447        self.elevate.store(elevate, Ordering::Relaxed)
448    }
449
450    /// Set the preserve environment flag without consuming the `Spawner`.
451    pub fn preserve_env_i(&self, preserve: bool) {
452        self.preserve_env.store(preserve, Ordering::Relaxed);
453    }
454
455    /// Add a capability without consuming the `Spawner`.
456    pub fn cap_i(&mut self, cap: Capability) {
457        self.whitelist.insert(cap);
458    }
459
460    /// Adds a capability set without consuming the `Spawner`.
461    pub fn caps_i(&mut self, caps: impl IntoIterator<Item = Capability>) {
462        caps.into_iter().for_each(|cap| {
463            self.whitelist.insert(cap);
464        });
465    }
466
467    /// Set the NO_NEW_PRIVS flag without consuming the `Spawner`.
468    pub fn new_privileges_i(&self, allow: bool) {
469        self.no_new_privileges.store(!allow, Ordering::Relaxed)
470    }
471
472    /// Sets an environment variable to the child process without consuming the `Spawner`.
473    pub fn env_i(
474        &self,
475        key: impl Into<Cow<'a, str>>,
476        value: impl Into<Cow<'a, str>>,
477    ) -> Result<(), Error> {
478        self.env.insert(
479            CString::from_str(&key.into())?,
480            CString::from_str(&value.into())?,
481        );
482        Ok(())
483    }
484
485    /// Pass an environment variable to the child process without consuming the `Spawner`.
486    pub fn pass_env_i(&self, key: impl Into<Cow<'a, str>>) -> Result<(), Error> {
487        let key = key.into();
488        let os_key = OsString::from_str(&key).map_err(|_| Error::Environment)?;
489        if let Ok(env) = std::env::var(&os_key) {
490            self.env
491                .insert(CString::from_str(&key)?, CString::from_str(&env)?);
492            Ok(())
493        } else {
494            Err(Error::Environment)
495        }
496    }
497
498    /// Set the user mode without consuming the `Spawner`.
499    #[cfg(feature = "user")]
500    pub fn mode_i(&self, mode: user::Mode) {
501        *self.mode.lock() = Some(mode);
502    }
503
504    /// Set a *SECCOMP* filter without consuming the `Spawner`.
505    #[cfg(feature = "seccomp")]
506    pub fn seccomp_i(&self, seccomp: Filter) {
507        *self.seccomp.lock() = Some(seccomp)
508    }
509
510    /// Move an argument to the `Spawner` in-place.
511    pub fn arg_i(&self, arg: impl Into<Cow<'a, str>>) -> Result<(), Error> {
512        self.args.lock().push(CString::new(arg.into().as_ref())?);
513        Ok(())
514    }
515
516    /// Move a FD to the `Spawner` in-place.
517    #[cfg(feature = "fd")]
518    pub fn fd_i(&self, fd: impl Into<OwnedFd>) {
519        self.fds.lock().push(fd.into());
520    }
521
522    /// Move FDs to the `Spawner` in-place, passing it as an argument.
523    #[cfg(feature = "fd")]
524    pub fn fd_arg_i(
525        &self,
526        arg: impl Into<Cow<'a, str>>,
527        fd: impl Into<OwnedFd>,
528    ) -> Result<(), Error> {
529        let fd = fd.into();
530        self.args_i([arg.into(), Cow::Owned(format!("{}", fd.as_raw_fd()))])?;
531        self.fd_i(fd);
532        Ok(())
533    }
534
535    /// Move an iterator of FDs to the `Spawner` in-place.
536    #[cfg(feature = "fd")]
537    pub fn fds_i<I, S>(&self, fds: I)
538    where
539        I: IntoIterator<Item = S>,
540        S: Into<OwnedFd>,
541    {
542        self.fds.lock().extend(fds.into_iter().map(Into::into));
543    }
544
545    /// Move an iterator of arguments to the `Spawner` in-place.
546    /// Both sequence and order are guaranteed.
547    pub fn args_i<I, S>(&self, args: I) -> Result<(), Error>
548    where
549        I: IntoIterator<Item = S>,
550        S: Into<Cow<'a, str>>,
551    {
552        let mut a = self.args.lock();
553
554        for s in args {
555            let cow: Cow<'a, str> = s.into();
556            a.push(CString::new(cow.as_ref())?);
557        }
558        Ok(())
559    }
560
561    /// Set the cache index.
562    /// Once the cache flag has been set, all subsequent arguments will
563    /// be cached to the file provided to cache_write.
564    /// On future runs, `cache_read` can be used to append those cached
565    /// contents to the `Spawner`'s arguments.
566    /// This function fails if cache_start is called twice without having
567    /// first called cache_write.
568    ///
569    /// ## Examples
570    ///
571    /// ```rust,ignore
572    /// let cache = std::path::PathBuf::from("cmd.cache");
573    /// let mut handle = spawn::Spawner::abs("/usr/bin/bash");
574    /// if cache.exists() {
575    ///     handle.cache_read(&cache).unwrap();
576    /// } else {
577    ///     handle.cache_start().unwrap();
578    ///     handle.arg_i("arg").unwrap();
579    ///     handle.cache_write(&cache).unwrap();
580    /// }
581    /// std::fs::remove_file(cache);
582    /// ```
583    ///
584    /// ## Caveat
585    ///
586    /// Because the cache is written to disk, ephemeral values, such
587    /// as FD values, temporary files, etc, must not be passed to the
588    /// Spawner, otherwise those values would be cached, and likely
589    /// be invalid when trying to use the cached results.
590    #[cfg(feature = "cache")]
591    pub fn cache_start(&self) -> Result<(), Error> {
592        let mut index = self.cache_index.lock();
593        if index.is_some() {
594            Err(Error::Cache("Caching already started!"))
595        } else {
596            *index = Some(self.args.lock().len());
597            Ok(())
598        }
599    }
600
601    /// Write all arguments added to the `Spawner` since `cache_start`
602    /// was called to the file provided.
603    /// This function will fail if `cache_start` was not called,
604    /// or if there are errors writing to the provided path.
605    #[cfg(feature = "cache")]
606    pub fn cache_write(&self, path: &Path) -> Result<(), Error> {
607        use std::io::Write;
608        let mut index = self.cache_index.lock();
609        if let Some(i) = *index {
610            let args = self.args.lock();
611            if let Some(parent) = path.parent()
612                && !parent.exists()
613            {
614                fs::create_dir(parent)?;
615            }
616            let mut file = fs::File::create(path)?;
617            let bytes = postcard::to_stdvec(&args[i..])
618                .map_err(|_| Error::Cache("Failed to serialize cache"))?;
619            file.write_all(&bytes)?;
620            *index = None;
621            Ok(())
622        } else {
623            Err(Error::Cache("Cache not started!"))
624        }
625    }
626
627    /// Read from the cache file, adding its contents to the `Spawner`'s
628    /// arguments.
629    /// This function will fail if there is an error reading the file,
630    /// or if the contents contain strings will NULL bytes.
631    #[cfg(feature = "cache")]
632    pub fn cache_read(&self, path: &Path) -> Result<(), Error> {
633        let mut args = self.args.lock();
634
635        let mut cached: Vec<CString> = postcard::from_bytes(&fs::read(path)?)
636            .map_err(|_| Error::Cache("Failed to decode cache"))?;
637        args.append(&mut cached);
638        Ok(())
639    }
640
641    /// Spawn the child process.
642    /// This consumes the structure, returning a `spawn::Handle`.
643    ///
644    /// ## Errors
645    /// This function can fail for many reasons:
646    ///
647    /// ### Parent Errors (Which will return Err)
648    /// * The `fork` fails.
649    /// * The Parent fails to setup/close/duplicate input/output/error pipes.
650    ///
651    /// ### Child Errors (Which will cause errors when using the `Handle`)
652    /// * The child fails to close/duplicate input/output/error pipes.
653    /// * The application to run cannot be resolved in **PATH**.
654    /// * Elevate is enabled, but `pkexec` cannot be found in **PATH**.
655    /// * The resolved application (Or `pkexec` if *elevate*) has a path containing a NULL byte.
656    /// * `F_SETFD` cannot be cleared on owned FDs.
657    /// * **SIGTERM** cannot be set as the Child's Death Sig.
658    /// * A user mode has been set, but dropping to it fails.
659    /// * A *SECCOMP* filter is set, but it fails to set.
660    /// * `execve` Fails.
661    pub fn spawn(mut self) -> Result<Handle, Error> {
662        // Create our pipes based on whether we need t
663        // hem.
664        // Because we use these conditionals later on when using them,
665        // we can unwrap() with impunity.
666
667        let stdout_mode = self.output.into_inner();
668        let stderr_mode = self.error.into_inner();
669        let stdin_mode = self.input.into_inner();
670
671        let stdout = cond_pipe(&stdout_mode)?;
672        let stderr = cond_pipe(&stderr_mode)?;
673        let stdin = cond_pipe(&stdin_mode)?;
674
675        #[cfg(feature = "fd")]
676        let fds = self.fds.into_inner();
677
678        let mut cmd_c: Option<CString> = None;
679        let mut args_c = Vec::<CString>::new();
680
681        // Launch with pkexec if we're elevated.
682        #[cfg(feature = "elevate")]
683        if self.elevate.load(Ordering::Relaxed) {
684            let polkit = CString::new("/usr/bin/pkexec".to_string())?;
685            if cmd_c.is_none() {
686                cmd_c = Some(polkit.clone());
687            }
688            args_c.push(polkit);
689        }
690
691        let resolved = CString::new(self.cmd.clone())?;
692        let cmd_c = if let Some(cmd) = cmd_c {
693            cmd
694        } else {
695            resolved.clone()
696        };
697
698        args_c.push(resolved);
699        args_c.append(&mut self.args.into_inner());
700
701        // Clear F_SETFD to allow passed FD's to persist after execve
702        #[cfg(feature = "fd")]
703        for fd in &fds {
704            fcntl(fd, FcntlArg::F_SETFD(FdFlag::empty()))
705                .map_err(|e| Error::Errno(None, "fnctl fd", e))?;
706        }
707
708        if self.preserve_env.load(Ordering::Relaxed) {
709            let environment: HashMap<_, _> = std::env::vars_os()
710                .filter_map(|(key, value)| {
711                    if let Some(key) = key.to_str()
712                        && let Some(value) = value.to_str()
713                        && let Ok(key) = CString::from_str(key)
714                        && let Ok(value) = CString::from_str(value)
715                    {
716                        if !self.env.contains_key(&key) {
717                            Some((key, value))
718                        } else {
719                            None
720                        }
721                    } else {
722                        None
723                    }
724                })
725                .collect();
726
727            self.env.extend(environment)
728        }
729
730        let envs: Vec<_> = self
731            .env
732            .into_iter()
733            .filter_map(|(k, v)| {
734                // We already know both are valid strings, because we converted them from Str to begin with.
735                CString::from_str(&format!("{}={}", k.to_str().unwrap(), v.to_str().unwrap())).ok()
736            })
737            .collect();
738
739        // Log if desired.
740        if log::log_enabled!(log::Level::Trace) {
741            let formatted = format_iter(args_c.iter().map(|e| e.to_string_lossy()));
742            if self.preserve_env.load(Ordering::Relaxed) {
743                trace!("[SYSTEM ENVIRONMENT] {formatted}",);
744            } else if !envs.is_empty() {
745                let env_formatted = format_iter(envs.iter().map(|e| e.to_string_lossy()));
746                trace!("{env_formatted} {formatted}",);
747            } else {
748                trace!("{formatted}");
749            }
750        }
751
752        let all = caps::all();
753        let set: HashSet<Capability> = self.whitelist.into_iter().collect();
754        let diff: CapsHashSet = all.difference(&set).copied().collect();
755
756        #[cfg(feature = "seccomp")]
757        let filter = {
758            let mut filter = self.seccomp.into_inner();
759            if let Some(filter) = &mut filter {
760                filter.setup()?;
761            }
762            filter
763        };
764
765        let fork = unsafe { fork() }.map_err(Error::Fork)?;
766        match fork {
767            ForkResult::Parent { child } => {
768                let name = if let Some(name) = self.unique_name.into_inner() {
769                    name
770                } else {
771                    self.cmd
772                };
773
774                // Set the relevant pipes.
775                let stdin = if let Some((read, write)) = stdin {
776                    close(read).map_err(|e| Error::Errno(Some(fork), "close input", e))?;
777                    Some(write)
778                } else {
779                    None
780                };
781
782                let stdout = if let Some((read, write)) = stdout {
783                    close(write).map_err(|e| Error::Errno(Some(fork), "close error", e))?;
784                    if let StreamMode::Log(log) = stdout_mode {
785                        let name = name.clone();
786                        std::thread::spawn(move || logger(log, read, name));
787                        None
788                    } else {
789                        Some(read)
790                    }
791                } else {
792                    None
793                };
794
795                let stderr = if let Some((read, write)) = stderr {
796                    close(write).map_err(|e| Error::Errno(Some(fork), "close output", e))?;
797                    if let StreamMode::Log(log) = stderr_mode {
798                        let name = name.clone();
799                        std::thread::spawn(move || logger(log, read, name));
800                        None
801                    } else {
802                        Some(read)
803                    }
804                } else {
805                    None
806                };
807
808                #[cfg(feature = "user")]
809                let mode = self.mode.into_inner().unwrap_or(
810                    user::current().map_err(|e| Error::Errno(Some(fork), "getresuid", e))?,
811                );
812
813                let associated: Vec<Handle> = self.associated.into_iter().map(|(_, v)| v).collect();
814
815                // Return.
816                let handle = Handle::new(
817                    name,
818                    child,
819                    #[cfg(feature = "user")]
820                    mode,
821                    stdin,
822                    stdout,
823                    stderr,
824                    associated,
825                );
826                Ok(handle)
827            }
828
829            ForkResult::Child => {
830                if let Some((read, write)) = stdin {
831                    let _ = close(write);
832                    let _ = dup2_stdin(read);
833                } else if let StreamMode::Discard = stdin_mode {
834                    let _ = dup2_stdin(dup_null().unwrap());
835                }
836                #[cfg(feature = "fd")]
837                if let StreamMode::Fd(fd) = stdin_mode {
838                    let _ = dup2_stdin(fd);
839                }
840
841                if let Some((read, write)) = stdout {
842                    let _ = close(read);
843                    let _ = dup2_stdout(write);
844                } else if let StreamMode::Discard = stdout_mode {
845                    let _ = dup2_stdout(dup_null().unwrap());
846                }
847                #[cfg(feature = "fd")]
848                if let StreamMode::Fd(fd) = stdout_mode {
849                    let _ = dup2_stdout(fd);
850                }
851
852                if let Some((read, write)) = stderr {
853                    let _ = close(read);
854                    let _ = dup2_stderr(write);
855                } else if let StreamMode::Discard = stderr_mode {
856                    let _ = dup2_stderr(dup_null().unwrap());
857                }
858                #[cfg(feature = "fd")]
859                if let StreamMode::Fd(fd) = stderr_mode {
860                    let _ = dup2_stderr(fd);
861                }
862
863                let _ = prctl::set_pdeathsig(SIGTERM);
864
865                // Drop modes
866                #[cfg(feature = "user")]
867                if let Some(mode) = self.mode.into_inner()
868                    && let Err(e) = user::drop(mode)
869                {
870                    warn!("Failed to drop user: {e}")
871                }
872
873                clear_capabilities(diff);
874
875                if self.no_new_privileges.load(Ordering::Relaxed)
876                    && let Err(e) = prctl::set_no_new_privs()
877                {
878                    warn!("Could not set NO_NEW_PRIVS: {e}");
879                }
880
881                // Apply SECCOMP.
882                // Because we can't just trust the application is able/willing to
883                // apply a SECCOMP filter on it's own, we have to do it before the execve
884                // call. That means the SECCOMP filter needs to either Allow, Log, Notify,
885                // or some other mechanism to let the process to spawn.
886                #[cfg(feature = "seccomp")]
887                if let Some(filter) = filter {
888                    filter.load();
889                }
890
891                // Execve
892                let _ = execve(&cmd_c, &args_c, &envs);
893                exit(-1);
894            }
895        }
896    }
897}
898
899#[cfg(test)]
900mod tests {
901    use anyhow::Result;
902    use std::io::Write;
903
904    use super::*;
905
906    #[test]
907    fn bash() -> Result<()> {
908        let string = "Hello, World!";
909        let mut handle = Spawner::new("bash")?
910            .args(["-c", &format!("echo '{string}'")])?
911            .output(StreamMode::Pipe)
912            .error(StreamMode::Pipe)
913            .spawn()?;
914
915        let output = handle.output()?.read_all()?;
916        assert!(output.trim() == string);
917        Ok(())
918    }
919
920    #[test]
921    fn cat() -> Result<()> {
922        let mut handle = Spawner::new("cat")?
923            .input(StreamMode::Pipe)
924            .output(StreamMode::Pipe)
925            .spawn()?;
926
927        let string = "Hello, World!";
928        write!(handle, "{string}")?;
929        handle.close()?;
930
931        let output = handle.output()?.read_all()?;
932        assert!(output.trim() == string);
933        Ok(())
934    }
935
936    #[test]
937    fn read() -> Result<()> {
938        let string = "Hello!";
939        let mut handle = Spawner::new("echo")?
940            .arg(string)?
941            .output(StreamMode::Pipe)
942            .spawn()?;
943
944        let bytes = handle.output()?.read_bytes(Some(string.len()))?;
945        let output = String::from_utf8_lossy(&bytes);
946        assert!(output.trim() == string);
947        Ok(())
948    }
949
950    #[test]
951    fn clear_env() -> Result<()> {
952        let mut handle = Spawner::new("bash")?
953            .args(["-c", "echo $USER"])?
954            .output(StreamMode::Pipe)
955            .error(StreamMode::Pipe)
956            .spawn()?;
957
958        let output = handle.output()?.read_all()?;
959        assert!(output.trim().is_empty());
960        Ok(())
961    }
962
963    #[test]
964    fn preserve_env() -> Result<()> {
965        let user = "Test";
966        let mut handle = Spawner::new("bash")?
967            .args(["-c", "echo $USER"])?
968            .env("USER", user)?
969            .output(StreamMode::Pipe)
970            .error(StreamMode::Pipe)
971            .spawn()?;
972
973        let output = handle.output()?.read_all()?;
974        assert!(output.trim() == user);
975        Ok(())
976    }
977}