memfd_exec/executable.rs
1//! This essentially reimplements the code at:
2//! which is an internal implementation of the code at:
3//! <https://github.com/rust-lang/rust/blob/master/library/std/src/process.rs>
4//! <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/process/process_unix.rs>
5//! <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/process/process_common.rs>
6//! for external use to provide a very similar interface to process::Command for in-memory executables
7
8use std::{
9 collections::BTreeMap,
10 ffi::{CStr, CString, OsStr, OsString},
11 io::{Error, ErrorKind, Result},
12 mem::MaybeUninit,
13 os::unix::prelude::{OsStrExt, OsStringExt},
14 path::Path,
15 ptr::null_mut,
16};
17
18use libc::{pid_t, sigemptyset, signal};
19use nix::{
20 sys::memfd::{memfd_create, MemFdCreateFlag},
21 unistd::{close, fexecve, write},
22};
23
24use crate::{
25 anon_pipe::anon_pipe,
26 child::Child,
27 command_env::CommandEnv,
28 cvt::{cvt, cvt_nz, cvt_r},
29 output::Output,
30 process::{ExitStatus, Process},
31 stdio::{ChildPipes, Stdio, StdioPipes},
32};
33
34/// This is the main struct used to create an in-memory only executable. Wherever possible, it
35/// is intended to be a drop-in replacement for the standard library's `process::Command` struct.
36///
37/// # Examples
38///
39/// This example is the "motivating case" for this library. It shows how to execute a binary
40/// entirely from memory, without writing it to disk. This is useful for executing binaries
41/// sneakily, or (the real reason) for bundling executables that are a pain to set up and
42/// compile, but whose static versions are very portable. Here's a "sneaky" example:
43///
44/// ```no_compile
45/// use memfd_exec::{MemFdExecutable, Stdio};
46///
47/// // You can include the entirety of a binary (for example, nc)
48/// let nc_binary= include_bytes!("/usr/bin/nc-static");
49///
50///
51/// // The first argument is just the name for the program, you can pick anything but
52/// // if the program expects a specific argv[0] value, use that.
53/// // The second argument is the binary code to execute.
54/// let mut cmd = MemFdExecutable::new("nc", nc_binary)
55/// // We can pass arbitrary args just like with Command. Here, we'll execute nc
56/// // to listen on a port and run a shell for connections, entirely from memory.
57/// .arg("-l")
58/// .arg("1234")
59/// .arg("-e")
60/// .arg("/bin/sh")
61/// // And we can get piped stdin/stdout just like with Command
62/// .stdout(Stdio::piped())
63/// // Spawn starts the child process and gives us a handle back
64/// .spawn()
65/// .expect("failed to execute process");
66///
67/// // Then, we can wait for the program to exit.
68/// cmd.wait();
69/// ```
70#[derive(Debug)]
71pub struct MemFdExecutable<'a> {
72 /// The contents of the ELF executable to run. This content can be included in the file
73 /// using the `include_bytes!()` macro, or you can do fancy things like read it in from
74 /// a socket.
75 code: &'a [u8],
76 /// The name of the program, this value is the argv\[0\] argument to the binary when
77 /// executed. If the program expects something specific here, that value should be
78 /// used, otherwise any name will do
79 program: CString,
80 /// The arguments to the program, excluding the program name
81 args: Vec<CString>,
82 /// The whole argv array, including the program name
83 argv: Argv,
84 /// The environment variables to set for the program
85 env: CommandEnv,
86 /// The current working directory to set for the program
87 cwd: Option<CString>,
88 /// The program's stdin handle
89 pub stdin: Option<Stdio>,
90 /// The program's stdout handle
91 pub stdout: Option<Stdio>,
92 /// The program's stderr handle
93 pub stderr: Option<Stdio>,
94 /// Holdover from Command, whether there was a NUL in the arguments or not
95 saw_nul: bool,
96}
97
98#[derive(Debug)]
99struct Argv(Vec<CString>);
100
101unsafe impl Send for Argv {}
102unsafe impl Sync for Argv {}
103
104fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
105 CString::new(s.as_bytes()).unwrap_or_else(|_e| {
106 *saw_nul = true;
107 CString::new("<string-with-nul>").unwrap()
108 })
109}
110
111fn construct_envp(env: BTreeMap<OsString, OsString>, saw_nul: &mut bool) -> Vec<CString> {
112 let mut result = Vec::with_capacity(env.len());
113 for (mut k, v) in env {
114 // Reserve additional space for '=' and null terminator
115 k.reserve_exact(v.len() + 2);
116 k.push("=");
117 k.push(&v);
118
119 // Add the new entry into the array
120 if let Ok(item) = CString::new(k.into_vec()) {
121 result.push(item);
122 } else {
123 *saw_nul = true;
124 }
125 }
126
127 result
128}
129
130impl<'a> MemFdExecutable<'a> {
131 /// Create a new MemFdExecutable with the given name and code. The name is the name of the
132 /// program, and is used as the argv\[0\] argument to the program. The code is the binary
133 /// code to execute (usually, the entire contents of an ELF file).
134 ///
135 /// # Examples
136 ///
137 /// You can run code that is included directly in your executable with `include_bytes!()`:
138 ///
139 /// ```no_compile
140 /// use memfd_exec::MemFdExecutable;
141 ///
142 /// let code = include_bytes!("/usr/bin/nc-static");
143 ///
144 /// let mut cmd = MemFdExecutable::new("nc", code)
145 /// .arg("-l")
146 /// .arg("1234")
147 /// .arg("-e")
148 /// .arg("/bin/sh")
149 /// .status()
150 /// .expect("failed to execute process");
151 /// ```
152 ///
153 pub fn new<S: AsRef<OsStr>>(name: S, code: &'a [u8]) -> Self {
154 let mut saw_nul = false;
155 let name = os2c(name.as_ref(), &mut saw_nul);
156 Self {
157 code,
158 program: name.clone(),
159 args: vec![name.clone()],
160 argv: Argv(vec![name]),
161 env: Default::default(),
162 cwd: None,
163 stdin: None,
164 stdout: None,
165 stderr: None,
166 saw_nul,
167 }
168 }
169
170 /// Add an argument to the program. This is equivalent to `Command::arg()`.
171 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
172 let arg = os2c(arg.as_ref(), &mut self.saw_nul);
173 self.argv.0.push(arg.clone());
174 self.args.push(arg);
175 self
176 }
177
178 /// Add multiple arguments to the program. This is equivalent to `Command::args()`.
179 pub fn args<I, S>(&mut self, args: I) -> &mut Self
180 where
181 I: IntoIterator<Item = S>,
182 S: AsRef<OsStr>,
183 {
184 for arg in args {
185 self.arg(arg.as_ref());
186 }
187 self
188 }
189
190 /// Add an environment variable to the program. This is equivalent to `Command::env()`.
191 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
192 where
193 K: AsRef<OsStr>,
194 V: AsRef<OsStr>,
195 {
196 self.env_mut().set(key.as_ref(), val.as_ref());
197 self
198 }
199
200 /// Add multiple environment variables to the program. This is equivalent to `Command::envs()`.
201 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
202 where
203 I: IntoIterator<Item = (K, V)>,
204 K: AsRef<OsStr>,
205 V: AsRef<OsStr>,
206 {
207 for (ref key, ref val) in vars {
208 self.env_mut().set(key.as_ref(), val.as_ref());
209 }
210 self
211 }
212
213 /// Remove an environment variable from the program. This is equivalent to `Command::env_remove()`.
214 pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
215 self.env_mut().remove(key.as_ref());
216 self
217 }
218
219 /// Clear all environment variables from the program. This is equivalent to `Command::env_clear()`.
220 pub fn env_clear(&mut self) -> &mut Self {
221 self.env_mut().clear();
222 self
223 }
224
225 /// Set the current working directory for the program. This is equivalent to `Command::current_dir()`.
226 pub fn cwd<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
227 self.cwd = Some(os2c(dir.as_ref().as_ref(), &mut self.saw_nul));
228 self
229 }
230
231 /// Set the stdin handle for the program. This is equivalent to `Command::stdin()`. The
232 /// default is to inherit the current process's stdin. Note that this `Stdio` is not the
233 /// same exactly as `process::Stdio`, but it is feature-equivalent.
234 ///
235 /// # Examples
236 ///
237 /// This example creates a `cat` process that will read in the contents passed to its
238 /// stdin handle and write them to a null stdout (i.e. it will be discarded). The same
239 /// methodology can be used to read from stderr/stdout.
240 ///
241 /// ```no_run
242 /// use std::thread::spawn;
243 /// use std::io::Write;
244 ///
245 /// use memfd_exec::{MemFdExecutable, Stdio};
246 ///
247 /// let mut cat_cmd = MemFdExecutable::new("cat", include_bytes!("/bin/cat"))
248 /// .stdin(Stdio::piped())
249 /// .stdout(Stdio::null())
250 /// .spawn()
251 /// .expect("failed to spawn cat");
252 ///
253 /// let mut cat_stdin = cat_cmd.stdin.take().expect("failed to open stdin");
254 /// spawn(move || {
255 /// cat_stdin.write_all(b"hello world").expect("failed to write to stdin");
256 /// });
257 /// ```
258 pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
259 self.stdin = Some(cfg.into());
260 self
261 }
262
263 /// Set the stdout handle for the program. This is equivalent to `Command::stdout()`. The
264 ///
265 /// # Arguments
266 /// * `cfg` - The configuration for the stdout handle. This will usually be one of the following:
267 /// * `Stdio::inherit()` - Inherit the current process's stdout handle
268 /// * `Stdio::piped()` - Create a pipe to the child process's stdout. This can be read
269 /// * `Stdio::null()` - Discard all output to stdout
270 ///
271 /// # Examples
272 ///
273 /// This example creates a `cat` process that will read in the contents passed to its stdin handle
274 /// and read them from its stdout handle. The same methodology can be used to read from stderr/stdout.
275 ///
276 /// ```
277 /// use std::thread::spawn;
278 /// use std::fs::read;
279 /// use std::io::{Read, Write};
280 ///
281 /// use memfd_exec::{MemFdExecutable, Stdio};
282 ///
283 /// let mut cat = MemFdExecutable::new("cat", &read("/bin/cat").unwrap())
284 /// .stdin(Stdio::piped())
285 /// .stdout(Stdio::piped())
286 /// .spawn()
287 /// .expect("failed to spawn cat");
288 ///
289 /// let mut cat_stdin = cat.stdin.take().expect("failed to open stdin");
290 /// let mut cat_stdout = cat.stdout.take().expect("failed to open stdout");
291 ///
292 /// spawn(move || {
293 /// cat_stdin.write_all(b"hello world").expect("failed to write to stdin");
294 /// });
295 ///
296 /// let mut output = Vec::new();
297 /// cat_stdout.read_to_end(&mut output).expect("failed to read from stdout");
298 /// assert_eq!(output, b"hello world");
299 /// cat.wait().expect("failed to wait on cat");
300 /// ```
301 pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
302 self.stdout = Some(cfg.into());
303 self
304 }
305
306 /// Set the stderr handle for the program. This is equivalent to `Command::stderr()`. The
307 ///
308 /// # Arguments
309 /// * `cfg` - The configuration for the stderr handle. This will usually be one of the following:
310 /// * `Stdio::inherit()` - Inherit the current process's stderr handle
311 /// * `Stdio::piped()` - Create a pipe to the child process's stderr. This can be read
312 /// * `Stdio::null()` - Discard all output to stderr
313 pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
314 self.stderr = Some(cfg.into());
315 self
316 }
317
318 /// Spawn the program as a child process. This is equivalent to `Command::spawn()`.
319 pub fn spawn(&mut self) -> Result<Child> {
320 let default = Stdio::Inherit;
321 let needs_stdin = true;
322 const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX";
323
324 let envp = self.capture_env();
325
326 if self.saw_nul() {
327 // TODO: Need err?
328 }
329
330 let (ours, theirs) = self.setup_io(default, needs_stdin)?;
331
332 let (input, output) = anon_pipe()?;
333
334 // Whatever happens after the fork is almost for sure going to touch or
335 // look at the environment in one way or another (PATH in `execvp` or
336 // accessing the `environ` pointer ourselves). Make sure no other thread
337 // is accessing the environment when we do the fork itself.
338 //
339 // Note that as soon as we're done with the fork there's no need to hold
340 // a lock any more because the parent won't do anything and the child is
341 // in its own process. Thus the parent drops the lock guard while the child
342 // forgets it to avoid unlocking it on a new thread, which would be invalid.
343 // TODO: Yeah....I had to remove the env lock. Whoops! Don't multithread env with this
344 // you insane person
345 let pid = unsafe { self.do_fork()? };
346
347 if pid == 0 {
348 drop(input);
349 let Err(err) = (unsafe { self.do_exec(theirs, envp) }) else { unreachable!("..."); };
350 panic!("failed to exec: {}", err);
351 }
352
353 drop(output);
354
355 // Safety: We obtained the pidfd from calling `clone3` with
356 // `CLONE_PIDFD` so it's valid an otherwise unowned.
357 let mut p = unsafe { Process::new(pid) };
358 let mut bytes = [0; 8];
359
360 // loop to handle EINTR
361 loop {
362 match input.read(&mut bytes) {
363 Ok(0) => return Ok(Child::new(p, ours)),
364 Ok(8) => {
365 let (errno, footer) = bytes.split_at(4);
366 assert_eq!(
367 CLOEXEC_MSG_FOOTER, footer,
368 "Validation on the CLOEXEC pipe failed: {:?}",
369 bytes
370 );
371 let errno = i32::from_be_bytes(errno.try_into().unwrap());
372 assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
373 return Err(Error::from_raw_os_error(errno));
374 }
375 Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
376 Err(e) => {
377 assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
378 panic!("the CLOEXEC pipe failed: {e:?}")
379 }
380 Ok(..) => {
381 // pipe I/O up to PIPE_BUF bytes should be atomic
382 assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
383 panic!("short read on the CLOEXEC pipe")
384 }
385 }
386 }
387 }
388
389 /// Spawn the program as a child process and wait for it to complete, obtaining the
390 /// output and exit status. This is equivalent to `Command::output()`.
391 pub fn output(&mut self) -> Result<Output> {
392 self.spawn()?.wait_with_output()
393 }
394
395 /// Spawn the program as a child process and wait for it to complete, obtaining the
396 /// exit status. This is equivalent to `Command::status()`.
397 pub fn status(&mut self) -> Result<ExitStatus> {
398 self.spawn()?.wait()
399 }
400
401 /// Set the program name (argv\[0\]) to a new value.
402 ///
403 /// # Arguments
404 /// * `name` - The new name for the program. This will be used as the first argument
405 pub fn set_program(&mut self, program: &OsStr) {
406 let arg = os2c(program, &mut self.saw_nul);
407 self.argv.0[0] = arg.clone();
408 self.args[0] = arg;
409 }
410
411 fn env_mut(&mut self) -> &mut CommandEnv {
412 &mut self.env
413 }
414
415 fn setup_io(&self, default: Stdio, needs_stdin: bool) -> Result<(StdioPipes, ChildPipes)> {
416 let null = Stdio::Null;
417 let default_stdin = if needs_stdin { &default } else { &null };
418 let stdin = self.stdin.as_ref().unwrap_or(default_stdin);
419 let stdout = self.stdout.as_ref().unwrap_or(&default);
420 let stderr = self.stderr.as_ref().unwrap_or(&default);
421 let (their_stdin, our_stdin) = stdin.to_child_stdio(true)?;
422 let (their_stdout, our_stdout) = stdout.to_child_stdio(false)?;
423 let (their_stderr, our_stderr) = stderr.to_child_stdio(false)?;
424 let ours = StdioPipes {
425 stdin: our_stdin,
426 stdout: our_stdout,
427 stderr: our_stderr,
428 };
429 let theirs = ChildPipes {
430 stdin: their_stdin,
431 stdout: their_stdout,
432 stderr: their_stderr,
433 };
434 Ok((ours, theirs))
435 }
436
437 fn saw_nul(&self) -> bool {
438 self.saw_nul
439 }
440
441 /// Get the current working directory for the child process.
442 pub fn get_cwd(&self) -> &Option<CString> {
443 &self.cwd
444 }
445
446 unsafe fn do_fork(&mut self) -> Result<pid_t> {
447 cvt(libc::fork())
448 }
449
450 fn capture_env(&mut self) -> Option<Vec<CString>> {
451 let maybe_env = self.env.capture_if_changed();
452 maybe_env.map(|env| construct_envp(env, &mut self.saw_nul))
453 }
454
455 /// Execute the command as a new process, replacing the current process.
456 ///
457 /// This function will not return.
458 ///
459 /// # Arguments
460 /// * `default` - The default stdio to use if the child process does not specify.
461 pub fn exec(&mut self, default: Stdio) -> Error {
462 let envp = self.capture_env();
463
464 if self.saw_nul() {
465 return Error::new(ErrorKind::InvalidInput, "nul byte found in provided data");
466 }
467
468 match self.setup_io(default, true) {
469 Ok((_, theirs)) => unsafe {
470 let Err(e) = self.do_exec(theirs, envp) else { unreachable!("..."); };
471 e
472 },
473 Err(e) => e,
474 }
475 }
476
477 /// Get the program name to use for the child process as a C string.
478 pub fn get_program_cstr(&self) -> &CStr {
479 &self.program
480 }
481
482 /// Get the program argv to use for the child process.
483 pub fn get_argv(&self) -> &Vec<CString> {
484 &self.argv.0
485 }
486
487 /// Get whether PATH has been affected by changes to the environment variables
488 /// of this command.
489 pub fn env_saw_path(&self) -> bool {
490 self.env.have_changed_path()
491 }
492
493 /// Get whether the program (argv\[0\]) is a path, as opposed to a name.
494 pub fn program_is_path(&self) -> bool {
495 self.program.to_bytes().contains(&b'/')
496 }
497
498 unsafe fn do_exec(
499 &mut self,
500 stdio: ChildPipes,
501 maybe_envp: Option<Vec<CString>>,
502 ) -> Result<()> {
503 if let Some(fd) = stdio.stdin.fd() {
504 cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?;
505 }
506 if let Some(fd) = stdio.stdout.fd() {
507 cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?;
508 }
509 if let Some(fd) = stdio.stderr.fd() {
510 cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?;
511 }
512
513 if let Some(ref cwd) = *self.get_cwd() {
514 cvt(libc::chdir(cwd.as_ptr()))?;
515 }
516
517 {
518 // Reset signal handling so the child process starts in a
519 // standardized state. libstd ignores SIGPIPE, and signal-handling
520 // libraries often set a mask. Child processes inherit ignored
521 // signals and the signal mask from their parent, but most
522 // UNIX programs do not reset these things on their own, so we
523 // need to clean things up now to avoid confusing the program
524 // we're about to run.
525 let mut set = MaybeUninit::<libc::sigset_t>::uninit();
526 cvt(sigemptyset(set.as_mut_ptr()))?;
527 cvt_nz(libc::pthread_sigmask(
528 libc::SIG_SETMASK,
529 set.as_ptr(),
530 null_mut(),
531 ))?;
532
533 {
534 let ret = signal(libc::SIGPIPE, libc::SIG_DFL);
535 if ret == libc::SIG_ERR {
536 return Err(Error::last_os_error());
537 }
538 }
539 }
540
541 // TODO: Env resetting isn't implemented because we're using fexecve not execvp
542
543 // Map the executable last, because it's a huge hit to memory if something else failed
544 let mfd = memfd_create(
545 CString::new("rust_exec").unwrap().as_c_str(),
546 MemFdCreateFlag::MFD_CLOEXEC,
547 )
548 .unwrap();
549
550 if let Ok(n) = write(mfd, self.code) {
551 if n != self.code.len() {
552 return Err(Error::new(
553 ErrorKind::BrokenPipe,
554 "Failed to write to memfd",
555 ));
556 }
557 } else {
558 return Err(Error::last_os_error());
559 }
560
561 let argv = self
562 .get_argv()
563 .iter()
564 .map(|s| s.as_c_str())
565 .collect::<Vec<_>>();
566
567 let maybe_envp = maybe_envp.unwrap_or_default();
568
569 let envp = maybe_envp.iter().map(|s| s.as_c_str()).collect::<Vec<_>>();
570
571 if let Err(err) = fexecve(mfd, &argv, &envp) {
572 // If we failed to exec, we need to close the memfd
573 // so that the child process doesn't leak it
574 let _ = close(mfd);
575 return Err(Error::new(ErrorKind::BrokenPipe, err));
576 }
577 Err(Error::last_os_error())
578 }
579}