Skip to content

Commit ada5714

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. Fixes: #183 [1]: https://github.com/gentoo/pax-utils/blob/9ef54b472e42ba2c5479fbd86b8be2275724b064/scanelf.c#L428-L512
1 parent a121a80 commit ada5714

File tree

1 file changed

+93
-16
lines changed

1 file changed

+93
-16
lines changed

crates/symbol-check/src/main.rs

Lines changed: 93 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ 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};
11-
use object::{Object, ObjectSymbol, Symbol, SymbolKind, SymbolScope, SymbolSection};
12+
use object::{
13+
Object, ObjectSection, ObjectSymbol, SectionFlags, Symbol, SymbolKind, SymbolScope,
14+
SymbolSection,
15+
};
1216
use serde_json::Value;
1317

1418
const CHECK_LIBRARIES: &[&str] = &["compiler_builtins", "builtins_test_intrinsics"];
@@ -32,8 +36,11 @@ fn main() {
3236
let paths = exec_cargo_with_args(rest);
3337
for path in paths {
3438
println!("Checking {}", path.display());
35-
verify_no_duplicates(&path);
36-
verify_core_symbols(&path);
39+
let archive = Archive::from_path(&path);
40+
41+
verify_no_duplicates(&archive);
42+
verify_core_symbols(&archive);
43+
verify_no_exec_stack(&archive);
3744
}
3845
}
3946
_ => {
@@ -133,12 +140,12 @@ impl SymInfo {
133140
/// Note that this will also locate cases where a symbol is weakly defined in more than one place.
134141
/// Technically there are no linker errors that will come from this, but it keeps our binary more
135142
/// straightforward and saves some distribution size.
136-
fn verify_no_duplicates(path: &Path) {
143+
fn verify_no_duplicates(archive: &Archive) {
137144
let mut syms = BTreeMap::<String, SymInfo>::new();
138145
let mut dups = Vec::new();
139146
let mut found_any = false;
140147

141-
for_each_symbol(path, |symbol, member| {
148+
archive.for_each_symbol(|symbol, member| {
142149
// Only check defined globals
143150
if !symbol.is_global() || symbol.is_undefined() {
144151
return;
@@ -185,12 +192,12 @@ fn verify_no_duplicates(path: &Path) {
185192
}
186193

187194
/// Ensure that there are no references to symbols from `core` that aren't also (somehow) defined.
188-
fn verify_core_symbols(path: &Path) {
195+
fn verify_core_symbols(archive: &Archive) {
189196
let mut defined = BTreeSet::new();
190197
let mut undefined = Vec::new();
191198
let mut has_symbols = false;
192199

193-
for_each_symbol(path, |symbol, member| {
200+
archive.for_each_symbol(|symbol, member| {
194201
has_symbols = true;
195202

196203
// Find only symbols from `core`
@@ -219,14 +226,84 @@ fn verify_core_symbols(path: &Path) {
219226
println!(" success: no undefined references to core found");
220227
}
221228

222-
/// For a given archive path, do something with each symbol.
223-
fn for_each_symbol(path: &Path, mut f: impl FnMut(Symbol, &ArchiveMember)) {
224-
let data = fs::read(path).expect("reading file failed");
225-
let archive = ArchiveFile::parse(data.as_slice()).expect("archive parse failed");
226-
for member in archive.members() {
227-
let member = member.expect("failed to access member");
228-
let obj_data = member.data(&*data).expect("failed to access object");
229-
let obj = object::File::parse(obj_data).expect("failed to parse object");
230-
obj.symbols().for_each(|sym| f(sym, &member));
229+
/// Check that all object files contain a section named `.note.GNU-stack`, indicating a
230+
/// nonexecutable stack.
231+
fn verify_no_exec_stack(archive: &Archive) {
232+
let mut problem_objfiles = Vec::new();
233+
234+
archive.for_each_object(|obj, member| {
235+
// Files other than elf likely do not use the same convention.
236+
if !matches!(obj, object::File::Elf32(_) | object::File::Elf64(_)) {
237+
return;
238+
}
239+
240+
let mut has_exe_sections = false;
241+
for sec in obj.sections() {
242+
let SectionFlags::Elf { sh_flags } = sec.flags() else {
243+
unreachable!("only elf files are being checked");
244+
};
245+
246+
let exe = (sh_flags & SHF_EXECINSTR as u64) != 0;
247+
has_exe_sections |= exe;
248+
249+
// Located a GNU-stack section, nothing else to do
250+
if sec.name().unwrap_or_default() == ".note.GNU-stack" {
251+
return;
252+
}
253+
}
254+
255+
// Ignore object files that have no executable sections, like rmeta
256+
if !has_exe_sections {
257+
return;
258+
}
259+
260+
problem_objfiles.push(String::from_utf8_lossy(member.name()).into_owned());
261+
});
262+
263+
if !problem_objfiles.is_empty() {
264+
panic!(
265+
"the following archive members have executable sections but no \
266+
`.note.GNU-stack` section: {problem_objfiles:#?}"
267+
);
268+
}
269+
270+
println!(" success: no writeable-executable sections found");
271+
}
272+
273+
/// Thin wrapper for owning data used by `object`.
274+
struct Archive {
275+
data: Vec<u8>,
276+
}
277+
278+
impl Archive {
279+
fn from_path(path: &Path) -> Self {
280+
Self {
281+
data: fs::read(path).expect("reading file failed"),
282+
}
283+
}
284+
285+
fn file(&self) -> ArchiveFile {
286+
ArchiveFile::parse(self.data.as_slice()).expect("archive parse failed")
287+
}
288+
289+
/// For a given archive, do something with each object file.
290+
fn for_each_object(&self, mut f: impl FnMut(object::File, &ArchiveMember)) {
291+
let archive = self.file();
292+
293+
for member in archive.members() {
294+
let member = member.expect("failed to access member");
295+
let obj_data = member
296+
.data(self.data.as_slice())
297+
.expect("failed to access object");
298+
let obj = object::File::parse(obj_data).expect("failed to parse object");
299+
f(obj, &member);
300+
}
301+
}
302+
303+
/// For a given archive, do something with each symbol.
304+
fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ArchiveMember)) {
305+
self.for_each_object(|obj, member| {
306+
obj.symbols().for_each(|sym| f(sym, member));
307+
});
231308
}
232309
}

0 commit comments

Comments
 (0)