use std::fmt;
use std::path::Path;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecurityStatus {
Full,
Partial,
None,
}
impl fmt::Display for SecurityStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SecurityStatus::Full => write!(f, "Full"),
SecurityStatus::Partial => write!(f, "Partial"),
SecurityStatus::None => write!(f, "None"),
}
}
}
#[derive(Debug, Clone)]
pub struct ChecksecResult {
pub relro: SecurityStatus,
pub canary: bool,
pub nx: bool,
pub pie: bool,
pub fortify: bool,
pub runpath: bool,
pub rpath: bool,
}
pub fn checksec(path: &Path) -> Result<ChecksecResult> {
let data = std::fs::read(path)
.map_err(|e| Error::Other(format!("read ELF '{}': {}", path.display(), e)))?;
checksec_bytes(&data)
}
pub fn checksec_bytes(data: &[u8]) -> Result<ChecksecResult> {
let elf =
goblin::elf::Elf::parse(data).map_err(|e| Error::Other(format!("parse ELF: {}", e)))?;
let has_relro = elf
.program_headers
.iter()
.any(|ph| ph.p_type == goblin::elf::program_header::PT_GNU_RELRO);
let has_bind_now = elf
.dynamic
.as_ref()
.map(|d| {
d.dyns.iter().any(|dyn_entry| {
dyn_entry.d_tag == goblin::elf::dynamic::DT_BIND_NOW
|| (dyn_entry.d_tag == goblin::elf::dynamic::DT_FLAGS
&& dyn_entry.d_val & goblin::elf::dynamic::DF_BIND_NOW != 0)
|| (dyn_entry.d_tag == goblin::elf::dynamic::DT_FLAGS_1
&& dyn_entry.d_val & goblin::elf::dynamic::DF_1_NOW != 0)
})
})
.unwrap_or(false);
let relro = if has_relro && has_bind_now {
SecurityStatus::Full
} else if has_relro {
SecurityStatus::Partial
} else {
SecurityStatus::None
};
let nx = elf
.program_headers
.iter()
.find(|ph| ph.p_type == goblin::elf::program_header::PT_GNU_STACK)
.map(|ph| ph.p_flags & goblin::elf::program_header::PF_X == 0)
.unwrap_or(true);
let pie = elf.header.e_type == goblin::elf::header::ET_DYN;
let canary = elf.dynsyms.iter().any(|sym| {
elf.dynstrtab
.get_at(sym.st_name)
.map(|name| name == "__stack_chk_fail" || name == "__stack_chk_guard")
.unwrap_or(false)
});
let fortify = elf.dynsyms.iter().any(|sym| {
elf.dynstrtab
.get_at(sym.st_name)
.map(|name| name.ends_with("_chk") || name.contains("_chk@"))
.unwrap_or(false)
});
let (rpath, runpath) = elf
.dynamic
.as_ref()
.map(|d| {
let has_rpath = d
.dyns
.iter()
.any(|e| e.d_tag == goblin::elf::dynamic::DT_RPATH);
let has_runpath = d
.dyns
.iter()
.any(|e| e.d_tag == goblin::elf::dynamic::DT_RUNPATH);
(has_rpath, has_runpath)
})
.unwrap_or((false, false));
Ok(ChecksecResult {
relro,
canary,
nx,
pie,
fortify,
rpath,
runpath,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn security_status_display() {
assert_eq!(format!("{}", SecurityStatus::Full), "Full");
assert_eq!(format!("{}", SecurityStatus::Partial), "Partial");
assert_eq!(format!("{}", SecurityStatus::None), "None");
}
#[test]
fn checksec_result_debug() {
let result = ChecksecResult {
relro: SecurityStatus::Full,
canary: true,
nx: true,
pie: true,
fortify: false,
rpath: false,
runpath: false,
};
let _ = format!("{:?}", result);
}
}