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}