@@ -7,9 +7,11 @@ use std::io::{BufRead, BufReader};
77use std:: path:: { Path , PathBuf } ;
88use std:: process:: { Command , Stdio } ;
99
10+ use object:: elf:: SHF_EXECINSTR ;
1011use object:: read:: archive:: { ArchiveFile , ArchiveMember } ;
1112use object:: {
12- File as ObjFile , Object , ObjectSection , ObjectSymbol , Symbol , SymbolKind , SymbolScope ,
13+ File as ObjFile , Object , ObjectSection , ObjectSymbol , SectionFlags , Symbol , SymbolKind ,
14+ SymbolScope ,
1315} ;
1416use serde_json:: Value ;
1517
@@ -24,6 +26,10 @@ Cargo will get invoked with `CARGO_ARGS` and the specified target. All output
2426`compiler_builtins*.rlib` files will be checked.
2527
2628If TARGET is not specified, the host target is used.
29+
30+ check ARCHIVE_PATHS ...
31+
32+ Run the same checks on the given set of paths, without invoking Cargo.
2733" ;
2834
2935fn main ( ) {
@@ -33,12 +39,14 @@ fn main() {
3339
3440 match & args_ref[ 1 ..] {
3541 [ "build-and-check" , target, "--" , args @ ..] if !args. is_empty ( ) => {
36- check_cargo_args ( args) ;
3742 run_build_and_check ( target, args) ;
3843 }
3944 [ "build-and-check" , "--" , args @ ..] if !args. is_empty ( ) => {
40- check_cargo_args ( args) ;
41- run_build_and_check ( & host_target ( ) , args) ;
45+ let target = & host_target ( ) ;
46+ run_build_and_check ( target, args) ;
47+ }
48+ [ "check" , paths @ ..] if !paths. is_empty ( ) => {
49+ check_paths ( paths) ;
4250 }
4351 _ => {
4452 println ! ( "{USAGE}" ) ;
@@ -47,25 +55,29 @@ fn main() {
4755 }
4856}
4957
50- /// Make sure `-- target` isn't passed to avoid confusion (since it should be proivded only once,
51- /// positionally).
52- fn check_cargo_args ( args : & [ & str ] ) {
58+ fn run_build_and_check ( target : & str , args : & [ & str ] ) {
59+ // Make sure `--target` isn't passed to avoid confusion (since it should be
60+ // proivded only once, positionally).
5361 for arg in args {
5462 assert ! (
5563 !arg. contains( "--target" ) ,
5664 "target must be passed positionally. {USAGE}"
5765 ) ;
5866 }
59- }
6067
61- fn run_build_and_check ( target : & str , args : & [ & str ] ) {
6268 let paths = exec_cargo_with_args ( target, args) ;
69+ check_paths ( & paths) ;
70+ }
71+
72+ fn check_paths < P : AsRef < Path > > ( paths : & [ P ] ) {
6373 for path in paths {
74+ let path = path. as_ref ( ) ;
6475 println ! ( "Checking {}" , path. display( ) ) ;
6576 let archive = Archive :: from_path ( & path) ;
6677
6778 verify_no_duplicates ( & archive) ;
6879 verify_core_symbols ( & archive) ;
80+ verify_no_exec_stack ( & archive) ;
6981 }
7082}
7183
@@ -288,6 +300,63 @@ fn verify_core_symbols(archive: &Archive) {
288300 println ! ( " success: no undefined references to core found" ) ;
289301}
290302
303+ /// Check that all object files contain a section named `.note.GNU-stack`, indicating a
304+ /// nonexecutable stack.
305+ ///
306+ /// Paraphrased from <https://www.man7.org/linux/man-pages/man1/ld.1.html>:
307+ ///
308+ /// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
309+ /// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
310+ /// - Without the section, behavior is target-specific and on some targets means an executable
311+ /// stack is required.
312+ fn verify_no_exec_stack ( archive : & Archive ) {
313+ let mut problem_objfiles = Vec :: new ( ) ;
314+
315+ archive. for_each_object ( |obj, member| {
316+ if obj_requires_exe_stack ( & obj) {
317+ problem_objfiles. push ( String :: from_utf8_lossy ( member. name ( ) ) . into_owned ( ) ) ;
318+ }
319+ } ) ;
320+
321+ if !problem_objfiles. is_empty ( ) {
322+ panic ! ( "the following archive members require an executable stack: {problem_objfiles:#?}" ) ;
323+ }
324+
325+ println ! ( " success: no writeable-executable sections found" ) ;
326+ }
327+
328+ fn obj_requires_exe_stack ( obj : & ObjFile ) -> bool {
329+ // Files other than elf likely do not use the same convention.
330+ if !matches ! ( obj, ObjFile :: Elf32 ( _) | ObjFile :: Elf64 ( _) ) {
331+ return false ;
332+ }
333+
334+ let mut has_exe_sections = false ;
335+ for sec in obj. sections ( ) {
336+ let SectionFlags :: Elf { sh_flags } = sec. flags ( ) else {
337+ unreachable ! ( "only elf files are being checked" ) ;
338+ } ;
339+
340+ let exe = ( sh_flags & SHF_EXECINSTR as u64 ) != 0 ;
341+
342+ // If the magic section is present, its exe bit tells us whether or not the object
343+ // file requires an executable stack.
344+ if sec. name ( ) . unwrap_or_default ( ) == ".note.GNU-stack" {
345+ return exe;
346+ }
347+
348+ // Otherwise, just keep track of whether or not we have exeuctable sections
349+ has_exe_sections |= exe;
350+ }
351+
352+ // Ignore object files that have no executable sections, like rmeta
353+ if !has_exe_sections {
354+ return false ;
355+ }
356+
357+ true
358+ }
359+
291360/// Thin wrapper for owning data used by `object`.
292361struct Archive {
293362 data : Vec < u8 > ,
@@ -325,3 +394,11 @@ impl Archive {
325394 } ) ;
326395 }
327396}
397+
398+ #[ test]
399+ fn check_obj ( ) {
400+ let p = option_env ! ( "HAS_WX_OBJ" ) . expect ( "this platform should support the wx test build" ) ;
401+ let f = fs:: read ( p) . unwrap ( ) ;
402+ let obj = ObjFile :: parse ( f. as_slice ( ) ) . unwrap ( ) ;
403+ assert ! ( obj_requires_exe_stack( & obj) ) ;
404+ }
0 commit comments