Skip to content

Commit 3c50f28

Browse files
cgzonesetke
authored andcommitted
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.
1 parent 0a65a4b commit 3c50f28

File tree

1 file changed

+168
-16
lines changed

1 file changed

+168
-16
lines changed

src/elf.rs

Lines changed: 168 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,44 @@ impl fmt::Display for PIE {
8484
}
8585
}
8686

87+
/// Fortification status: `Full`, `Partial`, `None` or `Undecidable`
88+
#[derive(Debug, Deserialize, Serialize)]
89+
pub enum Fortify {
90+
Full,
91+
Partial,
92+
None,
93+
Undecidable,
94+
}
95+
96+
impl fmt::Display for Fortify {
97+
#[cfg(not(feature = "color"))]
98+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99+
write!(
100+
f,
101+
"{:<11}",
102+
match self {
103+
Self::Full => "Full",
104+
Self::Partial => "Partial",
105+
Self::None => "None",
106+
Self::Undecidable => "Undecidable",
107+
}
108+
)
109+
}
110+
#[cfg(feature = "color")]
111+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112+
write!(
113+
f,
114+
"{:<11}",
115+
match self {
116+
Self::Full => "Full".green(),
117+
Self::Partial => "Partial".bright_green(),
118+
Self::None => "None".red(),
119+
Self::Undecidable => "Undecidable".yellow(),
120+
}
121+
)
122+
}
123+
}
124+
87125
/// Checksec result struct for ELF32/64 binaries
88126
///
89127
/// **Example**
@@ -111,10 +149,11 @@ pub struct CheckSecResults {
111149
/// Clang SafeStack (*CFLAGS=*`-fsanitize=safe-stack`)
112150
pub clang_safestack: bool,
113151
/// Fortify (*CFLAGS=*`-D_FORTIFY_SOURCE`)
114-
pub fortify: bool,
152+
pub fortify: Fortify,
115153
/// Fortified functions
116154
pub fortified: u32,
117-
//fortifiable: Option<Vec<OsString>>,
155+
/// Fortifiable functions
156+
pub fortifiable: u32,
118157
/// No Execute
119158
pub nx: bool,
120159
/// Position Inpendent Executable (*CFLAGS=*`-pie -fPIE`)
@@ -129,12 +168,20 @@ pub struct CheckSecResults {
129168
impl CheckSecResults {
130169
#[must_use]
131170
pub fn parse(elf: &Elf) -> Self {
171+
let (fortified, fortifiable) = elf.has_fortified();
172+
let fortify = match (fortified, fortifiable) {
173+
(1.., 0) => Fortify::Full,
174+
(1.., 1..) => Fortify::Partial,
175+
(0, 1..) => Fortify::None,
176+
(0, 0) => Fortify::Undecidable,
177+
};
132178
Self {
133179
canary: elf.has_canary(),
134180
clang_cfi: elf.has_clang_cfi(),
135181
clang_safestack: elf.has_clang_safestack(),
136-
fortify: elf.has_fortify(),
137-
fortified: elf.has_fortified(),
182+
fortify,
183+
fortified,
184+
fortifiable,
138185
nx: elf.has_nx(),
139186
pie: elf.has_pie(),
140187
relro: elf.has_relro(),
@@ -151,12 +198,13 @@ impl fmt::Display for CheckSecResults {
151198
write!(
152199
f,
153200
"Canary: {} CFI: {} SafeStack: {} Fortify: {} Fortified: {:2} \
154-
NX: {} PIE: {} Relro: {} RPATH: {} RUNPATH: {}",
201+
Fortifiable: {:2} NX: {} PIE: {} Relro: {} RPATH: {} RUNPATH: {}",
155202
self.canary,
156203
self.clang_cfi,
157204
self.clang_safestack,
158205
self.fortify,
159206
self.fortified,
207+
self.fortifiable,
160208
self.nx,
161209
self.pie,
162210
self.relro,
@@ -169,17 +217,19 @@ impl fmt::Display for CheckSecResults {
169217
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170218
write!(
171219
f,
172-
"{} {} {} {} {} {} {} {} {} {:2} {} {} {} {} {} {} {} {} {} {}",
220+
"{} {} {} {} {} {} {} {} {} {:2} {} {:2} {} {} {} {} {} {} {} {} {} {}",
173221
"Canary:".bold(),
174222
colorize_bool!(self.canary),
175223
"CFI:".bold(),
176224
colorize_bool!(self.clang_cfi),
177225
"SafeStack:".bold(),
178226
colorize_bool!(self.clang_safestack),
179227
"Fortify:".bold(),
180-
colorize_bool!(self.fortify),
228+
self.fortify,
181229
"Fortified:".bold(),
182230
self.fortified,
231+
"Fortifiable:".bold(),
232+
self.fortifiable,
183233
"NX:".bold(),
184234
colorize_bool!(self.nx),
185235
"PIE:".bold(),
@@ -221,10 +271,8 @@ pub trait Properties {
221271
fn has_clang_safestack(&self) -> bool;
222272
/// check for symbols ending in `_chk` from dynstrtab
223273
fn has_fortify(&self) -> bool;
224-
/// counts symbols ending in `_chk` from dynstrtab
225-
fn has_fortified(&self) -> u32;
226-
// requires running platform to be Linux
227-
//fn has_fortifiable(&self) -> u32;
274+
/// counts fortified and fortifiable symbols from dynstrtab
275+
fn has_fortified(&self) -> (u32, u32);
228276
/// check `p_flags` of the `PT_GNU_STACK` ELF header
229277
fn has_nx(&self) -> bool;
230278
/// check `d_val` of `DT_FLAGS`/`DT_FLAGS_1` of the `PT_DYN ELF` header
@@ -242,6 +290,94 @@ pub trait Properties {
242290
fn get_dynstr_by_tag(&self, tag: u64) -> Option<String>;
243291
}
244292

293+
// readelf -s -W /lib/x86_64-linux-gnu/libc.so.6 | grep _chk
294+
const FORTIFIABLE_FUNCTIONS: [&str; 79] = [
295+
"asprintf",
296+
"confstr",
297+
"dprintf",
298+
"explicit_bzero",
299+
"fdelt",
300+
"fgets",
301+
"fgets_unlocked",
302+
"fgetws",
303+
"fgetws_unlocked",
304+
"fprintf",
305+
"fread",
306+
"fread_unlocked",
307+
"fwprintf",
308+
"getcwd",
309+
"getdomainname",
310+
"getgroups",
311+
"gethostname",
312+
"getlogin_r",
313+
"gets",
314+
"getwd",
315+
"longjmp",
316+
"mbsnrtowcs",
317+
"mbsrtowcs",
318+
"mbstowcs",
319+
"memcpy",
320+
"memmove",
321+
"mempcpy",
322+
"memset",
323+
"obstack_printf",
324+
"obstack_vprintf",
325+
"poll",
326+
"ppoll",
327+
"pread64",
328+
"pread",
329+
"printf",
330+
"ptsname_r",
331+
"read",
332+
"readlinkat",
333+
"readlink",
334+
"realpath",
335+
"recv",
336+
"recvfrom",
337+
"snprintf",
338+
"sprintf",
339+
"stpcpy",
340+
"stpncpy",
341+
"strcat",
342+
"strcpy",
343+
"strncat",
344+
"strncpy",
345+
"swprintf",
346+
"syslog",
347+
"ttyname_r",
348+
"vasprintf",
349+
"vdprintf",
350+
"vfprintf",
351+
"vfwprintf",
352+
"vprintf",
353+
"vsnprintf",
354+
"vsprintf",
355+
"vswprintf",
356+
"vsyslog",
357+
"vwprintf",
358+
"wcpcpy",
359+
"wcpncpy",
360+
"wcrtomb",
361+
"wcscat",
362+
"wcscpy",
363+
"wcsncat",
364+
"wcsncpy",
365+
"wcsnrtombs",
366+
"wcsrtombs",
367+
"wcstombs",
368+
"wctomb",
369+
"wmemcpy",
370+
"wmemmove",
371+
"wmempcpy",
372+
"wmemset",
373+
"wprintf",
374+
];
375+
/*
376+
* TODO: static assert that FORTIFIABLE_FUNCTIONS is sorted
377+
* unstable library feature 'is_sorted':
378+
* const _: () = assert!(FORTIFIABLE_FUNCTIONS.is_sorted(), "must be sorted for binary search");
379+
*/
380+
245381
impl Properties for Elf<'_> {
246382
fn has_canary(&self) -> bool {
247383
for sym in &self.dynsyms {
@@ -286,28 +422,44 @@ impl Properties for Elf<'_> {
286422
}
287423
fn has_fortify(&self) -> bool {
288424
for sym in &self.dynsyms {
425+
if !sym.is_function() {
426+
continue;
427+
}
289428
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
290-
if name.ends_with("_chk") {
429+
if name.starts_with("__") && name.ends_with("_chk") {
291430
return true;
292431
}
293432
}
294433
}
295434
false
296435
}
297-
fn has_fortified(&self) -> u32 {
436+
fn has_fortified(&self) -> (u32, u32) {
298437
let mut fortified_count: u32 = 0;
438+
let mut fortifiable_count: u32 = 0;
299439
for sym in &self.dynsyms {
440+
if !sym.is_function() {
441+
continue;
442+
}
300443
if let Some(name) = self.dynstrtab.get_at(sym.st_name) {
301-
if name.ends_with("_chk") {
444+
if name.starts_with("__") && name.ends_with("_chk") {
302445
fortified_count += 1;
446+
} else if FORTIFIABLE_FUNCTIONS.binary_search(&name).is_ok() {
447+
fortifiable_count += 1;
303448
}
304449
}
305450
}
306-
fortified_count
451+
(fortified_count, fortifiable_count)
307452
}
308453
/*
309454
// requires running platform to be Linux
310-
fn has_forifiable(&self) -> Option<Vec<OsString>> {
455+
fn has_fortifiable(&self) -> Vec<String> {
456+
self.dynsyms
457+
.iter()
458+
.filter(goblin::elf::Sym::is_function)
459+
.filter_map(|sym| self.dynstrtab.get_at(sym.st_name))
460+
.filter(|func| FORTIFIABLE_FUNCTIONS.binary_search(func).is_ok())
461+
.map(std::string::ToString::to_string)
462+
.collect()
311463
}
312464
*/
313465
fn has_nx(&self) -> bool {

0 commit comments

Comments
 (0)