diff --git a/crates/symbol-check/src/main.rs b/crates/symbol-check/src/main.rs index e15522d22..135019e5f 100644 --- a/crates/symbol-check/src/main.rs +++ b/crates/symbol-check/src/main.rs @@ -58,6 +58,7 @@ fn main() { "The binaries will not be checked for executable stacks. Used for embedded targets which \ don't set `.note.GNU-stack` since there is no protection.", ); + opts.optflag("", "no-visibility", "Don't check visibility."); let print_usage_and_exit = |code: i32| -> ! { eprintln!("{}", opts.usage(USAGE)); @@ -74,6 +75,7 @@ fn main() { } let no_os_target = m.opt_present("no-os"); + let check_visibility = !m.opt_present("no-visibility"); let free_args = m.free.iter().map(String::as_str).collect::>(); for arg in &free_args { assert!( @@ -85,18 +87,18 @@ fn main() { if m.opt_present("build-and-check") { let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); let paths = exec_cargo_with_args(&target, &free_args); - check_paths(&paths, no_os_target); + check_paths(&paths, no_os_target, check_visibility); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); } - check_paths(&free_args, no_os_target); + check_paths(&free_args, no_os_target, check_visibility); } else { print_usage_and_exit(1); } } -fn check_paths>(paths: &[P], no_os_target: bool) { +fn check_paths>(paths: &[P], no_os_target: bool, check_visibility: bool) { for path in paths { let path = path.as_ref(); println!("Checking {}", path.display()); @@ -105,6 +107,9 @@ fn check_paths>(paths: &[P], no_os_target: bool) { verify_no_duplicates(&archive); verify_core_symbols(&archive); verify_no_exec_stack(&archive, no_os_target); + if check_visibility { + verify_hidden_visibility(&archive); + } } } @@ -329,6 +334,38 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core found"); } +/// Check for symbols with default visibility. +fn verify_hidden_visibility(archive: &BinFile) { + let mut visible = Vec::new(); + let mut found_any = false; + + archive.for_each_symbol(|symbol, obj, member| { + // Only check defined globals. + if !symbol.is_global() || symbol.is_undefined() { + return; + } + + let sym = SymInfo::new(&symbol, obj, member); + if sym.scope == SymbolScope::Dynamic { + visible.push(sym); + } + + found_any = true + }); + + if archive.has_symbol_tables() { + assert!(found_any, "no symbols found"); + } + + if !visible.is_empty() { + visible.sort_unstable_by(|a, b| a.name.cmp(&b.name)); + let num = visible.len(); + panic!("found {num:#?} visible symbols: {visible:#?}"); + } + + println!(" success: no visible symbols found"); +} + /// Reasons a binary is considered to have an executable stack. enum ExeStack { MissingGnuStackSec, diff --git a/crates/symbol-check/tests/all.rs b/crates/symbol-check/tests/all.rs index 6dc34c3e3..cfc8641ac 100644 --- a/crates/symbol-check/tests/all.rs +++ b/crates/symbol-check/tests/all.rs @@ -75,6 +75,20 @@ fn test_core_symbols() { .stderr_contains("from_utf8"); } +#[test] +fn test_visible_symbols() { + let t = TestTarget::from_env(); + if t.is_windows() { + eprintln!("windows does not have visibility, skipping"); + return; + } + let dir = tempdir().unwrap(); + let lib_out = dir.path().join("libfoo.rlib"); + t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); + let assert = t.symcheck_exe().arg(&lib_out).assert(); + assert.failure().stderr_contains("found 1 visible symbols"); // good is visible. +} + mod exe_stack { use super::*; @@ -95,7 +109,7 @@ mod exe_stack { let objs = t.cc_build().file(src).out_dir(&dir).compile_intermediates(); let [obj] = objs.as_slice() else { panic!() }; - let assert = t.symcheck_exe().arg(obj).assert(); + let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); if t.is_ppc64be() || t.no_os() || t.binary_obj_format() != BinaryFormat::Elf { // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf @@ -127,7 +141,7 @@ mod exe_stack { .compile_intermediates(); let [obj] = objs.as_slice() else { panic!() }; - let assert = t.symcheck_exe().arg(obj).assert(); + let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); if t.is_ppc64be() || t.no_os() { // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS. @@ -179,7 +193,11 @@ fn test_good_lib() { let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); - let assert = t.symcheck_exe().arg(&lib_out).assert(); + let assert = t + .symcheck_exe() + .arg(&lib_out) + .arg("--no-visibility") + .assert(); assert.success(); } @@ -199,7 +217,7 @@ fn test_good_bin() { t.set_bin_out_path(&mut cmd, &out); run(cmd.arg(input_dir().join("good_bin.c"))); - let assert = t.symcheck_exe().arg(&out).assert(); + let assert = t.symcheck_exe().arg(&out).arg("--no-visibility").assert(); assert.success(); }