@@ -7,9 +7,11 @@ use std::io::{BufRead, BufReader};
7
7
use std:: path:: { Path , PathBuf } ;
8
8
use std:: process:: { Command , Stdio } ;
9
9
10
+ use object:: elf:: SHF_EXECINSTR ;
10
11
use object:: read:: archive:: { ArchiveFile , ArchiveMember } ;
11
12
use object:: {
12
- File as ObjFile , Object , ObjectSection , ObjectSymbol , Symbol , SymbolKind , SymbolScope ,
13
+ File as ObjFile , Object , ObjectSection , ObjectSymbol , SectionFlags , Symbol , SymbolKind ,
14
+ SymbolScope ,
13
15
} ;
14
16
use serde_json:: Value ;
15
17
@@ -24,6 +26,10 @@ Cargo will get invoked with `CARGO_ARGS` and the specified target. All output
24
26
`compiler_builtins*.rlib` files will be checked.
25
27
26
28
If 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.
27
33
" ;
28
34
29
35
fn main ( ) {
@@ -33,12 +39,14 @@ fn main() {
33
39
34
40
match & args_ref[ 1 ..] {
35
41
[ "build-and-check" , target, "--" , args @ ..] if !args. is_empty ( ) => {
36
- check_cargo_args ( args) ;
37
42
run_build_and_check ( target, args) ;
38
43
}
39
44
[ "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) ;
42
50
}
43
51
_ => {
44
52
println ! ( "{USAGE}" ) ;
@@ -47,25 +55,29 @@ fn main() {
47
55
}
48
56
}
49
57
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).
53
61
for arg in args {
54
62
assert ! (
55
63
!arg. contains( "--target" ) ,
56
64
"target must be passed positionally. {USAGE}"
57
65
) ;
58
66
}
59
- }
60
67
61
- fn run_build_and_check ( target : & str , args : & [ & str ] ) {
62
68
let paths = exec_cargo_with_args ( target, args) ;
69
+ check_paths ( & paths) ;
70
+ }
71
+
72
+ fn check_paths < P : AsRef < Path > > ( paths : & [ P ] ) {
63
73
for path in paths {
74
+ let path = path. as_ref ( ) ;
64
75
println ! ( "Checking {}" , path. display( ) ) ;
65
76
let archive = Archive :: from_path ( & path) ;
66
77
67
78
verify_no_duplicates ( & archive) ;
68
79
verify_core_symbols ( & archive) ;
80
+ verify_no_exec_stack ( & archive) ;
69
81
}
70
82
}
71
83
@@ -288,6 +300,63 @@ fn verify_core_symbols(archive: &Archive) {
288
300
println ! ( " success: no undefined references to core found" ) ;
289
301
}
290
302
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
+
291
360
/// Thin wrapper for owning data used by `object`.
292
361
struct Archive {
293
362
data : Vec < u8 > ,
@@ -325,3 +394,11 @@ impl Archive {
325
394
} ) ;
326
395
}
327
396
}
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