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