vtcode_process_hardening/
lib.rs

1#[cfg(unix)]
2use std::ffi::OsString;
3#[cfg(unix)]
4use std::os::unix::ffi::OsStrExt;
5
6/// This is designed to be called pre-main() (using `#[ctor::ctor]`) to perform
7/// various process hardening steps, such as:
8/// - disabling core dumps
9/// - disabling ptrace attach on Linux and macOS
10/// - removing dangerous environment variables such as LD_PRELOAD and DYLD_*
11pub fn pre_main_hardening() {
12    #[cfg(any(target_os = "linux", target_os = "android"))]
13    pre_main_hardening_linux();
14
15    #[cfg(target_os = "macos")]
16    pre_main_hardening_macos();
17
18    #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
19    pre_main_hardening_bsd();
20
21    #[cfg(windows)]
22    pre_main_hardening_windows();
23}
24
25#[cfg(any(target_os = "linux", target_os = "android"))]
26const PRCTL_FAILED_EXIT_CODE: i32 = 5;
27
28#[cfg(target_os = "macos")]
29const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6;
30
31#[cfg(any(
32    target_os = "linux",
33    target_os = "android",
34    target_os = "macos",
35    target_os = "freebsd",
36    target_os = "netbsd",
37    target_os = "openbsd"
38))]
39const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7;
40
41#[cfg(any(target_os = "linux", target_os = "android"))]
42pub(crate) fn pre_main_hardening_linux() {
43    // Disable ptrace attach / mark process non-dumpable.
44    let ret_code = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
45    if ret_code != 0 {
46        eprintln!(
47            "ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
48            std::io::Error::last_os_error()
49        );
50        std::process::exit(PRCTL_FAILED_EXIT_CODE);
51    }
52
53    // For "defense in depth," set the core file size limit to 0.
54    set_core_file_size_limit_to_zero();
55
56    // VT Code is primarily MUSL-linked in release builds, which means that variables such
57    // as LD_PRELOAD are ignored anyway, but just to be sure, clear them here.
58    let ld_keys = env_keys_with_prefix(std::env::vars_os(), b"LD_");
59    for key in ld_keys {
60        unsafe {
61            std::env::remove_var(key);
62        }
63    }
64}
65
66#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
67pub(crate) fn pre_main_hardening_bsd() {
68    // FreeBSD/OpenBSD: set RLIMIT_CORE to 0 and clear LD_* env vars
69    set_core_file_size_limit_to_zero();
70
71    let ld_keys = env_keys_with_prefix(std::env::vars_os(), b"LD_");
72    for key in ld_keys {
73        unsafe {
74            std::env::remove_var(key);
75        }
76    }
77}
78
79#[cfg(target_os = "macos")]
80pub(crate) fn pre_main_hardening_macos() {
81    // Prevent debuggers from attaching to this process.
82    let ret_code = unsafe { libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) };
83    if ret_code == -1 {
84        eprintln!(
85            "ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
86            std::io::Error::last_os_error()
87        );
88        std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
89    }
90
91    // Set the core file size limit to 0 to prevent core dumps.
92    set_core_file_size_limit_to_zero();
93
94    // Remove all DYLD_* environment variables, which can be used to subvert
95    // library loading.
96    let dyld_keys = env_keys_with_prefix(std::env::vars_os(), b"DYLD_");
97    for key in dyld_keys {
98        unsafe {
99            std::env::remove_var(key);
100        }
101    }
102}
103
104#[cfg(unix)]
105fn set_core_file_size_limit_to_zero() {
106    let rlim = libc::rlimit {
107        rlim_cur: 0,
108        rlim_max: 0,
109    };
110    let ret_code = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim) };
111    if ret_code != 0 {
112        eprintln!(
113            "ERROR: setrlimit(RLIMIT_CORE) failed: {}",
114            std::io::Error::last_os_error()
115        );
116        std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
117    }
118}
119
120#[cfg(windows)]
121pub(crate) fn pre_main_hardening_windows() {
122    // Windows process hardening would involve using Job Objects to limit
123    // resource usage and restrict UI access, or using restricted tokens.
124    // This is currently a future enhancement.
125}
126
127#[cfg(unix)]
128fn env_keys_with_prefix<I>(vars: I, prefix: &[u8]) -> Vec<OsString>
129where
130    I: IntoIterator<Item = (OsString, OsString)>,
131{
132    vars.into_iter()
133        .filter_map(|(key, _)| {
134            key.as_os_str()
135                .as_bytes()
136                .starts_with(prefix)
137                .then_some(key)
138        })
139        .collect()
140}
141
142#[cfg(all(test, unix))]
143mod tests {
144    use super::*;
145    use pretty_assertions::assert_eq;
146    use std::ffi::OsStr;
147    use std::os::unix::ffi::OsStrExt;
148    use std::os::unix::ffi::OsStringExt;
149
150    #[test]
151    fn env_keys_with_prefix_handles_non_utf8_entries() {
152        // RĂ–DBURK - non-UTF8 environment variable name
153        let non_utf8_key1 = OsStr::from_bytes(b"R\xD6DBURK").to_os_string();
154        assert!(non_utf8_key1.clone().into_string().is_err());
155
156        let non_utf8_key2 = OsString::from_vec(vec![b'L', b'D', b'_', 0xF0]);
157        assert!(non_utf8_key2.clone().into_string().is_err());
158
159        let non_utf8_value = OsString::from_vec(vec![0xF0, 0x9F, 0x92, 0xA9]);
160
161        let keys = env_keys_with_prefix(
162            vec![
163                (non_utf8_key1, non_utf8_value.clone()),
164                (non_utf8_key2.clone(), non_utf8_value),
165            ],
166            b"LD_",
167        );
168
169        assert_eq!(
170            keys,
171            vec![non_utf8_key2],
172            "non-UTF-8 env entries with LD_ prefix should be retained"
173        );
174    }
175
176    #[test]
177    fn env_keys_with_prefix_filters_only_matching_keys() {
178        let ld_test_var = OsStr::from_bytes(b"LD_TEST");
179        let vars = vec![
180            (OsString::from("PATH"), OsString::from("/usr/bin")),
181            (ld_test_var.to_os_string(), OsString::from("1")),
182            (OsString::from("DYLD_FOO"), OsString::from("bar")),
183        ];
184
185        let keys = env_keys_with_prefix(vars, b"LD_");
186        assert_eq!(keys.len(), 1);
187        assert_eq!(keys[0].as_os_str(), ld_test_var);
188    }
189}