Auto merge of #77008 - fortanix:raoul/lvi-tests, r=Mark-Simulacrum

LVI hardening tests

Mitigating the speculative execution LVI attack against SGX enclaves requires compiler changes (i.e., adding lfences). This pull requests adds various tests to check if this happens correctly.
This commit is contained in:
bors 2020-09-28 03:28:04 +00:00
commit 6369a98ebd
32 changed files with 519 additions and 2 deletions

View File

@ -630,7 +630,14 @@ impl Step for TestHelpers {
if builder.config.dry_run {
return;
}
let target = self.target;
// The x86_64-fortanix-unknown-sgx target doesn't have a working C
// toolchain. However, some x86_64 ELF objects can be linked
// without issues. Use this hack to compile the test helpers.
let target = if self.target == "x86_64-fortanix-unknown-sgx" {
TargetSelection::from_user("x86_64-unknown-linux-gnu")
} else {
self.target
};
let dst = builder.test_helpers_out(target);
let src = builder.src.join("src/test/auxiliary/rust_test_helpers.c");
if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
@ -654,7 +661,6 @@ impl Step for TestHelpers {
}
cfg.compiler(builder.cc(target));
}
cfg.cargo_metadata(false)
.out_dir(&dst)
.target(&target.triple)

View File

@ -0,0 +1,17 @@
// Test LVI load hardening on SGX enclave code
// assembly-output: emit-asm
// compile-flags: --crate-type staticlib
// only-x86_64-fortanix-unknown-sgx
#[no_mangle]
pub extern fn plus_one(r: &mut u64) {
*r = *r + 1;
}
// CHECK: plus_one
// CHECK: lfence
// CHECK-NEXT: addq
// CHECK: popq [[REGISTER:%[a-z]+]]
// CHECK-NEXT: lfence
// CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,12 @@
// Test LVI ret hardening on generic rust code
// assembly-output: emit-asm
// compile-flags: --crate-type staticlib
// only-x86_64-fortanix-unknown-sgx
#[no_mangle]
pub extern fn myret() {}
// CHECK: myret:
// CHECK: popq [[REGISTER:%[a-z]+]]
// CHECK-NEXT: lfence
// CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,41 @@
// Test LVI load hardening on SGX inline assembly code
// assembly-output: emit-asm
// compile-flags: --crate-type staticlib
// only-x86_64-fortanix-unknown-sgx
#![feature(asm)]
#[no_mangle]
pub extern fn get(ptr: *const u64) -> u64 {
let value : u64;
unsafe {
asm!(".start_inline_asm:",
"mov {}, [{}]",
".end_inline_asm:",
out(reg) value,
in(reg) ptr);
}
value
}
// CHECK: get
// CHECK: .start_inline_asm
// CHECK-NEXT: movq
// CHECK-NEXT: lfence
// CHECK-NEXT: .end_inline_asm
#[no_mangle]
pub extern fn myret() {
unsafe {
asm!(".start_myret_inline_asm:
ret
.end_myret_inline_asm:");
}
}
// CHECK: myret
// CHECK: .start_myret_inline_asm
// CHECK-NEXT: shlq $0, (%rsp)
// CHECK-NEXT: lfence
// CHECK-NEXT: retq

View File

@ -0,0 +1,23 @@
-include ../../run-make-fulldeps/tools.mk
#only-x86_64-fortanix-unknown-sgx
# For cargo setting
export RUSTC := $(RUSTC_ORIGINAL)
export LD_LIBRARY_PATH := $(HOST_RPATH_DIR)
# We need to be outside of 'src' dir in order to run cargo
export WORK_DIR := $(TMPDIR)
export TEST_DIR := $(shell pwd)
## clean up unused env variables which might cause harm.
unexport RUSTC_LINKER
unexport RUSTC_BOOTSTRAP
unexport RUST_BUILD_STAGE
unexport RUST_TEST_THREADS
unexport RUST_TEST_TMPDIR
unexport AR
unexport CC
unexport CXX
all:
bash script.sh

View File

@ -0,0 +1,8 @@
CHECK: cc_plus_one_asm
CHECK-NEXT: movl
CHECK-NEXT: lfence
CHECK-NEXT: inc
CHECK-NEXT: notq (%rsp)
CHECK-NEXT: notq (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq

View File

@ -0,0 +1,6 @@
CHECK: cc_plus_one_c
CHECK: lfence
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,15 @@
CHECK: cc_plus_one_c_asm
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK-NEXT: incl
CHECK-NEXT: jmp
CHECK-NEXT: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,6 @@
CHECK: cc_plus_one_cxx
CHECK: lfence
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,16 @@
CHECK: cc_plus_one_cxx_asm
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: movl
CHECK: lfence
CHECK: lfence
CHECK-NEXT: incl
CHECK-NEXT: jmp 0x{{[[:xdigit:]]+}} <cc_plus_one_cxx_asm+0x{{[[:xdigit:]]+}}>
CHECK-NEXT: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,7 @@
CHECK: cmake_plus_one_asm
CHECK-NEXT: movl
CHECK-NEXT: lfence
CHECK-NEXT: incl
CHECK-NEXT: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq

View File

@ -0,0 +1,6 @@
CHECK: cmake_plus_one_c
CHECK: lfence
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,16 @@
CHECK: cmake_plus_one_c_asm
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: movl
CHECK: lfence
CHECK-NEXT: incl
CHECK-NEXT: jmp 0x{{[[:xdigit:]]+}} <cmake_plus_one_c_asm+0x{{[[:xdigit:]]+}}>
CHECK-NEXT: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,2 @@
CHECK: cmake_plus_one_c_global_asm
CHECK: lfence

View File

@ -0,0 +1,6 @@
CHECK: cmake_plus_one_cxx
CHECK: lfence
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,16 @@
CHECK: cmake_plus_one_cxx_asm
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: movl
CHECK: lfence
CHECK-NEXT: incl
CHECK-NEXT: jmp 0x{{[[:xdigit:]]+}} <cmake_plus_one_cxx_asm+0x{{[[:xdigit:]]+}}>
CHECK-NEXT: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq
CHECK: popq
CHECK-NEXT: popq [[REGISTER:%[a-z]+]]
CHECK-NEXT: lfence
CHECK-NEXT: jmpq *[[REGISTER]]

View File

@ -0,0 +1,2 @@
CHECK: cmake_plus_one_cxx_global_asm
CHECK: lfence

View File

@ -0,0 +1,13 @@
[package]
name = "enclave"
version = "0.1.0"
authors = ["Raoul Strackx <raoul.strackx@fortanix.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[build-dependencies]
cc = "1.0"
cmake = "0.1"

View File

@ -0,0 +1,30 @@
fn main() {
cc::Build::new()
.file("foo.c")
.compile("foo_c");
cc::Build::new()
.file("foo_asm.s")
.compile("foo_asm");
cc::Build::new()
.cpp(true)
.cpp_set_stdlib(None)
.file("foo_cxx.cpp")
.compile("foo_cxx");
// When the cmake crate detects the clang compiler, it passes the
// "--target" argument to the linker which subsequently fails. The
// `CMAKE_C_COMPILER_FORCED` option makes sure that `cmake` does not
// tries to test the compiler. From version 3.6 the option
// `CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY` can be used
// https://cmake.org/cmake/help/v3.5/module/CMakeForceCompiler.html
let dst = cmake::Config::new("libcmake_foo")
.build_target("cmake_foo")
.define("CMAKE_C_COMPILER_FORCED", "1")
.define("CMAKE_CXX_COMPILER_FORCED", "1")
.define("CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY", "1")
.build();
println!("cargo:rustc-link-search=native={}/build/", dst.display());
println!("cargo:rustc-link-lib=static=cmake_foo");
}

View File

@ -0,0 +1,18 @@
int cc_plus_one_c(int *arg) {
return *arg + 1;
}
int cc_plus_one_c_asm(int *arg) {
int value = 0;
asm volatile ( " movl (%1), %0\n"
" inc %0\n"
" jmp 1f\n"
" retq\n" // never executed, but a shortcut to determine how
// the assembler deals with `ret` instructions
"1:\n"
: "=r"(value)
: "r"(arg) );
return value;
}

View File

@ -0,0 +1,7 @@
.text
.global cc_plus_one_asm
.type cc_plus_one_asm, @function
cc_plus_one_asm:
movl (%rdi), %eax
inc %eax
retq

View File

@ -0,0 +1,21 @@
extern "C" int cc_plus_one_cxx(int *arg);
extern "C" int cc_plus_one_cxx_asm(int *arg);
int cc_plus_one_cxx(int *arg) {
return *arg + 1;
}
int cc_plus_one_cxx_asm(int *arg) {
int value = 0;
asm volatile ( " movl (%1), %0\n"
" inc %0\n"
" jmp 1f\n"
" retq\n" // never executed, but a shortcut to determine how
// the assembler deals with `ret` instructions
"1:\n"
: "=r"(value)
: "r"(arg) );
return value;
}

View File

@ -0,0 +1,33 @@
enable_language(C CXX ASM)
set(C_SOURCES
src/foo.c
)
set_source_files_properties(${C_SOURCES}
PROPERTIES
LANGUAGE C)
set(CXX_SOURCES
src/foo_cxx.cpp
)
set_source_files_properties(${CXX_SOURCES}
PROPERTIES
LANGUAGE CXX)
set(ASM_SOURCES
src/foo_asm.s
)
set_source_files_properties(${ASM_SOURCES}
PROPERTIES
LANGUAGE ASM)
set(SOURCES
${C_SOURCES}
${CXX_SOURCES}
${ASM_SOURCES})
add_library(cmake_foo STATIC
${SOURCES})

View File

@ -0,0 +1,26 @@
int cmake_plus_one_c(int *arg) {
return *arg + 1;
}
int cmake_plus_one_c_asm(int *arg) {
int value = 0;
asm volatile ( " movl (%1), %0\n"
" inc %0\n"
" jmp 1f\n"
" retq\n" // never executed, but a shortcut to determine how
// the assembler deals with `ret` instructions
"1:\n"
: "=r"(value)
: "r"(arg) );
return value;
}
asm(".text\n"
" .global cmake_plus_one_c_global_asm\n"
" .type cmake_plus_one_c_global_asm, @function\n"
"cmake_plus_one_c_global_asm:\n"
" movl (%rdi), %eax\n"
" inc %eax\n"
" retq\n" );

View File

@ -0,0 +1,7 @@
.text
.global cmake_plus_one_asm
.type cmake_plus_one_asm, @function
cmake_plus_one_asm:
movl (%rdi), %eax
inc %eax
retq

View File

@ -0,0 +1,29 @@
extern "C" int cmake_plus_one_cxx(int *arg);
extern "C" int cmake_plus_one_cxx_asm(int *arg);
int cmake_plus_one_cxx(int *arg) {
return *arg + 1;
}
int cmake_plus_one_cxx_asm(int *arg) {
int value = 0;
asm volatile ( " movl (%1), %0\n"
" inc %0\n"
" jmp 1f\n"
" retq\n" // never executed, but a shortcut to determine how
// the assembler deals with `ret` instructions
"1:\n"
: "=r"(value)
: "r"(arg) );
return value;
}
asm(".text\n"
" .global cmake_plus_one_cxx_global_asm\n"
" .type cmake_plus_one_cxx_global_asm, @function\n"
"cmake_plus_one_cxx_global_asm:\n"
" movl (%rdi), %eax\n"
" inc %eax\n"
" retq\n" );

View File

@ -0,0 +1,48 @@
#![feature(global_asm)]
global_asm!( r#"
.text
.global rust_plus_one_global_asm
.type rust_plus_one_global_asm, @function
rust_plus_one_global_asm:
movl (%rdi), %eax
inc %eax
retq
"# );
extern {
fn cc_plus_one_c(arg : &u32) -> u32;
fn cc_plus_one_c_asm(arg : &u32) -> u32;
fn cc_plus_one_cxx(arg : &u32) -> u32;
fn cc_plus_one_cxx_asm(arg : &u32) -> u32;
fn cc_plus_one_asm(arg : &u32) -> u32;
fn cmake_plus_one_c(arg : &u32) -> u32;
fn cmake_plus_one_c_asm(arg : &u32) -> u32;
fn cmake_plus_one_cxx(arg : &u32) -> u32;
fn cmake_plus_one_cxx_asm(arg : &u32) -> u32;
fn cmake_plus_one_c_global_asm(arg : &u32) -> u32;
fn cmake_plus_one_cxx_global_asm(arg : &u32) -> u32;
fn cmake_plus_one_asm(arg : &u32) -> u32;
fn rust_plus_one_global_asm(arg : &u32) -> u32;
}
fn main() {
let value : u32 = 41;
let question = "Answer to the Ultimate Question of Life, the Universe, and Everything:";
unsafe{
println!("{}: {}!", question,rust_plus_one_global_asm(&value));
println!("{}: {}!", question,cc_plus_one_c(&value));
println!("{}: {}!", question,cc_plus_one_c_asm(&value));
println!("{}: {}!", question,cc_plus_one_cxx(&value));
println!("{}: {}!", question,cc_plus_one_cxx_asm(&value));
println!("{}: {}!", question,cc_plus_one_asm(&value));
println!("{}: {}!", question,cmake_plus_one_c(&value));
println!("{}: {}!", question,cmake_plus_one_c_asm(&value));
println!("{}: {}!", question,cmake_plus_one_cxx(&value));
println!("{}: {}!", question,cmake_plus_one_cxx_asm(&value));
println!("{}: {}!", question,cmake_plus_one_c_global_asm(&value));
println!("{}: {}!", question,cmake_plus_one_cxx_global_asm(&value));
println!("{}: {}!", question,cmake_plus_one_asm(&value));
}
}

View File

@ -0,0 +1,8 @@
CHECK: libunwind::Registers_x86_64::jumpto
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq

View File

@ -0,0 +1,7 @@
CHECK: print
CHECK: lfence
CHECK: lfence
CHECK: lfence
CHECK: popq
CHECK: callq 0x{{[[:xdigit:]]*}} <_Unwind_Resume>
CHECK-NEXT: ud2

View File

@ -0,0 +1,2 @@
CHECK: rust_plus_one_global_asm
CHECK: lfence

View File

@ -0,0 +1,57 @@
set -exuo pipefail
function build {
CRATE=enclave
mkdir -p $WORK_DIR
pushd $WORK_DIR
rm -rf $CRATE
cp -a $TEST_DIR/enclave .
pushd $CRATE
echo ${WORK_DIR}
# 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.
env RUSTC_BOOTSTRAP=1
cargo -v run --target $TARGET
popd
popd
}
function check {
local func=$1
local checks="${TEST_DIR}/$2"
local asm=$(mktemp)
local objdump="${BUILD_DIR}/x86_64-unknown-linux-gnu/llvm/build/bin/llvm-objdump"
local filecheck="${BUILD_DIR}/x86_64-unknown-linux-gnu/llvm/build/bin/FileCheck"
${objdump} --disassemble-symbols=${func} --demangle \
${WORK_DIR}/enclave/target/x86_64-fortanix-unknown-sgx/debug/enclave > ${asm}
${filecheck} --input-file ${asm} ${checks}
}
build
check unw_getcontext unw_getcontext.checks
check "libunwind::Registers_x86_64::jumpto()" jumpto.checks
check "std::io::stdio::_print::h87f0c238421c45bc" print.checks
check rust_plus_one_global_asm rust_plus_one_global_asm.checks \
|| echo "warning: module level assembly currently not hardened"
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 \
|| echo "warning: the cc crate forwards assembly files to the CC compiler." \
"Clang uses its own intergrated assembler, which does not include the LVI passes."
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 \
|| echo "warning: module level assembly currently not hardened"
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 \
|| echo "warning: module level assembly currently not hardened"
check cmake_plus_one_asm cmake_plus_one_asm.checks

View File

@ -0,0 +1,6 @@
CHECK: unw_getcontext
CHECK: lfence
CHECK: lfence
CHECK: shlq $0, (%rsp)
CHECK-NEXT: lfence
CHECK-NEXT: retq