rust/tests/run-make/x86_64-fortanix-unknown-sgx.../rmake.rs

101 lines
4.3 KiB
Rust

// ignore-tidy-linelength
// Reason: intel.com link
// This security test checks that the disassembled form of certain symbols
// is "hardened" - that means, the assembly instructions match a pattern that
// mitigate potential Load Value Injection vulnerabilities.
// To do so, a test crate is compiled, and certain symbols are found, disassembled
// and checked one by one.
// See https://github.com/rust-lang/rust/pull/77008
// On load value injection:
// https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/technical-documentation/load-value-injection.html
//@ only-x86_64-fortanix-unknown-sgx
use run_make_support::{cmd, cwd, llvm_filecheck, llvm_objdump, regex, set_current_dir, target};
fn main() {
let main_dir = cwd();
set_current_dir("enclave");
// HACK(eddyb) sets `RUSTC_BOOTSTRAP=1` so Cargo can accept nightly features.
// These come from the top-level Rust workspace, that this crate is not a
// member of, but Cargo tries to load the workspace `Cargo.toml` anyway.
cmd("cargo")
.env("RUSTC_BOOTSTRAP", "1")
.arg("-v")
.arg("run")
.arg("--target")
.arg(target())
.run();
set_current_dir(&main_dir);
// Rust has various ways of adding code to a binary:
// - Rust code
// - Inline assembly
// - Global assembly
// - C/C++ code compiled as part of Rust crates
// For those different kinds, we do have very small code examples that should be
// mitigated in some way. Mostly we check that ret instructions should no longer be present.
check("unw_getcontext", "unw_getcontext.checks");
check("__libunwind_Registers_x86_64_jumpto", "jumpto.checks");
check("std::io::stdio::_print::[[:alnum:]]+", "print.with_frame_pointers.checks");
check("rust_plus_one_global_asm", "rust_plus_one_global_asm.checks");
check("cc_plus_one_c", "cc_plus_one_c.checks");
check("cc_plus_one_c_asm", "cc_plus_one_c_asm.checks");
check("cc_plus_one_cxx", "cc_plus_one_cxx.checks");
check("cc_plus_one_cxx_asm", "cc_plus_one_cxx_asm.checks");
check("cc_plus_one_asm", "cc_plus_one_asm.checks");
check("cmake_plus_one_c", "cmake_plus_one_c.checks");
check("cmake_plus_one_c_asm", "cmake_plus_one_c_asm.checks");
check("cmake_plus_one_c_global_asm", "cmake_plus_one_c_global_asm.checks");
check("cmake_plus_one_cxx", "cmake_plus_one_cxx.checks");
check("cmake_plus_one_cxx_asm", "cmake_plus_one_cxx_asm.checks");
check("cmake_plus_one_cxx_global_asm", "cmake_plus_one_cxx_global_asm.checks");
check("cmake_plus_one_asm", "cmake_plus_one_asm.checks");
}
fn check(func_re: &str, mut checks: &str) {
let dump = llvm_objdump()
.input("enclave/target/x86_64-fortanix-unknown-sgx/debug/enclave")
.args(&["--syms", "--demangle"])
.run()
.stdout_utf8();
let re = regex::Regex::new(&format!("[[:blank:]]+{func_re}")).unwrap();
let func = re.find_iter(&dump).map(|m| m.as_str().trim()).collect::<Vec<&str>>().join(",");
assert!(!func.is_empty());
let dump = llvm_objdump()
.input("enclave/target/x86_64-fortanix-unknown-sgx/debug/enclave")
.args(&["--demangle", &format!("--disassemble-symbols={func}")])
.run()
.stdout_utf8();
let dump = dump.as_bytes();
// Unique case, must succeed at one of two possible tests.
// This is because frame pointers are optional, and them being enabled requires
// an additional `popq` in the pattern checking file.
if func_re == "std::io::stdio::_print::[[:alnum:]]+" {
let output = llvm_filecheck().stdin_buf(&dump).patterns(checks).run_unchecked();
if !output.status().success() {
checks = "print.without_frame_pointers.checks";
llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
}
} else {
llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
}
if !["rust_plus_one_global_asm", "cmake_plus_one_c_global_asm", "cmake_plus_one_cxx_global_asm"]
.contains(&func_re)
{
// The assembler cannot avoid explicit `ret` instructions. Sequences
// of `shlq $0x0, (%rsp); lfence; retq` are used instead.
llvm_filecheck()
.args(&["--implicit-check-not", "ret"])
.stdin_buf(dump)
.patterns(checks)
.run();
}
}