Skip to content

Commit b6e06fd

Browse files
committed
Use symcheck to locate writeable+executable object files
From what I have been able to find, compilers that try to emit object files compatible with a GNU linker appear to add a `.note.GNU-stack` section if the stack should not be writeable (this section is empty). We never want a writeable stack, so extend the object file check to verify that object files with any executable sections also have this `.note.GNU-stack` section. This appears to match what is done by `scanelf` to emit `!WX` [2], which is the tool used to create the output in the issue. Closes: #183 [1]: https://github.com/gentoo/pax-utils/blob/9ef54b472e42ba2c5479fbd86b8be2275724b064/scanelf.c#L428-L512
1 parent aef8a1e commit b6e06fd

File tree

4 files changed

+86
-1
lines changed

4 files changed

+86
-1
lines changed

crates/symbol-check/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ serde_json = "1.0.140"
1111

1212
[features]
1313
wasm = ["object/wasm"]
14+
15+
[build-dependencies]
16+
cc = "1.2.25"

crates/symbol-check/build.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fn main() {
2+
let intermediates = cc::Build::new()
3+
.file("has_wx.c")
4+
.try_compile_intermediates();
5+
if let Ok(list) = intermediates {
6+
let [obj] = list.as_slice() else { panic!() };
7+
println!("cargo::rustc-env=HAS_WX_OBJ={}", obj.display());
8+
}
9+
}

crates/symbol-check/has_wx.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
void intermediate(void (*)(int, int), int);
2+
3+
int hack(int *array, int size) {
4+
void store (int index, int value) {
5+
array[index] = value;
6+
}
7+
8+
intermediate(store, size);
9+
}

crates/symbol-check/src/main.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ use std::io::{BufRead, BufReader};
77
use std::path::{Path, PathBuf};
88
use std::process::{Command, Stdio};
99

10+
use object::elf::SHF_EXECINSTR;
1011
use object::read::archive::{ArchiveFile, ArchiveMember};
1112
use object::{
12-
File as ObjFile, Object, ObjectSymbol, Symbol, SymbolKind, SymbolScope, SymbolSection,
13+
File as ObjFile, Object, ObjectSection, ObjectSymbol, SectionFlags, Symbol, SymbolKind,
14+
SymbolScope, SymbolSection,
1315
};
1416
use serde_json::Value;
1517

@@ -51,6 +53,7 @@ fn run_build_and_check(target: Option<&str>, args: &[&str]) {
5153

5254
verify_no_duplicates(&archive);
5355
verify_core_symbols(&archive);
56+
verify_no_exec_stack(&archive);
5457
}
5558
}
5659

@@ -249,6 +252,56 @@ fn verify_core_symbols(archive: &Archive) {
249252
println!(" success: no undefined references to core found");
250253
}
251254

255+
/// Check that all object files contain a section named `.note.GNU-stack`, indicating a
256+
/// nonexecutable stack.
257+
fn verify_no_exec_stack(archive: &Archive) {
258+
let mut problem_objfiles = Vec::new();
259+
260+
archive.for_each_object(|obj, member| {
261+
if obj_has_exe_stack(&obj) {
262+
problem_objfiles.push(String::from_utf8_lossy(member.name()).into_owned());
263+
}
264+
});
265+
266+
if !problem_objfiles.is_empty() {
267+
panic!(
268+
"the following archive members have executable sections but no \
269+
`.note.GNU-stack` section: {problem_objfiles:#?}"
270+
);
271+
}
272+
273+
println!(" success: no writeable-executable sections found");
274+
}
275+
276+
fn obj_has_exe_stack(obj: &ObjFile) -> bool {
277+
// Files other than elf likely do not use the same convention.
278+
if !matches!(obj, ObjFile::Elf32(_) | ObjFile::Elf64(_)) {
279+
return false;
280+
}
281+
282+
let mut has_exe_sections = false;
283+
for sec in obj.sections() {
284+
let SectionFlags::Elf { sh_flags } = sec.flags() else {
285+
unreachable!("only elf files are being checked");
286+
};
287+
288+
let exe = (sh_flags & SHF_EXECINSTR as u64) != 0;
289+
has_exe_sections |= exe;
290+
291+
// Located a GNU-stack section, nothing else to do
292+
if sec.name().unwrap_or_default() == ".note.GNU-stack" {
293+
return false;
294+
}
295+
}
296+
297+
// Ignore object files that have no executable sections, like rmeta
298+
if !has_exe_sections {
299+
return false;
300+
}
301+
302+
true
303+
}
304+
252305
/// Thin wrapper for owning data used by `object`.
253306
struct Archive {
254307
data: Vec<u8>,
@@ -286,3 +339,14 @@ impl Archive {
286339
});
287340
}
288341
}
342+
343+
#[test]
344+
fn check_obj() {
345+
let Some(p) = option_env!("HAS_WX_OBJ") else {
346+
return;
347+
};
348+
349+
let f = fs::read(p).unwrap();
350+
let obj = ObjFile::parse(f.as_slice()).unwrap();
351+
assert!(obj_has_exe_stack(&obj));
352+
}

0 commit comments

Comments
 (0)