Skip to main content

libdd_common/unix_utils/
execve.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use libc::execve;
5use nix::errno::Errno;
6use std::ffi::CString;
7
8// The args_cstrings and env_vars_strings fields are just storage.  Even though they're
9// unreferenced, they're a necessary part of the struct.
10#[allow(dead_code)]
11#[derive(Debug)]
12pub struct PreparedExecve {
13    binary_path: CString,
14    args_cstrings: Vec<CString>,
15    args_ptrs: Vec<*const libc::c_char>,
16    env_vars_cstrings: Vec<CString>,
17    env_vars_ptrs: Vec<*const libc::c_char>,
18}
19
20#[derive(Debug, thiserror::Error)]
21pub enum PreparedExecveError {
22    #[error("Failed to convert binary path to CString: {0}")]
23    BinaryPathError(std::ffi::NulError),
24    #[error("Failed to convert argument to CString: {0}")]
25    ArgumentError(std::ffi::NulError),
26    #[error("Failed to convert environment variable to CString: {0}")]
27    EnvironmentError(std::ffi::NulError),
28}
29
30impl From<std::ffi::NulError> for PreparedExecveError {
31    fn from(err: std::ffi::NulError) -> Self {
32        PreparedExecveError::BinaryPathError(err)
33    }
34}
35
36impl PreparedExecve {
37    pub fn new(
38        binary_path: &str,
39        args: &[String],
40        env: &[(String, String)],
41    ) -> Result<Self, PreparedExecveError> {
42        // Allocate and store binary path
43        let binary_path =
44            CString::new(binary_path).map_err(PreparedExecveError::BinaryPathError)?;
45
46        // Allocate and store arguments
47        let args_cstrings: Vec<CString> = args
48            .iter()
49            .map(|s| CString::new(s.as_str()))
50            .collect::<Result<Vec<CString>, std::ffi::NulError>>()
51            .map_err(PreparedExecveError::ArgumentError)?;
52        let args_ptrs: Vec<*const libc::c_char> = args_cstrings
53            .iter()
54            .map(|arg| arg.as_ptr())
55            .chain(std::iter::once(std::ptr::null())) // Adds a null pointer to the end of the list
56            .collect();
57
58        // Allocate and store environment variables
59        let env_vars_cstrings: Vec<CString> = env
60            .iter()
61            .map(|(key, value)| {
62                let env_str = format!("{key}={value}");
63                CString::new(env_str)
64            })
65            .collect::<Result<Vec<CString>, std::ffi::NulError>>()
66            .map_err(PreparedExecveError::EnvironmentError)?;
67        let env_vars_ptrs: Vec<*const libc::c_char> = env_vars_cstrings
68            .iter()
69            .map(|env| env.as_ptr())
70            .chain(std::iter::once(std::ptr::null())) // Adds a null pointer to the end of the list
71            .collect();
72
73        Ok(Self {
74            binary_path,
75            args_cstrings,
76            args_ptrs,
77            env_vars_cstrings,
78            env_vars_ptrs,
79        })
80    }
81
82    /// Calls `execve` on the prepared arguments.
83    pub fn exec(&self) -> Result<(), Errno> {
84        // Safety: the only way to make one of these is through `new`, which ensures that everything
85        // is well-formed.
86        unsafe {
87            if execve(
88                self.binary_path.as_ptr(),
89                self.args_ptrs.as_ptr(),
90                self.env_vars_ptrs.as_ptr(),
91            ) == -1
92            {
93                Err(Errno::last())
94            } else {
95                Ok(())
96            }
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    // Note: None of these tests call the exec() method, because execve replaces the current process
104    // image. If exec() were called, the test process would be replaced and the test runner
105    // would lose control. To test exec(), use an integration test that forks a child process
106    // and calls exec() in the child.
107    use super::*;
108
109    #[test]
110    fn test_prepared_execve_new_basic() {
111        let binary_path = "/bin/echo";
112        let args = vec!["hello".to_string(), "world".to_string()];
113        let env = vec![
114            ("PATH".to_string(), "/bin:/usr/bin".to_string()),
115            ("HOME".to_string(), "/home/user".to_string()),
116        ];
117
118        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
119
120        // Verify the struct was created successfully
121        // We can't directly access private fields, but we can verify the struct exists
122        assert!(std::mem::size_of_val(&prepared) > 0);
123    }
124
125    #[test]
126    fn test_prepared_execve_new_empty_args() {
127        let binary_path = "/bin/true";
128        let args: Vec<String> = vec![];
129        let env: Vec<(String, String)> = vec![];
130
131        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
132
133        // Should still create successfully with empty args and env
134        assert!(std::mem::size_of_val(&prepared) > 0);
135    }
136
137    #[test]
138    fn test_prepared_execve_new_complex_args() {
139        let binary_path = "/usr/bin/env";
140        let args = vec![
141            "program".to_string(),
142            "--flag".to_string(),
143            "value with spaces".to_string(),
144            "arg with \"quotes\"".to_string(),
145        ];
146        let env = vec![
147            ("VAR1".to_string(), "value1".to_string()),
148            ("VAR2".to_string(), "value with spaces".to_string()),
149            ("VAR3".to_string(), "value with \"quotes\"".to_string()),
150        ];
151
152        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
153
154        // Should handle complex arguments and environment variables
155        assert!(std::mem::size_of_val(&prepared) > 0);
156    }
157
158    #[test]
159    fn test_prepared_execve_new_special_characters() {
160        let binary_path = "/bin/test";
161        let args = vec![
162            "normal".to_string(),
163            "with\nnewline".to_string(),
164            "with\ttab".to_string(),
165            "with\0null".to_string(),
166        ];
167        let env = vec![
168            ("NORMAL".to_string(), "value".to_string()),
169            ("SPECIAL".to_string(), "value\nwith\nnewlines".to_string()),
170        ];
171
172        // This should return an error due to null bytes in the arguments
173        let result = PreparedExecve::new(binary_path, &args, &env);
174        assert!(result.is_err());
175        assert!(matches!(
176            result.unwrap_err(),
177            PreparedExecveError::ArgumentError(_)
178        ));
179    }
180
181    #[test]
182    fn test_prepared_execve_new_null_bytes_in_env() {
183        let binary_path = "/bin/test";
184        let args = vec!["normal".to_string()];
185        let env = vec![
186            ("NORMAL".to_string(), "value".to_string()),
187            ("SPECIAL".to_string(), "value\0with\0nulls".to_string()),
188        ];
189
190        // This should return an error due to null bytes in the environment variables
191        let result = PreparedExecve::new(binary_path, &args, &env);
192        assert!(result.is_err());
193        assert!(matches!(
194            result.unwrap_err(),
195            PreparedExecveError::EnvironmentError(_)
196        ));
197    }
198
199    #[test]
200    fn test_prepared_execve_new_null_bytes_in_binary_path() {
201        let binary_path = "/bin/test\0with\0nulls";
202        let args = vec!["normal".to_string()];
203        let env: Vec<(String, String)> = vec![];
204
205        // This should return an error due to null bytes in the binary path
206        let result = PreparedExecve::new(binary_path, &args, &env);
207        assert!(result.is_err());
208        assert!(matches!(
209            result.unwrap_err(),
210            PreparedExecveError::BinaryPathError(_)
211        ));
212    }
213
214    #[test]
215    fn test_prepared_execve_new_unicode() {
216        let binary_path = "/bin/echo";
217        let args = vec![
218            "normal".to_string(),
219            "with 🦀 emoji".to_string(),
220            "with émojis".to_string(),
221        ];
222        let env = vec![
223            ("NORMAL".to_string(), "value".to_string()),
224            ("UNICODE".to_string(), "value with 🦀 emoji".to_string()),
225            ("ACCENTS".to_string(), "value with émojis".to_string()),
226        ];
227
228        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
229
230        // Should handle Unicode characters
231        assert!(std::mem::size_of_val(&prepared) > 0);
232    }
233
234    #[test]
235    fn test_prepared_execve_new_large_args() {
236        let binary_path = "/bin/echo";
237        let args: Vec<String> = (0..1000).map(|i| format!("arg{i}")).collect();
238        let env = vec![("TEST".to_string(), "value".to_string())];
239
240        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
241
242        // Should handle large numbers of arguments
243        assert!(std::mem::size_of_val(&prepared) > 0);
244    }
245
246    #[test]
247    fn test_prepared_execve_new_large_env() {
248        let binary_path = "/bin/echo";
249        let args = vec!["test".to_string()];
250        let env: Vec<(String, String)> = (0..1000)
251            .map(|i| (format!("VAR{i}"), format!("value{i}")))
252            .collect();
253
254        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
255
256        // Should handle large numbers of environment variables
257        assert!(std::mem::size_of_val(&prepared) > 0);
258    }
259
260    #[test]
261    fn test_prepared_execve_new_empty_binary_path() {
262        let binary_path = "";
263        let args = vec!["test".to_string()];
264        let env: Vec<(String, String)> = vec![];
265
266        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
267
268        // Should handle empty binary path (though this might not be valid for execve)
269        assert!(std::mem::size_of_val(&prepared) > 0);
270    }
271
272    #[test]
273    fn test_prepared_execve_new_empty_env_keys() {
274        let binary_path = "/bin/echo";
275        let args = vec!["test".to_string()];
276        let env = vec![
277            ("".to_string(), "value".to_string()), // Empty key
278            ("KEY".to_string(), "".to_string()),   // Empty value
279        ];
280
281        let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
282
283        // Should handle empty keys and values
284        assert!(std::mem::size_of_val(&prepared) > 0);
285    }
286
287    #[test]
288    fn test_prepared_execve_error_variants() {
289        // Test binary path error
290        let result = PreparedExecve::new("/bin/test\0", &[], &[]);
291        assert!(matches!(
292            result.unwrap_err(),
293            PreparedExecveError::BinaryPathError(_)
294        ));
295
296        // Test argument error
297        let result = PreparedExecve::new("/bin/test", &["arg\0".to_string()], &[]);
298        assert!(matches!(
299            result.unwrap_err(),
300            PreparedExecveError::ArgumentError(_)
301        ));
302
303        // Test environment error
304        let result = PreparedExecve::new(
305            "/bin/test",
306            &[],
307            &[("KEY\0".to_string(), "value".to_string())],
308        );
309        assert!(matches!(
310            result.unwrap_err(),
311            PreparedExecveError::EnvironmentError(_)
312        ));
313    }
314}