Skip to content

Commit 4b6d8f0

Browse files
committed
validate ELF hardening properties in distributions
Extend validate-distribution to reject * non-PIE executables * writable and executable load segments * missing RELRO * lazy symbol binding * text relocations. Preserve the existing executable-stack validation and skip dynamic binding checks when validating static binaries.
1 parent 973bf57 commit 4b6d8f0

1 file changed

Lines changed: 69 additions & 12 deletions

File tree

src/validation.rs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use {
1010
object::{
1111
Architecture, Endianness, FileKind, Object, SectionIndex, SymbolScope,
1212
elf::{
13-
ET_DYN, ET_EXEC, FileHeader32, FileHeader64, PF_X, PT_GNU_STACK, SHN_UNDEF, STB_GLOBAL,
14-
STB_WEAK, STV_DEFAULT, STV_HIDDEN,
13+
DF_1_NOW, DF_BIND_NOW, DF_TEXTREL, DT_BIND_NOW, DT_FLAGS, DT_FLAGS_1, DT_TEXTREL,
14+
ET_DYN, ET_EXEC, FileHeader32, FileHeader64, PF_W, PF_X, PT_GNU_RELRO, PT_GNU_STACK,
15+
PT_LOAD, SHN_UNDEF, STB_GLOBAL, STB_WEAK, STV_DEFAULT, STV_HIDDEN,
1516
},
1617
macho::{LC_CODE_SIGNATURE, MH_OBJECT, MH_TWOLEVEL, MachHeader32, MachHeader64},
1718
read::{
@@ -1027,9 +1028,14 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
10271028

10281029
let versions = sections.versions(endian, data)?;
10291030

1031+
let mut has_dynamic_section = false;
1032+
let mut has_bind_now = false;
1033+
let mut has_text_relocations = false;
1034+
10301035
for (section_index, section) in sections.iter().enumerate() {
10311036
// Dynamic sections defined needed libraries, which we validate.
10321037
if let Some((entries, index)) = section.dynamic(endian, data)? {
1038+
has_dynamic_section = true;
10331039
let strings = sections.strings(endian, data, index).unwrap_or_default();
10341040

10351041
for entry in entries {
@@ -1087,6 +1093,20 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
10871093
}
10881094
}
10891095
}
1096+
1097+
match entry.tag32(endian) {
1098+
Some(DT_BIND_NOW) => has_bind_now = true,
1099+
Some(DT_FLAGS) => {
1100+
let flags = entry.val32(endian).unwrap_or_default();
1101+
has_bind_now |= flags & DF_BIND_NOW != 0;
1102+
has_text_relocations |= flags & DF_TEXTREL != 0;
1103+
}
1104+
Some(DT_FLAGS_1) => {
1105+
has_bind_now |= entry.val32(endian).unwrap_or_default() & DF_1_NOW != 0;
1106+
}
1107+
Some(DT_TEXTREL) => has_text_relocations = true,
1108+
_ => {}
1109+
}
10901110
}
10911111
}
10921112

@@ -1180,8 +1200,9 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
11801200
}
11811201
}
11821202

1183-
// Verify that objects are not requesting an executable stack. For backwards compatibility,
1184-
// Linux (the kernel when loading an executable, and glibc when loading a shared library)
1203+
// Verify that objects are not requesting an executable stack and that other hardening
1204+
// properties exist in the linked outputs. For backwards compatibility, Linux (the kernel
1205+
// when loading an executable, and glibc when loading a shared library)
11851206
// assumes you need an executable stack unless you request otherwise. In linked outputs
11861207
// (executables and shared libraries) this is in the program header: the flags of a
11871208
// PT_GNU_STACK entry specify stack permissions, and the default if unspecified is RWX. In
@@ -1193,16 +1214,35 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
11931214
// .note.GNU-stack section, which we are overriding with -Wl,-z,noexecstack.
11941215

11951216
if matches!(elf.e_type(endian), ET_EXEC | ET_DYN) {
1217+
if elf.e_type(endian) == ET_EXEC {
1218+
context.errors.push(format!(
1219+
"{} is not position-independent (ELF type is ET_EXEC)",
1220+
path.display(),
1221+
));
1222+
}
1223+
11961224
let mut found_pt_gnu_stack = false;
1225+
let mut found_pt_gnu_relro = false;
11971226
for phdr in elf.program_headers(endian, data)? {
1198-
if phdr.p_type(endian) != PT_GNU_STACK {
1199-
continue;
1200-
}
1201-
found_pt_gnu_stack = true;
1202-
if (phdr.p_flags(endian) & PF_X) != 0 {
1203-
context
1204-
.errors
1205-
.push(format!("{} requests executable stack", path.display()));
1227+
let flags = phdr.p_flags(endian);
1228+
1229+
match phdr.p_type(endian) {
1230+
PT_GNU_STACK => {
1231+
found_pt_gnu_stack = true;
1232+
if flags & PF_X != 0 {
1233+
context
1234+
.errors
1235+
.push(format!("{} requests executable stack", path.display()));
1236+
}
1237+
}
1238+
PT_GNU_RELRO => found_pt_gnu_relro = true,
1239+
PT_LOAD if flags & (PF_W | PF_X) == (PF_W | PF_X) => {
1240+
context.errors.push(format!(
1241+
"{} has a writable and executable PT_LOAD segment",
1242+
path.display(),
1243+
));
1244+
}
1245+
_ => {}
12061246
}
12071247
}
12081248
if !found_pt_gnu_stack {
@@ -1211,6 +1251,23 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
12111251
path.display(),
12121252
));
12131253
}
1254+
if !found_pt_gnu_relro {
1255+
context.errors.push(format!(
1256+
"{} is missing a PT_GNU_RELRO segment",
1257+
path.display(),
1258+
));
1259+
}
1260+
if has_dynamic_section && !has_bind_now {
1261+
context.errors.push(format!(
1262+
"{} does not request immediate symbol binding",
1263+
path.display(),
1264+
));
1265+
}
1266+
if has_text_relocations {
1267+
context
1268+
.errors
1269+
.push(format!("{} contains text relocations", path.display()));
1270+
}
12141271
}
12151272

12161273
Ok(())

0 commit comments

Comments
 (0)