#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Hardening {
pub applied: Vec<&'static str>,
}
impl Hardening {
#[must_use]
pub fn is_empty(&self) -> bool {
self.applied.is_empty()
}
}
#[must_use]
pub fn harden_process() -> Hardening {
let mut hardening = Hardening::default();
#[cfg(target_os = "linux")]
linux::harden(&mut hardening);
#[cfg(target_os = "macos")]
macos::harden(&mut hardening);
#[cfg(windows)]
windows::harden(&mut hardening);
hardening
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn disable_core_dumps(hardening: &mut Hardening) {
use rustix::process::{Resource, Rlimit, setrlimit};
if setrlimit(
Resource::Core,
Rlimit {
current: Some(0),
maximum: Some(0),
},
)
.is_ok()
{
hardening.applied.push("core dumps disabled");
}
}
#[cfg(target_os = "linux")]
mod linux {
use rustix::process::{DumpableBehavior, set_dumpable_behavior};
pub fn harden(hardening: &mut super::Hardening) {
super::disable_core_dumps(hardening);
if set_dumpable_behavior(DumpableBehavior::NotDumpable).is_ok() {
hardening.applied.push("ptrace attach refused");
}
}
}
#[cfg(target_os = "macos")]
mod macos {
pub fn harden(hardening: &mut super::Hardening) {
super::disable_core_dumps(hardening);
let rc = unsafe { libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) };
if rc == 0 {
hardening.applied.push("ptrace attach refused");
}
}
}
#[cfg(windows)]
mod windows {
use windows_sys::Win32::System::Diagnostics::Debug::{
SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SetErrorMode,
};
pub fn harden(hardening: &mut super::Hardening) {
let previous = unsafe { SetErrorMode(0) };
unsafe {
SetErrorMode(previous | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS);
}
hardening.applied.push("crash dialog suppressed");
}
}
#[cfg(test)]
mod tests {
#[test]
fn harden_process_is_best_effort_and_repeatable() {
let _ = super::harden_process();
let _ = super::harden_process();
}
#[cfg(any(target_os = "linux", target_os = "macos", windows))]
#[test]
fn harden_applies_something_on_every_supported_platform() {
let hardening = super::harden_process();
assert!(!hardening.is_empty(), "{hardening:?}");
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn harden_disables_core_dumps() {
let hardening = super::harden_process();
assert!(
hardening.applied.contains(&"core dumps disabled"),
"{hardening:?}"
);
let lim = rustix::process::getrlimit(rustix::process::Resource::Core);
assert_eq!(lim.current, Some(0), "core-dump soft limit must be zero");
}
#[cfg(windows)]
#[test]
fn harden_suppresses_the_crash_dialog() {
use windows_sys::Win32::System::Diagnostics::Debug::{SEM_NOGPFAULTERRORBOX, SetErrorMode};
let _ = super::harden_process();
let mode = unsafe { SetErrorMode(0) };
unsafe {
SetErrorMode(mode);
}
assert_ne!(mode & SEM_NOGPFAULTERRORBOX, 0, "mode {mode:#x}");
}
}