memfd_runner/
lib.rs

1//! # memfd-runner
2//!
3//! A minimal Linux library for executing in-memory ELF files using `memfd_create` and `execve`.
4//!
5//! This library provides a simple interface to load and execute ELF binaries directly from memory
6//! without writing them to disk. It uses Linux's `memfd_create` system call to create an anonymous
7//! file in memory, writes the ELF data to it, then executes it via the `/proc/self/fd/` interface.
8//!
9//! ## Features
10//!
11//! - **Minimal** - <400 lines of code, 1 dependency ([syscaller](https://github.com/mathyslv/syscaller))
12//! - **Two execution modes** - fork child process or replace current process
13//! - **`no_std`** - works in embedded and kernel environments  
14//!
15//! ## Platform Support
16//!
17//! - **Linux only** - requires `memfd_create` system call (Linux 3.17+)
18//! - **x86_64** - tested on x86_64 architecture
19//!
20//! ## Usage
21//!
22//! ### Simple execution (fork mode)
23//!
24//! ```rust,no_run
25//! use memfd_runner::run;
26//!
27//! let elf_bytes = std::fs::read("/usr/bin/echo").unwrap();
28//! let exit_code = run(&elf_bytes).unwrap();
29//! println!("Process exited with code: {}", exit_code);
30//! ```
31//!
32//! ### Replace current process
33//!
34//! ```rust,no_run
35//! use memfd_runner::{run_with_options, RunOptions};
36//!
37//! let elf_bytes = std::fs::read("/usr/bin/uname").unwrap();
38//! let options = RunOptions::new().with_replace(true);
39//! run_with_options(&elf_bytes, options).unwrap(); // Does not return
40//! ```
41//!
42//! ### Passing arguments and environment variables
43//!
44//! ```rust,no_run
45//! use memfd_runner::{run_with_options, RunOptions};
46//!
47//! let elf_bytes = std::fs::read("/usr/bin/echo").unwrap();
48//! let options = RunOptions::new()
49//!     .with_args(&["Hello", "World!"])  // Just the arguments, not the program name
50//!     .with_env(&["PATH=/usr/bin", "HOME=/tmp"]);
51//! let exit_code = run_with_options(&elf_bytes, options).unwrap();
52//! // Executes: /proc/self/fd/X "Hello" "World!"
53//! ```
54//!
55//! ### Custom argv[0] (program name)
56//!
57//! ```rust,no_run
58//! use memfd_runner::{run_with_options, RunOptions};
59//!
60//! let elf_bytes = std::fs::read("/usr/bin/echo").unwrap();
61//! let options = RunOptions::new()
62//!     .with_argv0("my-echo")  // Custom program name
63//!     .with_args(&["Hello", "World!"]);
64//! let exit_code = run_with_options(&elf_bytes, options).unwrap();
65//! // The program sees argv[0] as "my-echo" instead of "/proc/self/fd/X"
66//! ```
67//!
68//! ### Error handling
69//!
70//! ```rust,no_run
71//! use memfd_runner::{run, RunError};
72//!
73//! let invalid_elf = b"not an elf file";
74//! match run(invalid_elf) {
75//!     Ok(exit_code) => println!("Success: {}", exit_code),
76//!     Err(RunError::InvalidElfFormat) => println!("Invalid ELF format"),
77//!     Err(RunError::FdCreationFailed(errno)) => println!("Failed to create memfd: {}", errno),
78//!     Err(e) => println!("Other error: {:?}", e),
79//! }
80//! ```
81//!
82//! ## Limitations
83//!
84//! - Linux-specific
85//! - Maximum 32 command line arguments (256 chars each)
86//! - Maximum 64 environment variables (256 chars each)
87//! - Very basic ELF validation only (magic bytes, minimum size)
88//! - No support for complex ELF features or dynamic linking validation
89
90#![no_std]
91
92mod syscalls;
93
94const MFD_CLOEXEC: u8 = 0x1;
95
96#[used]
97pub static EMPTY_STRING: [u8; 8] = [0; 8];
98
99/// Error types returned by memfd-runner operations.
100#[derive(Debug)]
101pub enum RunError {
102    /// Failed to create memory file descriptor via memfd_create()
103    FdCreationFailed(i32),
104    /// Failed to write all ELF bytes to memory file
105    BytesNotWritten(usize, usize),
106    /// execve() system call failed
107    ExecError(i32),
108    /// fork() system call failed
109    ForkError(i32),
110    /// wait4() system call failed while waiting for child process
111    WaitError(i32),
112    /// ELF validation failed - invalid magic bytes or insufficient size
113    InvalidElfFormat,
114    /// Too many command line arguments provided (limit: 32)
115    TooManyArgs,
116    /// Too many environment variables provided (limit: 64)
117    TooManyEnvVars,
118    /// Command line argument too long (limit: 256 characters)
119    ArgTooLong,
120    /// Environment variable too long (limit: 256 characters)
121    EnvVarTooLong,
122}
123
124const MAX_ARGS: usize = 32;
125const MAX_ENV: usize = 64;
126const MAX_STRING_LEN: usize = 256;
127
128/// Options which can be used to customize arguments, environment and how the ELF is executed.
129#[derive(Clone, Default)]
130pub struct RunOptions<'a> {
131    replace: bool,
132    args: Option<&'a [&'a str]>,
133    env: Option<&'a [&'a str]>,
134    argv0: Option<&'a str>,
135}
136
137impl<'a> RunOptions<'a> {
138    /// Creates a blank new set of options ready for configuration.
139    ///
140    /// All options are initially empty / set to false.
141    pub fn new() -> Self {
142        Self::default()
143    }
144
145    /// Toggles the replace mode. If set to `true`, the current process will be replaced by the executed binary.
146    /// Otherwise, `fork()` will be called and the current process will be able to wait for the child.
147    pub fn with_replace(mut self, replace: bool) -> Self {
148        self.replace = replace;
149        self
150    }
151
152    /// Set command line arguments for the executed binary.
153    ///
154    /// These are the actual arguments passed to the program (argv[1], argv[2], etc.).
155    /// The program name (argv[0]) is automatically set to the memfd path.
156    ///
157    /// # Example
158    /// ```rust,no_run
159    /// use memfd_runner::{run_with_options, RunOptions};
160    ///
161    /// let elf_bytes = std::fs::read("/usr/bin/echo").unwrap();
162    /// let options = RunOptions::new().with_args(&["Hello", "World!"]);
163    /// let exit_code = run_with_options(&elf_bytes, options).unwrap();
164    /// // This executes: /proc/self/fd/X "Hello" "World!"
165    /// ```
166    pub fn with_args(mut self, args: &'a [&'a str]) -> Self {
167        self.args = Some(args);
168        self
169    }
170
171    /// Set environment variables for the executed binary.
172    /// Environment variables should be in "KEY=value" format.
173    ///
174    /// # Example
175    /// ```rust,no_run
176    /// use memfd_runner::{run_with_options, RunOptions};
177    ///
178    /// let elf_bytes = std::fs::read("/usr/bin/env").unwrap();
179    /// let options = RunOptions::new().with_env(&["PATH=/usr/bin", "HOME=/tmp"]);
180    /// let exit_code = run_with_options(&elf_bytes, options).unwrap();
181    /// ```
182    pub fn with_env(mut self, env: &'a [&'a str]) -> Self {
183        self.env = Some(env);
184        self
185    }
186
187    /// Set a custom argv[0] for the executed binary.
188    ///
189    /// By default, argv[0] is set to the memfd path (`/proc/self/fd/N`). This method
190    /// allows you to customize what the executed program sees as its program name.
191    ///
192    /// # Example
193    /// ```rust,no_run
194    /// use memfd_runner::{run_with_options, RunOptions};
195    ///
196    /// let elf_bytes = std::fs::read("/usr/bin/echo").unwrap();
197    /// let options = RunOptions::new()
198    ///     .with_argv0("my-custom-program")
199    ///     .with_args(&["Hello", "World!"]);
200    /// let exit_code = run_with_options(&elf_bytes, options).unwrap();
201    /// // The program sees argv[0] as "my-custom-program"
202    /// ```
203    pub fn with_argv0(mut self, argv0: &'a str) -> Self {
204        self.argv0 = Some(argv0);
205        self
206    }
207}
208
209/// Executes an in-memory ELF binary by creating a child process.
210///
211/// This is the simplest way to execute an ELF binary from memory. It does a very basic ELF header verification,
212/// creates a memory file descriptor, writes the ELF data to it, then forks a child process
213/// and executes the binary via `/proc/self/fd/{fd}`.
214///
215/// # Arguments
216///
217/// * `bytes` - The ELF binary data to execute (must have valid ELF magic bytes)
218///
219/// # Returns
220///
221/// * `Ok(exit_code)` - The exit code of the executed process (0-255)
222/// * `Err(RunError)` - Various error conditions during execution
223///
224/// # Examples
225///
226/// ```rust,no_run
227/// // Execute /usr/bin/ls from memory
228/// let elf_bytes = std::fs::read("/usr/bin/ls").unwrap();
229/// let exit_code = memfd_runner::run(&elf_bytes).unwrap();
230/// println!("Process exited with code: {}", exit_code);
231/// ```
232pub fn run<B: AsRef<[u8]>>(bytes: B) -> Result<i32, RunError> {
233    run_with_options(bytes, RunOptions::default())
234}
235
236/// Executes an in-memory ELF binary with configurable options.
237///
238/// This function provides more control over the execution process compared to [`run`].
239/// It supports both fork mode (default) and replace mode where the current process
240/// is replaced by the executed binary.
241///
242/// # Arguments
243///
244/// * `bytes` - The ELF binary data to execute (must have valid ELF magic bytes)
245/// * `options` - Configuration options for execution behavior
246///
247/// # Returns
248///
249/// * `Ok(exit_code)` - The exit code of the executed process (fork mode only)
250/// * `Err(RunError)` - Various error conditions during execution
251/// * **Never returns** in `replace` mode on successful execution
252///
253/// # Examples
254///
255/// ```rust,no_run
256/// use memfd_runner::{run_with_options, RunOptions};
257///
258/// let elf_bytes = std::fs::read("/usr/bin/uname").unwrap();
259/// // replace mode
260/// let options = RunOptions::new().with_replace(true);
261/// run_with_options(&elf_bytes, options).unwrap(); // never returns
262/// unreachable!("this line will never execute");
263/// ```
264pub fn run_with_options<B: AsRef<[u8]>>(
265    bytes: B,
266    options: RunOptions<'_>,
267) -> Result<i32, RunError> {
268    let fd = create_fd()?;
269    let bytes = bytes.as_ref();
270    write_bytes(fd, bytes)?;
271    execute(fd, options)
272}
273
274fn create_fd() -> Result<u16, RunError> {
275    // Safety: EMPTY_STRING is a valid null-terminated string
276    let fd = unsafe { syscalls::memfd_create(EMPTY_STRING, MFD_CLOEXEC as u32) };
277    if fd == -1 {
278        return Err(RunError::FdCreationFailed(-1)); // TODO: get actual errno
279    }
280    Ok(fd as _)
281}
282
283fn validate_elf_header(bytes: &[u8]) -> bool {
284    // Check minimum header size
285    if bytes.len() < 16 {
286        return false;
287    }
288    // Check ELF magic: 0x7f, 'E', 'L', 'F'
289    bytes[0] == 0x7f && bytes[1] == b'E' && bytes[2] == b'L' && bytes[3] == b'F'
290}
291
292fn write_bytes(fd: u16, bytes: &[u8]) -> Result<(), RunError> {
293    if !validate_elf_header(bytes) {
294        unsafe { syscalls::close(fd as i32) };
295        return Err(RunError::InvalidElfFormat);
296    }
297    let written = unsafe { syscalls::write(fd as _, bytes.as_ptr().cast_mut(), bytes.len()) };
298    if written != bytes.len() as _ {
299        unsafe { syscalls::close(fd as i32) };
300        return Err(RunError::BytesNotWritten(written as usize, bytes.len()));
301    }
302    Ok(())
303}
304
305/// Prepare argv directly in provided stack storage
306/// Returns the number of arguments prepared
307fn prepare_argv(
308    fd: u16,
309    options: &RunOptions<'_>,
310    storage: &mut [[u8; MAX_STRING_LEN]; MAX_ARGS],
311    ptrs: &mut [*const u8; MAX_ARGS + 1],
312) -> Result<usize, RunError> {
313    // Set argv[0] - either custom or default memfd path
314    if let Some(custom_argv0) = options.argv0 {
315        let argv0_bytes = custom_argv0.as_bytes();
316        if argv0_bytes.len() >= MAX_STRING_LEN {
317            return Err(RunError::ArgTooLong);
318        }
319        storage[0][..argv0_bytes.len()].copy_from_slice(argv0_bytes);
320        storage[0][argv0_bytes.len()] = 0; // null terminate
321    } else {
322        // Use default memfd path
323        let path = build_path(fd);
324        let null_pos = path.iter().position(|&b| b == 0).unwrap();
325        storage[0][..null_pos].copy_from_slice(&path[..null_pos]);
326        storage[0][null_pos] = 0; // ensure null termination
327    }
328
329    // Add user-provided arguments
330    let mut arg_count = 1;
331    if let Some(user_args) = options.args {
332        if user_args.len() > MAX_ARGS - 1 {
333            return Err(RunError::TooManyArgs);
334        }
335
336        for &arg in user_args.iter() {
337            let arg_bytes = arg.as_bytes();
338            if arg_bytes.len() >= MAX_STRING_LEN {
339                return Err(RunError::ArgTooLong);
340            }
341
342            storage[arg_count][..arg_bytes.len()].copy_from_slice(arg_bytes);
343            storage[arg_count][arg_bytes.len()] = 0; // null terminate
344            arg_count += 1;
345        }
346    }
347
348    // CRITICAL FIX: Set all pointers AFTER all data is prepared
349    for i in 0..arg_count {
350        ptrs[i] = storage[i].as_ptr();
351    }
352    ptrs[arg_count] = core::ptr::null();
353
354    Ok(arg_count)
355}
356
357/// Prepare envp directly in provided stack storage
358/// Returns the number of environment variables prepared
359fn prepare_envp(
360    env: Option<&[&str]>,
361    storage: &mut [[u8; MAX_STRING_LEN]; MAX_ENV],
362    ptrs: &mut [*const u8; MAX_ENV + 1],
363) -> Result<usize, RunError> {
364    let mut env_count = 0;
365
366    if let Some(user_env) = env {
367        if user_env.len() > MAX_ENV {
368            return Err(RunError::TooManyEnvVars);
369        }
370
371        for (i, &env_var) in user_env.iter().enumerate() {
372            let env_bytes = env_var.as_bytes();
373            if env_bytes.len() >= MAX_STRING_LEN {
374                return Err(RunError::EnvVarTooLong);
375            }
376
377            storage[i][..env_bytes.len()].copy_from_slice(env_bytes);
378            storage[i][env_bytes.len()] = 0; // null terminate
379            ptrs[i] = storage[i].as_ptr();
380            env_count += 1;
381        }
382    }
383    ptrs[env_count] = core::ptr::null();
384    Ok(env_count)
385}
386
387/// Execute the child process
388fn execute_child(fd: u16, options: &RunOptions<'_>) -> Result<i32, RunError> {
389    let path = build_path(fd);
390
391    // Stack-allocated storage
392    let mut argv_storage: [[u8; MAX_STRING_LEN]; MAX_ARGS] = [[0; MAX_STRING_LEN]; MAX_ARGS];
393    let mut argv: [*const u8; MAX_ARGS + 1] = [core::ptr::null(); MAX_ARGS + 1];
394
395    let mut envp_storage: [[u8; MAX_STRING_LEN]; MAX_ENV] = [[0; MAX_STRING_LEN]; MAX_ENV];
396    let mut envp: [*const u8; MAX_ENV + 1] = [core::ptr::null(); MAX_ENV + 1];
397
398    // Prepare arguments and environment directly in stack storage
399    prepare_argv(fd, options, &mut argv_storage, &mut argv)?;
400    prepare_envp(options.env, &mut envp_storage, &mut envp)?;
401
402    // Execute with stable pointers using direct syscall
403
404    let ret = unsafe { syscalls::execve(path, argv.as_ptr() as *mut u8, envp.as_ptr() as *mut u8) };
405
406    if ret == -1 {
407        return Err(RunError::ExecError(-1)); // TODO: get actual errno
408    }
409    unreachable!("execve should not return on success");
410}
411
412fn execute(fd: u16, options: RunOptions<'_>) -> Result<i32, RunError> {
413    let pid = match options.replace {
414        true => 0, // simulate we are the child
415        false => unsafe { syscalls::fork() },
416    };
417
418    // if child, call execve
419    match pid {
420        0 => execute_child(fd, &options),
421        -1 => Err(RunError::ForkError(-1)), // TODO: get actual errno
422        _ => {
423            let mut status: i32 = 0;
424            let waited_pid = unsafe {
425                syscalls::wait4(
426                    pid,
427                    &mut status as *mut i32 as *mut u8,
428                    0,
429                    core::ptr::null_mut(),
430                )
431            };
432            if waited_pid == -1 {
433                return Err(RunError::WaitError(-1)); // TODO: get actual errno
434            }
435            // Extract exit code using WEXITSTATUS equivalent: (status >> 8) & 0xff
436            Ok((status >> 8) & 0xff)
437        }
438    }
439}
440
441const EXEC_PATH: [u8; 20] = *b"/proc/self/fd/\0\0\0\0\0\0";
442const EXEC_PATH_LEN: usize = EXEC_PATH.len();
443
444fn build_path(fd: u16) -> [u8; EXEC_PATH_LEN] {
445    let mut path = [0u8; EXEC_PATH_LEN];
446
447    unsafe { core::ptr::copy_nonoverlapping(EXEC_PATH.as_ptr(), path.as_mut_ptr(), EXEC_PATH_LEN) };
448    let mut idx = 14;
449    if fd >= 10000 {
450        path[idx] = b'0' + (fd / 10000) as u8;
451        idx += 1;
452    }
453    if fd >= 1000 {
454        path[idx] = b'0' + ((fd / 1000) % 10) as u8;
455        idx += 1;
456    }
457    if fd >= 100 {
458        path[idx] = b'0' + ((fd / 100) % 10) as u8;
459        idx += 1;
460    }
461    if fd >= 10 {
462        path[idx] = b'0' + ((fd / 10) % 10) as u8;
463        idx += 1;
464    }
465    path[idx] = b'0' + (fd % 10) as u8;
466    path
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472    extern crate std;
473    use std::format;
474
475    // Helper functions for testing the new inline API
476    fn test_prepare_argv(fd: u16, options: &RunOptions<'_>) -> Result<usize, RunError> {
477        let mut storage: [[u8; MAX_STRING_LEN]; MAX_ARGS] = [[0; MAX_STRING_LEN]; MAX_ARGS];
478        let mut ptrs: [*const u8; MAX_ARGS + 1] = [core::ptr::null(); MAX_ARGS + 1];
479        prepare_argv(fd, options, &mut storage, &mut ptrs)
480    }
481
482    fn test_prepare_envp(env: Option<&[&str]>) -> Result<usize, RunError> {
483        let mut storage: [[u8; MAX_STRING_LEN]; MAX_ENV] = [[0; MAX_STRING_LEN]; MAX_ENV];
484        let mut ptrs: [*const u8; MAX_ENV + 1] = [core::ptr::null(); MAX_ENV + 1];
485        prepare_envp(env, &mut storage, &mut ptrs)
486    }
487
488    // RunOptions Tests
489    #[test]
490    fn test_run_options_default() {
491        let options = RunOptions::new();
492        assert!(!options.replace);
493        assert!(options.args.is_none());
494        assert!(options.env.is_none());
495    }
496
497    #[test]
498    fn test_run_options_with_replace() {
499        let options = RunOptions::new().with_replace(true);
500        assert!(options.replace);
501    }
502
503    #[test]
504    fn test_run_options_with_args() {
505        let args = ["test", "arg1", "arg2"];
506        let options = RunOptions::new().with_args(&args);
507        assert!(options.args.is_some());
508        assert_eq!(options.args.unwrap().len(), 3);
509        assert_eq!(options.args.unwrap()[0], "test");
510        assert_eq!(options.args.unwrap()[1], "arg1");
511        assert_eq!(options.args.unwrap()[2], "arg2");
512    }
513
514    #[test]
515    fn test_run_options_with_env() {
516        let env = ["PATH=/usr/bin", "HOME=/tmp"];
517        let options = RunOptions::new().with_env(&env);
518        assert!(options.env.is_some());
519        assert_eq!(options.env.unwrap().len(), 2);
520        assert_eq!(options.env.unwrap()[0], "PATH=/usr/bin");
521        assert_eq!(options.env.unwrap()[1], "HOME=/tmp");
522    }
523
524    #[test]
525    fn test_run_options_chaining() {
526        let args = ["test", "arg1"];
527        let env = ["VAR=value"];
528        let options = RunOptions::new()
529            .with_replace(true)
530            .with_args(&args)
531            .with_env(&env);
532
533        assert!(options.replace);
534        assert!(options.args.is_some());
535        assert!(options.env.is_some());
536        assert_eq!(options.args.unwrap().len(), 2);
537        assert_eq!(options.env.unwrap().len(), 1);
538    }
539
540    #[test]
541    fn test_run_options_with_argv0() {
542        let options = RunOptions::new().with_argv0("custom-program");
543        assert!(options.argv0.is_some());
544        assert_eq!(options.argv0.unwrap(), "custom-program");
545    }
546
547    #[test]
548    fn test_run_options_argv0_chaining() {
549        let args = ["test", "arg1"];
550        let env = ["VAR=value"];
551        let options = RunOptions::new()
552            .with_argv0("my-program")
553            .with_args(&args)
554            .with_env(&env);
555
556        assert!(options.argv0.is_some());
557        assert_eq!(options.argv0.unwrap(), "my-program");
558        assert!(options.args.is_some());
559        assert!(options.env.is_some());
560    }
561
562    // prepare_argv Tests
563    #[test]
564    fn test_prepare_argv_path_only() {
565        let options = RunOptions::new();
566        let result = test_prepare_argv(123, &options);
567        assert!(result.is_ok());
568        assert_eq!(result.unwrap(), 1);
569    }
570
571    #[test]
572    fn test_prepare_argv_with_args() {
573        let args = ["arg1", "arg2"];
574        let options = RunOptions::new().with_args(&args);
575        let result = test_prepare_argv(123, &options);
576        assert!(result.is_ok());
577        assert_eq!(result.unwrap(), 3);
578    }
579
580    #[test]
581    fn test_prepare_argv_too_many_args() {
582        let mut args = std::vec::Vec::new();
583        for _i in 0..MAX_ARGS {
584            args.push("arg");
585        }
586        let options = RunOptions::new().with_args(&args);
587        let result = test_prepare_argv(123, &options);
588        assert!(matches!(result, Err(RunError::TooManyArgs)));
589    }
590
591    #[test]
592    fn test_prepare_argv_arg_too_long() {
593        let long_arg = "a".repeat(MAX_STRING_LEN);
594        let args = [long_arg.as_str()];
595        let options = RunOptions::new().with_args(&args);
596        let result = test_prepare_argv(123, &options);
597        assert!(matches!(result, Err(RunError::ArgTooLong)));
598    }
599
600    #[test]
601    fn test_prepare_argv_custom_argv0() {
602        let options = RunOptions::new().with_argv0("my-custom-program");
603        let result = test_prepare_argv(123, &options);
604        assert!(result.is_ok());
605        assert_eq!(result.unwrap(), 1);
606    }
607
608    #[test]
609    fn test_prepare_argv_custom_argv0_with_args() {
610        let args = ["arg1", "arg2"];
611        let options = RunOptions::new().with_argv0("custom-name").with_args(&args);
612        let result = test_prepare_argv(123, &options);
613        assert!(result.is_ok());
614        assert_eq!(result.unwrap(), 3);
615    }
616
617    #[test]
618    fn test_prepare_argv_custom_argv0_too_long() {
619        let long_argv0 = "a".repeat(MAX_STRING_LEN);
620        let options = RunOptions::new().with_argv0(&long_argv0);
621        let result = test_prepare_argv(123, &options);
622        assert!(matches!(result, Err(RunError::ArgTooLong)));
623    }
624
625    // prepare_envp Tests
626    #[test]
627    fn test_prepare_envp_none() {
628        let result = test_prepare_envp(None);
629        assert!(result.is_ok());
630        assert_eq!(result.unwrap(), 0);
631    }
632
633    #[test]
634    fn test_prepare_envp_with_env() {
635        let env = ["PATH=/usr/bin", "HOME=/tmp"];
636        let result = test_prepare_envp(Some(&env));
637        assert!(result.is_ok());
638        assert_eq!(result.unwrap(), 2);
639    }
640
641    #[test]
642    fn test_prepare_envp_too_many_vars() {
643        let mut env = std::vec::Vec::new();
644        for _i in 0..MAX_ENV + 1 {
645            env.push("VAR=value");
646        }
647        let result = test_prepare_envp(Some(&env));
648        assert!(matches!(result, Err(RunError::TooManyEnvVars)));
649    }
650
651    #[test]
652    fn test_prepare_envp_var_too_long() {
653        let long_var = format!("VAR={}", "a".repeat(MAX_STRING_LEN));
654        let env = [long_var.as_str()];
655        let result = test_prepare_envp(Some(&env));
656        assert!(matches!(result, Err(RunError::EnvVarTooLong)));
657    }
658
659    // New error types tests
660    #[test]
661    fn test_new_error_types() {
662        let errors = [
663            RunError::TooManyArgs,
664            RunError::TooManyEnvVars,
665            RunError::ArgTooLong,
666            RunError::EnvVarTooLong,
667        ];
668
669        for error in &errors {
670            let debug_str = format!("{error:?}");
671            assert!(!debug_str.is_empty());
672        }
673    }
674}