codex_process_hardening/
lib.rs1#[cfg(unix)]
2use std::ffi::OsString;
3
4#[cfg(unix)]
5use std::os::unix::ffi::OsStrExt;
6
7pub fn pre_main_hardening() {
13 #[cfg(any(target_os = "linux", target_os = "android"))]
14 pre_main_hardening_linux();
15
16 #[cfg(target_os = "macos")]
17 pre_main_hardening_macos();
18
19 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
21 pre_main_hardening_bsd();
22
23 #[cfg(windows)]
24 pre_main_hardening_windows();
25}
26
27#[cfg(any(target_os = "linux", target_os = "android"))]
28const PRCTL_FAILED_EXIT_CODE: i32 = 5;
29
30#[cfg(target_os = "macos")]
31const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6;
32
33#[cfg(any(
34 target_os = "linux",
35 target_os = "android",
36 target_os = "macos",
37 target_os = "freebsd",
38 target_os = "netbsd",
39 target_os = "openbsd"
40))]
41const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7;
42
43#[cfg(any(target_os = "linux", target_os = "android"))]
44pub(crate) fn pre_main_hardening_linux() {
45 let ret_code = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
47 if ret_code != 0 {
48 eprintln!(
49 "ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
50 std::io::Error::last_os_error()
51 );
52 std::process::exit(PRCTL_FAILED_EXIT_CODE);
53 }
54
55 set_core_file_size_limit_to_zero();
57
58 remove_env_vars_with_prefix(b"LD_");
61}
62
63#[cfg(target_os = "linux")]
65pub fn disable_process_dumping() -> std::io::Result<()> {
66 let ret_code = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
67 if ret_code == 0 {
68 Ok(())
69 } else {
70 Err(std::io::Error::last_os_error())
71 }
72}
73
74#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
75pub(crate) fn pre_main_hardening_bsd() {
76 set_core_file_size_limit_to_zero();
78
79 remove_env_vars_with_prefix(b"LD_");
80}
81
82#[cfg(target_os = "macos")]
83pub(crate) fn pre_main_hardening_macos() {
84 let ret_code = unsafe { libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) };
86 if ret_code == -1 {
87 eprintln!(
88 "ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
89 std::io::Error::last_os_error()
90 );
91 std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
92 }
93
94 set_core_file_size_limit_to_zero();
96
97 remove_env_vars_with_prefix(b"DYLD_");
100}
101
102#[cfg(unix)]
103fn set_core_file_size_limit_to_zero() {
104 let rlim = libc::rlimit {
105 rlim_cur: 0,
106 rlim_max: 0,
107 };
108
109 let ret_code = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim) };
110 if ret_code != 0 {
111 eprintln!(
112 "ERROR: setrlimit(RLIMIT_CORE) failed: {}",
113 std::io::Error::last_os_error()
114 );
115 std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
116 }
117}
118
119#[cfg(windows)]
120pub(crate) fn pre_main_hardening_windows() {
121 }
123
124#[cfg(unix)]
125fn remove_env_vars_with_prefix(prefix: &[u8]) {
126 for key in env_keys_with_prefix(std::env::vars_os(), prefix) {
127 unsafe {
128 std::env::remove_var(key);
129 }
130 }
131}
132
133#[cfg(unix)]
134fn env_keys_with_prefix<I>(vars: I, prefix: &[u8]) -> Vec<OsString>
135where
136 I: IntoIterator<Item = (OsString, OsString)>,
137{
138 vars.into_iter()
139 .filter_map(|(key, _)| {
140 key.as_os_str()
141 .as_bytes()
142 .starts_with(prefix)
143 .then_some(key)
144 })
145 .collect()
146}
147
148#[cfg(all(test, unix))]
149mod tests {
150 use super::*;
151 use pretty_assertions::assert_eq;
152 use std::ffi::OsStr;
153 use std::os::unix::ffi::OsStrExt;
154 use std::os::unix::ffi::OsStringExt;
155
156 #[test]
157 fn env_keys_with_prefix_handles_non_utf8_entries() {
158 let non_utf8_key1 = OsStr::from_bytes(b"R\xD6DBURK").to_os_string();
160 assert!(non_utf8_key1.clone().into_string().is_err());
161 let non_utf8_key2 = OsString::from_vec(vec![b'L', b'D', b'_', 0xF0]);
162 assert!(non_utf8_key2.clone().into_string().is_err());
163
164 let non_utf8_value = OsString::from_vec(vec![0xF0, 0x9F, 0x92, 0xA9]);
165
166 let keys = env_keys_with_prefix(
167 vec![
168 (non_utf8_key1, non_utf8_value.clone()),
169 (non_utf8_key2.clone(), non_utf8_value),
170 ],
171 b"LD_",
172 );
173 assert_eq!(
174 keys,
175 vec![non_utf8_key2],
176 "non-UTF-8 env entries with LD_ prefix should be retained"
177 );
178 }
179
180 #[test]
181 fn env_keys_with_prefix_filters_only_matching_keys() {
182 let ld_test_var = OsStr::from_bytes(b"LD_TEST");
183 let vars = vec![
184 (OsString::from("PATH"), OsString::from("/usr/bin")),
185 (ld_test_var.to_os_string(), OsString::from("1")),
186 (OsString::from("DYLD_FOO"), OsString::from("bar")),
187 ];
188
189 let keys = env_keys_with_prefix(vars, b"LD_");
190 assert_eq!(keys.len(), 1);
191 assert_eq!(keys[0].as_os_str(), ld_test_var);
192 }
193}