1use libc::execve;
5use nix::errno::Errno;
6use std::ffi::CString;
7
8#[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 let binary_path =
44 CString::new(binary_path).map_err(PreparedExecveError::BinaryPathError)?;
45
46 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())) .collect();
57
58 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())) .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 pub fn exec(&self) -> Result<(), Errno> {
84 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 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 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 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 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 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 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 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 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 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 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 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()), ("KEY".to_string(), "".to_string()), ];
280
281 let prepared = PreparedExecve::new(binary_path, &args, &env).unwrap();
282
283 assert!(std::mem::size_of_val(&prepared) > 0);
285 }
286
287 #[test]
288 fn test_prepared_execve_error_variants() {
289 let result = PreparedExecve::new("/bin/test\0", &[], &[]);
291 assert!(matches!(
292 result.unwrap_err(),
293 PreparedExecveError::BinaryPathError(_)
294 ));
295
296 let result = PreparedExecve::new("/bin/test", &["arg\0".to_string()], &[]);
298 assert!(matches!(
299 result.unwrap_err(),
300 PreparedExecveError::ArgumentError(_)
301 ));
302
303 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}