vtcode_process_hardening/
lib.rs1#[cfg(unix)]
2use std::ffi::OsString;
3#[cfg(unix)]
4use std::os::unix::ffi::OsStrExt;
5
6#[cfg(any(target_os = "linux", target_os = "android"))]
7#[allow(unsafe_code)]
8fn prctl_set_dumpable() -> i32 {
9 unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) }
12}
13
14#[cfg(target_os = "macos")]
15#[allow(unsafe_code)]
16fn ptrace_deny_attach() -> i32 {
17 unsafe { libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) }
20}
21
22#[cfg(unix)]
23#[allow(unsafe_code)]
24fn remove_env_var(key: OsString) {
25 unsafe { std::env::remove_var(key) }
29}
30
31pub fn pre_main_hardening() {
40 #[cfg(any(target_os = "linux", target_os = "android"))]
41 pre_main_hardening_linux();
42
43 #[cfg(target_os = "macos")]
44 pre_main_hardening_macos();
45
46 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
47 pre_main_hardening_bsd();
48
49 #[cfg(windows)]
50 pre_main_hardening_windows();
51}
52
53#[cfg(any(target_os = "linux", target_os = "android"))]
54const PRCTL_FAILED_EXIT_CODE: i32 = 5;
55
56#[cfg(target_os = "macos")]
57const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6;
58
59#[cfg(any(
60 target_os = "linux",
61 target_os = "android",
62 target_os = "macos",
63 target_os = "freebsd",
64 target_os = "netbsd",
65 target_os = "openbsd"
66))]
67const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7;
68
69#[cfg(any(target_os = "linux", target_os = "android"))]
70pub(crate) fn pre_main_hardening_linux() {
71 cap_stack_rlimit();
72
73 let ret_code = prctl_set_dumpable();
75 if ret_code != 0 {
76 eprintln!(
77 "ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
78 std::io::Error::last_os_error()
79 );
80 std::process::exit(PRCTL_FAILED_EXIT_CODE);
81 }
82
83 set_core_file_size_limit_to_zero();
85
86 let ld_keys = env_keys_with_prefix(std::env::vars_os(), b"LD_");
89 for key in ld_keys {
90 remove_env_var(key);
91 }
92}
93
94#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
95pub(crate) fn pre_main_hardening_bsd() {
96 cap_stack_rlimit();
97
98 set_core_file_size_limit_to_zero();
100
101 let ld_keys = env_keys_with_prefix(std::env::vars_os(), b"LD_");
102 for key in ld_keys {
103 remove_env_var(key);
104 }
105}
106
107#[cfg(target_os = "macos")]
108pub(crate) fn pre_main_hardening_macos() {
109 cap_stack_rlimit();
110
111 let ret_code = ptrace_deny_attach();
113 if ret_code == -1 {
114 eprintln!(
115 "ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
116 std::io::Error::last_os_error()
117 );
118 std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
119 }
120
121 set_core_file_size_limit_to_zero();
123
124 let dyld_keys = env_keys_with_prefix(std::env::vars_os(), b"DYLD_");
127 for key in dyld_keys {
128 remove_env_var(key);
129 }
130}
131
132#[cfg(unix)]
133#[allow(unsafe_code)]
134fn set_core_file_size_limit_to_zero() {
135 let rlim = libc::rlimit {
136 rlim_cur: 0,
137 rlim_max: 0,
138 };
139 let ret_code = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim) };
142 if ret_code != 0 {
143 eprintln!(
144 "ERROR: setrlimit(RLIMIT_CORE) failed: {}",
145 std::io::Error::last_os_error()
146 );
147 std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
148 }
149}
150
151#[allow(unused_variables)]
166#[allow(unsafe_code)]
167fn cap_stack_rlimit() {
168 #[cfg(any(
169 target_os = "linux",
170 target_os = "android",
171 target_os = "macos",
172 target_os = "freebsd",
173 target_os = "netbsd",
174 target_os = "openbsd"
175 ))]
176 {
177 const STACK_CAP_BYTES: u64 = 8 * 1024 * 1024;
178
179 let mut current: libc::rlimit = libc::rlimit {
180 rlim_cur: 0,
181 rlim_max: 0,
182 };
183 let ret = unsafe { libc::getrlimit(libc::RLIMIT_STACK, &mut current) };
185 if ret != 0 {
186 return;
187 }
188 if current.rlim_cur != libc::RLIM_INFINITY {
190 return;
191 }
192 let capped = libc::rlimit {
193 rlim_cur: STACK_CAP_BYTES,
194 rlim_max: STACK_CAP_BYTES,
195 };
196 let _ = unsafe { libc::setrlimit(libc::RLIMIT_STACK, &capped) };
199 }
200}
201
202#[cfg(windows)]
203pub(crate) fn pre_main_hardening_windows() {
204 }
208
209#[cfg(unix)]
210fn env_keys_with_prefix<I>(vars: I, prefix: &[u8]) -> Vec<OsString>
211where
212 I: IntoIterator<Item = (OsString, OsString)>,
213{
214 vars.into_iter()
215 .filter_map(|(key, _)| {
216 key.as_os_str()
217 .as_bytes()
218 .starts_with(prefix)
219 .then_some(key)
220 })
221 .collect()
222}
223
224#[cfg(all(test, unix))]
225mod tests {
226 use super::*;
227 use pretty_assertions::assert_eq;
228 use std::ffi::OsStr;
229 use std::os::unix::ffi::OsStrExt;
230 use std::os::unix::ffi::OsStringExt;
231
232 #[test]
233 fn env_keys_with_prefix_handles_non_utf8_entries() {
234 let non_utf8_key1 = OsStr::from_bytes(b"R\xD6DBURK").to_os_string();
236 assert!(non_utf8_key1.clone().into_string().is_err());
237
238 let non_utf8_key2 = OsString::from_vec(vec![b'L', b'D', b'_', 0xF0]);
239 assert!(non_utf8_key2.clone().into_string().is_err());
240
241 let non_utf8_value = OsString::from_vec(vec![0xF0, 0x9F, 0x92, 0xA9]);
242
243 let keys = env_keys_with_prefix(
244 vec![
245 (non_utf8_key1, non_utf8_value.clone()),
246 (non_utf8_key2.clone(), non_utf8_value),
247 ],
248 b"LD_",
249 );
250
251 assert_eq!(
252 keys,
253 vec![non_utf8_key2],
254 "non-UTF-8 env entries with LD_ prefix should be retained"
255 );
256 }
257
258 #[test]
259 fn env_keys_with_prefix_filters_only_matching_keys() {
260 let ld_test_var = OsStr::from_bytes(b"LD_TEST");
261 let vars = vec![
262 (OsString::from("PATH"), OsString::from("/usr/bin")),
263 (ld_test_var.to_os_string(), OsString::from("1")),
264 (OsString::from("DYLD_FOO"), OsString::from("bar")),
265 ];
266
267 let keys = env_keys_with_prefix(vars, b"LD_");
268 assert_eq!(keys.len(), 1);
269 assert_eq!(keys[0].as_os_str(), ld_test_var);
270 }
271
272 #[test]
273 fn env_keys_with_prefix_returns_empty_when_no_matches_exist() {
274 let vars = vec![
275 (OsString::from("PATH"), OsString::from("/usr/bin")),
276 (OsString::from("HOME"), OsString::from("/tmp/home")),
277 ];
278
279 let keys = env_keys_with_prefix(vars, b"LD_");
280 assert!(keys.is_empty());
281 }
282
283 #[test]
284 fn env_keys_with_prefix_matches_exact_prefix_and_is_case_sensitive() {
285 let vars = vec![
286 (OsString::from("LD_"), OsString::from("exact-prefix")),
287 (OsString::from("Ld_TEST"), OsString::from("mixed-case")),
288 (OsString::from("LD_PRELOAD"), OsString::from("/tmp/lib.so")),
289 ];
290
291 let keys = env_keys_with_prefix(vars, b"LD_");
292 assert_eq!(
293 keys,
294 vec![OsString::from("LD_"), OsString::from("LD_PRELOAD")]
295 );
296 }
297
298 #[test]
299 fn env_keys_with_prefix_supports_dyld_prefix_filtering() {
300 let vars = vec![
301 (
302 OsString::from("DYLD_INSERT_LIBRARIES"),
303 OsString::from("/tmp/inject.dylib"),
304 ),
305 (OsString::from("DYLD_FOO"), OsString::from("bar")),
306 (
307 OsString::from("LD_PRELOAD"),
308 OsString::from("/tmp/other.so"),
309 ),
310 ];
311
312 let keys = env_keys_with_prefix(vars, b"DYLD_");
313 assert_eq!(
314 keys,
315 vec![
316 OsString::from("DYLD_INSERT_LIBRARIES"),
317 OsString::from("DYLD_FOO")
318 ]
319 );
320 }
321}