#[cfg(feature = "color")]
use colored::Colorize;
use goblin::elf::dynamic::{
DF_1_NOW, DF_1_PIE, DF_BIND_NOW, DT_RPATH, DT_RUNPATH,
};
use goblin::elf::header::ET_DYN;
use goblin::elf::program_header::{PF_X, PT_GNU_RELRO, PT_GNU_STACK};
use goblin::elf::Elf;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
#[cfg(feature = "color")]
use crate::colorize_bool;
use crate::shared::{Rpath, VecRpath};
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum Relro {
None,
Partial,
Full,
}
impl fmt::Display for Relro {
#[cfg(not(feature = "color"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<7}",
match *self {
Self::None => "None",
Self::Partial => "Partial",
Self::Full => "Full",
}
)
}
#[cfg(feature = "color")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<7}",
match *self {
Self::None => "None".red(),
Self::Partial => "Partial".yellow(),
Self::Full => "Full".green(),
}
)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub enum PIE {
None,
DSO,
PIE,
}
impl fmt::Display for PIE {
#[cfg(not(feature = "color"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<4}",
match *self {
Self::None => "None",
Self::DSO => "DSO",
Self::PIE => "Full",
}
)
}
#[cfg(feature = "color")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<4}",
match *self {
Self::None => "None".red(),
Self::DSO => "DSO".yellow(),
Self::PIE => "Full".green(),
}
)
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Deserialize, Serialize)]
pub struct CheckSecResults {
pub canary: bool,
pub clang_cfi: bool,
pub clang_safestack: bool,
pub fortify: bool,
pub fortified: u32,
pub nx: bool,
pub pie: PIE,
pub relro: Relro,
pub rpath: VecRpath,
pub runpath: VecRpath,
}
impl CheckSecResults {
#[must_use]
pub fn parse(elf: &Elf) -> Self {
Self {
canary: elf.has_canary(),
clang_cfi: elf.has_clang_cfi(),
clang_safestack: elf.has_clang_safestack(),
fortify: elf.has_fortify(),
fortified: elf.has_fortified(),
nx: elf.has_nx(),
pie: elf.has_pie(),
relro: elf.has_relro(),
rpath: elf.has_rpath(),
runpath: elf.has_runpath(),
}
}
}
impl fmt::Display for CheckSecResults {
#[cfg(not(feature = "color"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Canary: {} CFI: {} SafeStack: {} Fortify: {} Fortified: {:2} \
NX: {} PIE: {} Relro: {} RPATH: {} RUNPATH: {}",
self.canary,
self.clang_cfi,
self.clang_safestack,
self.fortify,
self.fortified,
self.nx,
self.pie,
self.relro,
self.rpath,
self.runpath
)
}
#[cfg(feature = "color")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {} {} {} {} {} {} {} {:2} {} {} {} {} {} {} {} {} {} {}",
"Canary:".bold(),
colorize_bool!(self.canary),
"CFI:".bold(),
colorize_bool!(self.clang_cfi),
"SafeStack:".bold(),
colorize_bool!(self.clang_safestack),
"Fortify:".bold(),
colorize_bool!(self.fortify),
"Fortified:".bold(),
self.fortified,
"NX:".bold(),
colorize_bool!(self.nx),
"PIE:".bold(),
self.pie,
"Relro:".bold(),
self.relro,
"RPATH:".bold(),
self.rpath,
"RUNPATH:".bold(),
self.runpath
)
}
}
pub trait Properties {
fn has_canary(&self) -> bool;
fn has_clang_cfi(&self) -> bool;
fn has_clang_safestack(&self) -> bool;
fn has_fortify(&self) -> bool;
fn has_fortified(&self) -> u32;
fn has_nx(&self) -> bool;
fn has_pie(&self) -> PIE;
fn has_relro(&self) -> Relro;
fn has_rpath(&self) -> VecRpath;
fn has_runpath(&self) -> VecRpath;
fn get_dynstr_by_tag(&self, tag: u64) -> Option<String>;
}
impl Properties for Elf<'_> {
fn has_canary(&self) -> bool {
for sym in &self.dynsyms {
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
match name {
"__stack_chk_fail" | "__intel_security_cookie" => {
return true
}
_ => continue,
}
}
}
false
}
fn has_clang_cfi(&self) -> bool {
for sym in &self.syms {
if let Some(name) = self.strtab.get_at(sym.st_name) {
if name.contains(".cfi") {
return true;
}
}
}
for sym in &self.dynsyms {
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
if name.contains(".cfi") || name.contains("_cfi") {
return true;
}
}
}
false
}
fn has_clang_safestack(&self) -> bool {
for sym in &self.dynsyms {
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
if name == "__safestack_init" {
return true;
}
}
}
false
}
fn has_fortify(&self) -> bool {
for sym in &self.dynsyms {
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
if name.ends_with("_chk") {
return true;
}
}
}
false
}
fn has_fortified(&self) -> u32 {
let mut fortified_count: u32 = 0;
for sym in &self.dynsyms {
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
if name.ends_with("_chk") {
fortified_count += 1;
}
}
}
fortified_count
}
fn has_nx(&self) -> bool {
for header in &self.program_headers {
if header.p_type == PT_GNU_STACK {
if PF_X != header.p_flags & PF_X {
return true;
}
break;
}
}
false
}
fn has_pie(&self) -> PIE {
if self.header.e_type == ET_DYN {
if let Some(dynamic) = &self.dynamic {
if DF_1_PIE & dynamic.info.flags_1 == DF_1_PIE {
return PIE::PIE;
}
}
return PIE::DSO;
}
PIE::None
}
fn has_relro(&self) -> Relro {
for header in &self.program_headers {
if header.p_type == PT_GNU_RELRO {
if let Some(dynamic) = &self.dynamic {
if DF_BIND_NOW & dynamic.info.flags == DF_BIND_NOW
&& DF_1_NOW & dynamic.info.flags_1 == DF_1_NOW
{
return Relro::Full;
}
}
return Relro::Partial;
}
}
Relro::None
}
fn has_rpath(&self) -> VecRpath {
if self.dynamic.is_some() {
if let Some(name) = self.get_dynstr_by_tag(DT_RPATH) {
return VecRpath::new(
name.split(':')
.map(|path| Rpath::Yes(path.to_string()))
.collect(),
);
}
}
VecRpath::new(vec![Rpath::None])
}
fn has_runpath(&self) -> VecRpath {
if self.dynamic.is_some() {
if let Some(name) = self.get_dynstr_by_tag(DT_RUNPATH) {
return VecRpath::new(
name.split(':')
.map(|path| Rpath::Yes(path.to_string()))
.collect(),
);
}
}
VecRpath::new(vec![Rpath::None])
}
fn get_dynstr_by_tag(&self, tag: u64) -> Option<String> {
if let Some(dynamic) = &self.dynamic {
for dynamic in &dynamic.dyns {
if dynamic.d_tag == tag {
#[allow(clippy::cast_possible_truncation)]
if let Some(name) =
self.dynstrtab.get_at(dynamic.d_val as usize)
{
return Some(name.to_string());
}
}
}
}
None
}
}