Skip to content

Commit cc0131f

Browse files
authored
Linux/ELF: Add option to scan loaded shared libraries (#24)
Dynamic linking is the standard on Linux. Thus the hardening of dynamic loaded shared libraries can affect programs as well. Add a an option to scan all dynamic libraries for a binary/process. For binaries the list of libraries is taken from the ELF information and for processes all executable mapped memory regions backed up by a file are scanned.
1 parent 3d5a0fe commit cc0131f

File tree

13 files changed

+852
-184
lines changed

13 files changed

+852
-184
lines changed

Cargo.lock

Lines changed: 25 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ panic = 'abort' # Abort on panic
2828
clap = {version = "4.0.14", features = ["cargo"]}
2929
colored = {version = "2.0.0", optional = true}
3030
colored_json = {version = "3.0.1", optional = true}
31+
either = "1.8.1"
32+
glob = "0.3.0"
3133
goblin = "0.6.0"
3234
ignore = "0.4.18"
35+
itertools = "0.10.5"
3336
memmap2 = "0.5.7"
37+
rayon = "1.7.0"
3438
scroll = "0.11.0"
3539
scroll_derive = "0.11.0"
3640
serde = {version = "1.0.145", features = ["derive"]}

src/binary.rs

Lines changed: 16 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
#[cfg(feature = "color")]
22
use colored::Colorize;
33
use serde::{Deserialize, Serialize};
4-
#[cfg(all(feature = "color", not(target_os = "windows")))]
5-
use std::os::unix::fs::PermissionsExt;
6-
#[cfg(all(feature = "color", not(target_os = "windows")))]
7-
use std::path::Path;
84
use std::path::PathBuf;
95
use std::{fmt, usize};
106

@@ -15,7 +11,7 @@ use checksec::macho;
1511
#[cfg(feature = "pe")]
1612
use checksec::pe;
1713

18-
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
14+
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1915
pub enum BinType {
2016
#[cfg(feature = "elf")]
2117
Elf32,
@@ -69,7 +65,7 @@ impl fmt::Display for BinType {
6965
}
7066
}
7167

72-
#[derive(Debug, Deserialize, Serialize)]
68+
#[derive(Clone, Debug, Deserialize, Serialize)]
7369
pub enum BinSpecificProperties {
7470
#[cfg(feature = "elf")]
7571
Elf(elf::CheckSecResults),
@@ -91,83 +87,30 @@ impl fmt::Display for BinSpecificProperties {
9187
}
9288
}
9389

94-
#[derive(Debug, Deserialize, Serialize)]
95-
pub struct Binary {
90+
#[derive(Clone, Debug, Deserialize, Serialize)]
91+
pub struct Blob {
9692
pub binarytype: BinType,
97-
pub file: PathBuf,
9893
pub properties: BinSpecificProperties,
9994
}
100-
#[cfg(not(feature = "color"))]
101-
impl fmt::Display for Binary {
102-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103-
write!(
104-
f,
105-
"{}: | {} | File: {}",
106-
self.binarytype,
107-
self.properties,
108-
self.file.display()
109-
)
110-
}
111-
}
112-
#[cfg(feature = "color")]
113-
impl fmt::Display for Binary {
114-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115-
#[cfg(target_os = "windows")]
116-
let filefmt = self.file.display().to_string().bright_blue();
117-
#[cfg(not(target_os = "windows"))]
118-
let filefmt = match std::fs::metadata(&self.file) {
119-
Ok(md) => {
120-
#[cfg(target_os = "linux")]
121-
fn has_filecaps(file: &Path) -> bool {
122-
xattr::get(file, "security.capability")
123-
.unwrap_or(None)
124-
.is_some()
125-
}
126-
#[cfg(not(target_os = "linux"))]
127-
fn has_filecaps(_file: &Path) -> bool {
128-
false
129-
}
13095

131-
let mode = md.permissions().mode();
132-
if mode & 0o4000 == 0o4000 {
133-
self.file.display().to_string().white().on_red()
134-
} else if mode & 0o2000 == 0o2000 {
135-
self.file.display().to_string().black().on_yellow()
136-
} else if has_filecaps(&self.file) {
137-
self.file.display().to_string().black().on_blue()
138-
} else {
139-
self.file.display().to_string().bright_blue()
140-
}
141-
}
142-
Err(_) => self.file.display().to_string().bright_blue(),
143-
};
144-
145-
write!(
146-
f,
147-
"{}: | {} | {} {}",
148-
self.binarytype,
149-
self.properties,
150-
"File:".bold().underline(),
151-
filefmt
152-
)
153-
}
154-
}
155-
impl Binary {
156-
pub const fn new(
96+
impl Blob {
97+
pub fn new(
15798
binarytype: BinType,
158-
file: PathBuf,
15999
properties: BinSpecificProperties,
160100
) -> Self {
161-
Self { binarytype, file, properties }
101+
Self { binarytype, properties }
162102
}
163103
}
164104

165-
#[derive(Deserialize, Serialize)]
166-
pub struct Binaries {
167-
pub binaries: Vec<Binary>,
105+
#[derive(Clone, Deserialize, Serialize)]
106+
pub struct Binary {
107+
pub file: PathBuf,
108+
pub blobs: Vec<Blob>,
109+
pub libraries: Vec<Binary>,
168110
}
169-
impl Binaries {
170-
pub fn new(binaries: Vec<Binary>) -> Self {
171-
Self { binaries }
111+
112+
impl Binary {
113+
pub fn new(file: PathBuf, blobs: Vec<Blob>) -> Self {
114+
Self { file, blobs, libraries: vec![] }
172115
}
173116
}

src/elf.rs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Implements checksec for ELF binaries
22
#[cfg(feature = "color")]
33
use colored::Colorize;
4+
#[cfg(target_os = "linux")]
5+
use either::Either;
46
use goblin::elf::dynamic::{
57
DF_1_NOW, DF_1_PIE, DF_BIND_NOW, DT_RPATH, DT_RUNPATH,
68
};
@@ -9,13 +11,17 @@ use goblin::elf::program_header::{PF_X, PT_GNU_RELRO, PT_GNU_STACK};
911
use goblin::elf::Elf;
1012
use serde_derive::{Deserialize, Serialize};
1113
use std::fmt;
14+
#[cfg(target_os = "linux")]
15+
use std::path::{Path, PathBuf};
1216

1317
#[cfg(feature = "color")]
1418
use crate::colorize_bool;
19+
#[cfg(target_os = "linux")]
20+
use crate::ldso::{LdSoError, LdSoLookup};
1521
use crate::shared::{Rpath, VecRpath};
1622

1723
/// Relocation Read-Only mode: `None`, `Partial`, or `Full`
18-
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
24+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1925
pub enum Relro {
2026
None,
2127
Partial,
@@ -50,7 +56,7 @@ impl fmt::Display for Relro {
5056
}
5157

5258
/// Position Independent Executable mode: `None`, `DSO`, or `PIE`
53-
#[derive(Debug, Deserialize, Serialize)]
59+
#[derive(Clone, Debug, Deserialize, Serialize)]
5460
pub enum PIE {
5561
None,
5662
DSO,
@@ -85,7 +91,7 @@ impl fmt::Display for PIE {
8591
}
8692

8793
/// Fortification status: `Full`, `Partial`, `None` or `Undecidable`
88-
#[derive(Debug, Deserialize, Serialize)]
94+
#[derive(Clone, Debug, Deserialize, Serialize)]
8995
pub enum Fortify {
9096
Full,
9197
Partial,
@@ -140,7 +146,7 @@ impl fmt::Display for Fortify {
140146
/// }
141147
/// ```
142148
#[allow(clippy::struct_excessive_bools)]
143-
#[derive(Debug, Deserialize, Serialize)]
149+
#[derive(Clone, Debug, Deserialize, Serialize)]
144150
pub struct CheckSecResults {
145151
/// Stack Canary (*CFLAGS=*`-fstack-protector*`)
146152
pub canary: bool,
@@ -164,6 +170,8 @@ pub struct CheckSecResults {
164170
pub rpath: VecRpath,
165171
/// Run-time search path (`DT_RUNTIME`)
166172
pub runpath: VecRpath,
173+
/// Linked dynamic libraries
174+
pub dynlibs: Vec<String>,
167175
}
168176
impl CheckSecResults {
169177
#[must_use]
@@ -187,6 +195,11 @@ impl CheckSecResults {
187195
relro: elf.has_relro(),
188196
rpath: elf.has_rpath(),
189197
runpath: elf.has_runpath(),
198+
dynlibs: elf
199+
.libraries
200+
.iter()
201+
.map(std::string::ToString::to_string)
202+
.collect(),
190203
}
191204
}
192205
}
@@ -540,6 +553,75 @@ impl Properties for Elf<'_> {
540553
}
541554
}
542555

556+
#[cfg(target_os = "linux")]
557+
pub struct LibraryLookup {
558+
ldsolookup: LdSoLookup,
559+
}
560+
561+
#[cfg(target_os = "linux")]
562+
impl LibraryLookup {
563+
/// Initialize a library lookup handle for Elf files.
564+
///
565+
/// # Errors
566+
/// Will fail if the ld.so.conf configuration can not be read or has an
567+
/// invalid format.
568+
pub fn new() -> Result<Self, LdSoError> {
569+
Ok(Self { ldsolookup: LdSoLookup::gen_lookup_dirs()? })
570+
}
571+
572+
#[must_use]
573+
pub fn lookup(
574+
&self,
575+
binarypath: &Path,
576+
rpath: &VecRpath,
577+
runpath: &VecRpath,
578+
libfilename: &str,
579+
) -> Option<PathBuf> {
580+
let parentbinpath = if let Some(parent) = binarypath.parent() {
581+
parent.to_str()
582+
} else {
583+
None
584+
};
585+
586+
for rpath in rpath.iter().filter_map(|rpath| match rpath {
587+
Rpath::YesRW(ref str) | Rpath::Yes(ref str) => Some(str),
588+
Rpath::None => None,
589+
}) {
590+
let rpath = if let Some(p) = parentbinpath {
591+
Either::Left(rpath.replace("$ORIGIN", p))
592+
} else {
593+
Either::Right(rpath)
594+
};
595+
let path = Path::new(&rpath).join(libfilename);
596+
if path.is_file() {
597+
return Some(path);
598+
}
599+
}
600+
601+
if let Some(path) = self.ldsolookup.search(libfilename) {
602+
return Some(path);
603+
}
604+
605+
for runpath in runpath.iter().filter_map(|rpath| match rpath {
606+
Rpath::YesRW(ref str) | Rpath::Yes(ref str) => Some(str),
607+
608+
Rpath::None => None,
609+
}) {
610+
let runpath = if let Some(p) = parentbinpath {
611+
Either::Left(runpath.replace("$ORIGIN", p))
612+
} else {
613+
Either::Right(runpath)
614+
};
615+
let path = Path::new(&runpath).join(libfilename);
616+
if path.is_file() {
617+
return Some(path);
618+
}
619+
}
620+
621+
None
622+
}
623+
}
624+
543625
#[cfg(test)]
544626
mod tests {
545627
use super::FORTIFIABLE_FUNCTIONS;

0 commit comments

Comments
 (0)