From 3c50f28574c4c86b9399f4bffe79563feecd22a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Tue, 27 Sep 2022 20:27:41 +0200 Subject: [PATCH] ELF: count fortifiable functions Count the functions that can be fortified, by checking whether there exists a fortified version of it. The list for available fortified function is taken from glibc 2.36. Covert the fortification status into four states from just yes/no: Full, Partial, None and Undecidable. --- src/elf.rs | 184 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 168 insertions(+), 16 deletions(-) diff --git a/src/elf.rs b/src/elf.rs index f3a225c..da5d48f 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -84,6 +84,44 @@ impl fmt::Display for PIE { } } +/// Fortification status: `Full`, `Partial`, `None` or `Undecidable` +#[derive(Debug, Deserialize, Serialize)] +pub enum Fortify { + Full, + Partial, + None, + Undecidable, +} + +impl fmt::Display for Fortify { + #[cfg(not(feature = "color"))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:<11}", + match self { + Self::Full => "Full", + Self::Partial => "Partial", + Self::None => "None", + Self::Undecidable => "Undecidable", + } + ) + } + #[cfg(feature = "color")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:<11}", + match self { + Self::Full => "Full".green(), + Self::Partial => "Partial".bright_green(), + Self::None => "None".red(), + Self::Undecidable => "Undecidable".yellow(), + } + ) + } +} + /// Checksec result struct for ELF32/64 binaries /// /// **Example** @@ -111,10 +149,11 @@ pub struct CheckSecResults { /// Clang SafeStack (*CFLAGS=*`-fsanitize=safe-stack`) pub clang_safestack: bool, /// Fortify (*CFLAGS=*`-D_FORTIFY_SOURCE`) - pub fortify: bool, + pub fortify: Fortify, /// Fortified functions pub fortified: u32, - //fortifiable: Option>, + /// Fortifiable functions + pub fortifiable: u32, /// No Execute pub nx: bool, /// Position Inpendent Executable (*CFLAGS=*`-pie -fPIE`) @@ -129,12 +168,20 @@ pub struct CheckSecResults { impl CheckSecResults { #[must_use] pub fn parse(elf: &Elf) -> Self { + let (fortified, fortifiable) = elf.has_fortified(); + let fortify = match (fortified, fortifiable) { + (1.., 0) => Fortify::Full, + (1.., 1..) => Fortify::Partial, + (0, 1..) => Fortify::None, + (0, 0) => Fortify::Undecidable, + }; 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(), + fortify, + fortified, + fortifiable, nx: elf.has_nx(), pie: elf.has_pie(), relro: elf.has_relro(), @@ -151,12 +198,13 @@ impl fmt::Display for CheckSecResults { write!( f, "Canary: {} CFI: {} SafeStack: {} Fortify: {} Fortified: {:2} \ - NX: {} PIE: {} Relro: {} RPATH: {} RUNPATH: {}", + Fortifiable: {:2} NX: {} PIE: {} Relro: {} RPATH: {} RUNPATH: {}", self.canary, self.clang_cfi, self.clang_safestack, self.fortify, self.fortified, + self.fortifiable, self.nx, self.pie, self.relro, @@ -169,7 +217,7 @@ impl fmt::Display for CheckSecResults { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{} {} {} {} {} {} {} {} {} {:2} {} {} {} {} {} {} {} {} {} {}", + "{} {} {} {} {} {} {} {} {} {:2} {} {:2} {} {} {} {} {} {} {} {} {} {}", "Canary:".bold(), colorize_bool!(self.canary), "CFI:".bold(), @@ -177,9 +225,11 @@ impl fmt::Display for CheckSecResults { "SafeStack:".bold(), colorize_bool!(self.clang_safestack), "Fortify:".bold(), - colorize_bool!(self.fortify), + self.fortify, "Fortified:".bold(), self.fortified, + "Fortifiable:".bold(), + self.fortifiable, "NX:".bold(), colorize_bool!(self.nx), "PIE:".bold(), @@ -221,10 +271,8 @@ pub trait Properties { fn has_clang_safestack(&self) -> bool; /// check for symbols ending in `_chk` from dynstrtab fn has_fortify(&self) -> bool; - /// counts symbols ending in `_chk` from dynstrtab - fn has_fortified(&self) -> u32; - // requires running platform to be Linux - //fn has_fortifiable(&self) -> u32; + /// counts fortified and fortifiable symbols from dynstrtab + fn has_fortified(&self) -> (u32, u32); /// check `p_flags` of the `PT_GNU_STACK` ELF header fn has_nx(&self) -> bool; /// check `d_val` of `DT_FLAGS`/`DT_FLAGS_1` of the `PT_DYN ELF` header @@ -242,6 +290,94 @@ pub trait Properties { fn get_dynstr_by_tag(&self, tag: u64) -> Option; } +// readelf -s -W /lib/x86_64-linux-gnu/libc.so.6 | grep _chk +const FORTIFIABLE_FUNCTIONS: [&str; 79] = [ + "asprintf", + "confstr", + "dprintf", + "explicit_bzero", + "fdelt", + "fgets", + "fgets_unlocked", + "fgetws", + "fgetws_unlocked", + "fprintf", + "fread", + "fread_unlocked", + "fwprintf", + "getcwd", + "getdomainname", + "getgroups", + "gethostname", + "getlogin_r", + "gets", + "getwd", + "longjmp", + "mbsnrtowcs", + "mbsrtowcs", + "mbstowcs", + "memcpy", + "memmove", + "mempcpy", + "memset", + "obstack_printf", + "obstack_vprintf", + "poll", + "ppoll", + "pread64", + "pread", + "printf", + "ptsname_r", + "read", + "readlinkat", + "readlink", + "realpath", + "recv", + "recvfrom", + "snprintf", + "sprintf", + "stpcpy", + "stpncpy", + "strcat", + "strcpy", + "strncat", + "strncpy", + "swprintf", + "syslog", + "ttyname_r", + "vasprintf", + "vdprintf", + "vfprintf", + "vfwprintf", + "vprintf", + "vsnprintf", + "vsprintf", + "vswprintf", + "vsyslog", + "vwprintf", + "wcpcpy", + "wcpncpy", + "wcrtomb", + "wcscat", + "wcscpy", + "wcsncat", + "wcsncpy", + "wcsnrtombs", + "wcsrtombs", + "wcstombs", + "wctomb", + "wmemcpy", + "wmemmove", + "wmempcpy", + "wmemset", + "wprintf", +]; +/* + * TODO: static assert that FORTIFIABLE_FUNCTIONS is sorted + * unstable library feature 'is_sorted': + * const _: () = assert!(FORTIFIABLE_FUNCTIONS.is_sorted(), "must be sorted for binary search"); + */ + impl Properties for Elf<'_> { fn has_canary(&self) -> bool { for sym in &self.dynsyms { @@ -286,28 +422,44 @@ impl Properties for Elf<'_> { } fn has_fortify(&self) -> bool { for sym in &self.dynsyms { + if !sym.is_function() { + continue; + } if let Some(name) = self.dynstrtab.get_at(sym.st_name) { - if name.ends_with("_chk") { + if name.starts_with("__") && name.ends_with("_chk") { return true; } } } false } - fn has_fortified(&self) -> u32 { + fn has_fortified(&self) -> (u32, u32) { let mut fortified_count: u32 = 0; + let mut fortifiable_count: u32 = 0; for sym in &self.dynsyms { + if !sym.is_function() { + continue; + } if let Some(name) = self.dynstrtab.get_at(sym.st_name) { - if name.ends_with("_chk") { + if name.starts_with("__") && name.ends_with("_chk") { fortified_count += 1; + } else if FORTIFIABLE_FUNCTIONS.binary_search(&name).is_ok() { + fortifiable_count += 1; } } } - fortified_count + (fortified_count, fortifiable_count) } /* // requires running platform to be Linux - fn has_forifiable(&self) -> Option> { + fn has_fortifiable(&self) -> Vec { + self.dynsyms + .iter() + .filter(goblin::elf::Sym::is_function) + .filter_map(|sym| self.dynstrtab.get_at(sym.st_name)) + .filter(|func| FORTIFIABLE_FUNCTIONS.binary_search(func).is_ok()) + .map(std::string::ToString::to_string) + .collect() } */ fn has_nx(&self) -> bool {