#![allow(missing_docs)]
use std::path::PathBuf;
#[derive(Debug, Default, Clone)]
pub struct HardeningReport {
pub no_core_dumps: bool,
pub no_ptrace: bool,
pub mlocked: bool,
pub coredump_filter_safe: bool,
pub failures: Vec<String>,
}
pub fn apply_default_protections() -> HardeningReport {
let mut report = HardeningReport::default();
#[cfg(target_os = "linux")]
{
let rc = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
if rc == 0 {
report.no_core_dumps = true;
report.no_ptrace = true;
} else {
let err = std::io::Error::last_os_error();
report
.failures
.push(format!("prctl(PR_SET_DUMPABLE): {err}"));
}
}
#[cfg(target_os = "macos")]
{
const PT_DENY_ATTACH: libc::c_int = 31;
let rc = unsafe { libc::ptrace(PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) };
if rc == 0 {
report.no_ptrace = true;
report.no_core_dumps = true;
} else {
let err = std::io::Error::last_os_error();
report
.failures
.push(format!("ptrace(PT_DENY_ATTACH): {err}"));
}
}
#[cfg(target_os = "windows")]
{
report.no_core_dumps = true;
report.no_ptrace = true;
}
report
}
pub fn apply_lockdown_protections() -> HardeningReport {
let mut report = apply_default_protections();
#[cfg(target_os = "linux")]
{
let rc = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
if rc == 0 {
report.mlocked = true;
} else {
let err = std::io::Error::last_os_error();
report.failures.push(format!("mlockall: {err}"));
}
let rlim_zero = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
let rc = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim_zero) };
if rc != 0 {
let err = std::io::Error::last_os_error();
report
.failures
.push(format!("setrlimit(RLIMIT_CORE, 0): {err}"));
}
let filter = std::fs::read_to_string("/proc/self/coredump_filter")
.ok()
.and_then(|s| u32::from_str_radix(s.trim(), 16).ok());
let rlimit_blocked = rc == 0;
match filter {
Some(0) => report.coredump_filter_safe = true,
Some(_other) if rlimit_blocked => {
report.coredump_filter_safe = true;
}
Some(other) => report.failures.push(format!(
"/proc/self/coredump_filter = 0x{other:x} - anonymous pages would be dumped; \
RLIMIT_CORE could not be set to 0 either. Set ulimit -c 0 in the parent shell."
)),
None => {
if rlimit_blocked {
report.coredump_filter_safe = true;
} else {
report
.failures
.push("could not read /proc/self/coredump_filter".into());
}
}
}
}
#[cfg(not(target_os = "linux"))]
{
report.mlocked = false;
}
report
}
#[must_use]
pub fn lockdown_disk_cache_violations() -> Vec<PathBuf> {
let mut hits = Vec::new();
if let Some(cache_root) = dirs::cache_dir() {
let keyhog_root = cache_root.join("keyhog");
let has_findings_cache = std::fs::read_dir(&keyhog_root)
.map(|entries| {
entries.filter_map(Result::ok).any(|e| {
let name = e.file_name();
let name = name.to_string_lossy();
!(name.starts_with("hs-") && name.ends_with(".db"))
})
})
.unwrap_or(false);
if has_findings_cache {
hits.push(keyhog_root);
}
}
hits
}