Skip to content

Commit

Permalink
ELF: count fortifiable functions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cgzones authored and etke committed Oct 8, 2022
1 parent 0a65a4b commit 3c50f28
Showing 1 changed file with 168 additions and 16 deletions.
184 changes: 168 additions & 16 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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<Vec<OsString>>,
/// Fortifiable functions
pub fortifiable: u32,
/// No Execute
pub nx: bool,
/// Position Inpendent Executable (*CFLAGS=*`-pie -fPIE`)
Expand All @@ -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(),
Expand All @@ -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,
Expand All @@ -169,17 +217,19 @@ 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(),
colorize_bool!(self.clang_cfi),
"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(),
Expand Down Expand Up @@ -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
Expand All @@ -242,6 +290,94 @@ pub trait Properties {
fn get_dynstr_by_tag(&self, tag: u64) -> Option<String>;
}

// 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 {
Expand Down Expand Up @@ -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<Vec<OsString>> {
fn has_fortifiable(&self) -> Vec<String> {
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 {
Expand Down

0 comments on commit 3c50f28

Please sign in to comment.