Initial forward-edge CFI implementation (#3693)
* Initial forward-edge CFI implementation Give the user the option to start all basic blocks that are targets of indirect branches with the BTI instruction introduced by the Branch Target Identification extension to the Arm instruction set architecture. Copyright (c) 2022, Arm Limited. * Refactor `from_artifacts` to avoid second `make_executable` (#1) This involves "parsing" twice but this is parsing just the header of an ELF file so it's not a very intensive operation and should be ok to do twice. * Address the code review feedback Copyright (c) 2022, Arm Limited. Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
@@ -459,6 +459,10 @@ impl wasmtime_environ::Compiler for Compiler {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn is_branch_protection_enabled(&self) -> bool {
|
||||
self.isa.is_branch_protection_enabled()
|
||||
}
|
||||
|
||||
#[cfg(feature = "component-model")]
|
||||
fn component_compiler(&self) -> &dyn wasmtime_environ::component::ComponentCompiler {
|
||||
self
|
||||
|
||||
@@ -266,6 +266,9 @@ pub trait Compiler: Send + Sync {
|
||||
/// Same as [`Compiler::flags`], but ISA-specific (a cranelift-ism)
|
||||
fn isa_flags(&self) -> BTreeMap<String, FlagValue>;
|
||||
|
||||
/// Get a flag indicating whether branch protection is enabled.
|
||||
fn is_branch_protection_enabled(&self) -> bool;
|
||||
|
||||
/// Returns a suitable compiler usable for component-related compliations.
|
||||
///
|
||||
/// Note that the `ComponentCompiler` trait can also be implemented for
|
||||
|
||||
@@ -127,6 +127,7 @@ impl<'a> Arbitrary<'a> for CodegenSettings {
|
||||
"aarch64" => {
|
||||
test: is_aarch64_feature_detected,
|
||||
|
||||
std: "bti" => clif: "use_bti",
|
||||
std: "lse" => clif: "has_lse",
|
||||
// even though the natural correspondence seems to be
|
||||
// between "paca" and "has_pauth", the latter has no effect
|
||||
|
||||
@@ -40,7 +40,7 @@ pub struct Publish<'a> {
|
||||
pub obj: File<'a>,
|
||||
|
||||
/// Reference to the entire `MmapVec` and its contents.
|
||||
pub mmap: &'a [u8],
|
||||
pub mmap: &'a MmapVec,
|
||||
|
||||
/// Reference to just the text section of the object file, a subslice of
|
||||
/// `mmap`.
|
||||
@@ -87,7 +87,7 @@ impl CodeMemory {
|
||||
/// After this function executes all JIT code should be ready to execute.
|
||||
/// The various parsed results of the internals of the `MmapVec` are
|
||||
/// returned through the `Publish` structure.
|
||||
pub fn publish(&mut self) -> Result<Publish<'_>> {
|
||||
pub fn publish(&mut self, enable_branch_protection: bool) -> Result<Publish<'_>> {
|
||||
assert!(!self.published);
|
||||
self.published = true;
|
||||
|
||||
@@ -159,7 +159,7 @@ impl CodeMemory {
|
||||
// read/execute, notably not using read/write/execute to prevent
|
||||
// modifications.
|
||||
self.mmap
|
||||
.make_executable(text_range.clone())
|
||||
.make_executable(text_range.clone(), enable_branch_protection)
|
||||
.expect("unable to make memory executable");
|
||||
|
||||
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
|
||||
|
||||
@@ -136,6 +136,9 @@ struct Metadata {
|
||||
/// Note that even if this flag is `true` sections may be missing if they
|
||||
/// weren't found in the original wasm module itself.
|
||||
has_wasm_debuginfo: bool,
|
||||
|
||||
/// Whether or not branch protection is enabled.
|
||||
is_branch_protection_enabled: bool,
|
||||
}
|
||||
|
||||
/// Finishes compilation of the `translation` specified, producing the final
|
||||
@@ -160,6 +163,7 @@ pub fn finish_compile(
|
||||
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
||||
trampolines: Vec<Trampoline>,
|
||||
tunables: &Tunables,
|
||||
is_branch_protection_enabled: bool,
|
||||
) -> Result<(MmapVec, CompiledModuleInfo)> {
|
||||
let ModuleTranslation {
|
||||
mut module,
|
||||
@@ -265,6 +269,7 @@ pub fn finish_compile(
|
||||
has_unparsed_debuginfo,
|
||||
code_section_offset: debuginfo.wasm_file.code_section_offset,
|
||||
has_wasm_debuginfo: tunables.parse_wasm_debuginfo,
|
||||
is_branch_protection_enabled,
|
||||
},
|
||||
};
|
||||
bincode::serialize_into(&mut bytes, &info)?;
|
||||
@@ -398,19 +403,10 @@ impl CompiledModule {
|
||||
profiler: &dyn ProfilingAgent,
|
||||
id_allocator: &CompiledModuleIdAllocator,
|
||||
) -> Result<Self> {
|
||||
// Transfer ownership of `obj` to a `CodeMemory` object which will
|
||||
// manage permissions, such as the executable bit. Once it's located
|
||||
// there we also publish it for being able to execute. Note that this
|
||||
// step will also resolve pending relocations in the compiled image.
|
||||
let mut code_memory = CodeMemory::new(mmap);
|
||||
let code = code_memory
|
||||
.publish()
|
||||
.context("failed to publish code memory")?;
|
||||
|
||||
let obj = File::parse(&mmap[..]).context("failed to parse internal elf file")?;
|
||||
let opt_section = |name: &str| obj.section_by_name(name).and_then(|s| s.data().ok());
|
||||
let section = |name: &str| {
|
||||
code.obj
|
||||
.section_by_name(name)
|
||||
.and_then(|s| s.data().ok())
|
||||
opt_section(name)
|
||||
.ok_or_else(|| anyhow!("missing section `{}` in compilation artifacts", name))
|
||||
};
|
||||
|
||||
@@ -422,35 +418,28 @@ impl CompiledModule {
|
||||
.context("failed to deserialize wasmtime module info")?,
|
||||
};
|
||||
|
||||
let func_name_data = match code
|
||||
.obj
|
||||
.section_by_name(ELF_NAME_DATA)
|
||||
.and_then(|s| s.data().ok())
|
||||
{
|
||||
Some(data) => subslice_range(data, code.mmap),
|
||||
None => 0..0,
|
||||
};
|
||||
|
||||
let mut ret = Self {
|
||||
module: Arc::new(info.module),
|
||||
funcs: info.funcs,
|
||||
trampolines: info.trampolines,
|
||||
wasm_data: subslice_range(section(ELF_WASM_DATA)?, code.mmap),
|
||||
address_map_data: code
|
||||
.obj
|
||||
.section_by_name(ELF_WASMTIME_ADDRMAP)
|
||||
.and_then(|s| s.data().ok())
|
||||
.map(|slice| subslice_range(slice, code.mmap))
|
||||
wasm_data: subslice_range(section(ELF_WASM_DATA)?, &mmap),
|
||||
address_map_data: opt_section(ELF_WASMTIME_ADDRMAP)
|
||||
.map(|slice| subslice_range(slice, &mmap))
|
||||
.unwrap_or(0..0),
|
||||
trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, code.mmap),
|
||||
code: subslice_range(code.text, code.mmap),
|
||||
func_name_data: opt_section(ELF_NAME_DATA)
|
||||
.map(|slice| subslice_range(slice, &mmap))
|
||||
.unwrap_or(0..0),
|
||||
trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, &mmap),
|
||||
code: subslice_range(section(".text")?, &mmap),
|
||||
dbg_jit_registration: None,
|
||||
code_memory,
|
||||
code_memory: CodeMemory::new(mmap),
|
||||
meta: info.meta,
|
||||
unique_id: id_allocator.alloc(),
|
||||
func_names: info.func_names,
|
||||
func_name_data,
|
||||
};
|
||||
ret.code_memory
|
||||
.publish(ret.meta.is_branch_protection_enabled)
|
||||
.context("failed to publish code memory")?;
|
||||
ret.register_debug_and_profiling(profiler)?;
|
||||
|
||||
Ok(ret)
|
||||
|
||||
@@ -412,7 +412,11 @@ impl Mmap {
|
||||
}
|
||||
|
||||
/// Makes the specified `range` within this `Mmap` to be read/execute.
|
||||
pub unsafe fn make_executable(&self, range: Range<usize>) -> Result<()> {
|
||||
pub unsafe fn make_executable(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
enable_branch_protection: bool,
|
||||
) -> Result<()> {
|
||||
assert!(range.start <= self.len());
|
||||
assert!(range.end <= self.len());
|
||||
assert!(range.start <= range.end);
|
||||
@@ -428,8 +432,15 @@ impl Mmap {
|
||||
use std::io;
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
|
||||
let flags = if enable_branch_protection {
|
||||
// TODO: We use this check to avoid an unused variable warning,
|
||||
// but some of the CFG-related flags might be applicable
|
||||
PAGE_EXECUTE_READ
|
||||
} else {
|
||||
PAGE_EXECUTE_READ
|
||||
};
|
||||
let mut old = 0;
|
||||
let result = VirtualProtect(base, len, PAGE_EXECUTE_READ, &mut old);
|
||||
let result = VirtualProtect(base, len, flags, &mut old);
|
||||
if result == 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
@@ -438,8 +449,25 @@ impl Mmap {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use rustix::mm::{mprotect, MprotectFlags};
|
||||
mprotect(base, len, MprotectFlags::READ | MprotectFlags::EXEC)?;
|
||||
|
||||
let flags = MprotectFlags::READ | MprotectFlags::EXEC;
|
||||
let flags = if enable_branch_protection {
|
||||
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
|
||||
if std::arch::is_aarch64_feature_detected!("bti") {
|
||||
MprotectFlags::from_bits_unchecked(flags.bits() | /* PROT_BTI */ 0x10)
|
||||
} else {
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "aarch64", target_os = "linux")))]
|
||||
flags
|
||||
} else {
|
||||
flags
|
||||
};
|
||||
|
||||
mprotect(base, len, flags)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -102,9 +102,15 @@ impl MmapVec {
|
||||
}
|
||||
|
||||
/// Makes the specified `range` within this `mmap` to be read/execute.
|
||||
pub unsafe fn make_executable(&self, range: Range<usize>) -> Result<()> {
|
||||
self.mmap
|
||||
.make_executable(range.start + self.range.start..range.end + self.range.start)
|
||||
pub unsafe fn make_executable(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
enable_branch_protection: bool,
|
||||
) -> Result<()> {
|
||||
self.mmap.make_executable(
|
||||
range.start + self.range.start..range.end + self.range.start,
|
||||
enable_branch_protection,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the underlying file that this mmap is mapping, if present.
|
||||
|
||||
@@ -164,7 +164,7 @@ impl Component {
|
||||
let static_modules = static_modules?;
|
||||
let (lowerings, always_trap, transcoders, trampolines, trampoline_obj) = trampolines?;
|
||||
let mut trampoline_obj = CodeMemory::new(trampoline_obj);
|
||||
let code = trampoline_obj.publish()?;
|
||||
let code = trampoline_obj.publish(engine.compiler().is_branch_protection_enabled())?;
|
||||
let text = wasmtime_jit::subslice_range(code.text, code.mmap);
|
||||
|
||||
// This map is used to register all known tramplines in the
|
||||
|
||||
@@ -462,6 +462,9 @@ impl Engine {
|
||||
"sign_return_address" => Some(true),
|
||||
// No effect on its own.
|
||||
"sign_return_address_with_bkey" => Some(true),
|
||||
// The `BTI` instruction acts as a `NOP` when unsupported, so it
|
||||
// is safe to enable it.
|
||||
"use_bti" => Some(true),
|
||||
// fall through to the very bottom to indicate that support is
|
||||
// not enabled to test whether this feature is enabled on the
|
||||
// host.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::Engine;
|
||||
use crate::{
|
||||
signatures::SignatureCollection,
|
||||
types::{ExportType, ExternType, ImportType},
|
||||
Engine,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
@@ -328,18 +328,15 @@ impl Module {
|
||||
///
|
||||
/// This is where compilation actually happens of WebAssembly modules and
|
||||
/// translation/parsing/validation of the binary input occurs. The actual
|
||||
/// result here is a triple of:
|
||||
/// result here is a combination of:
|
||||
///
|
||||
/// * The index into the second field of the "main module". The "main
|
||||
/// module" in this case is the outermost module described by the `wasm`
|
||||
/// input, and is here for the module linking proposal.
|
||||
/// * A list of compilation artifacts for each module found within `wasm`.
|
||||
/// Note that if module linking is disabled then this list will always
|
||||
/// have a size of exactly 1. These pairs are returned by
|
||||
/// `wasmtime_jit::finish_compile`.
|
||||
/// * Type information about all the modules returned. All returned modules
|
||||
/// have local type information with indices that refer to these returned
|
||||
/// * The compilation artifacts for the module found within `wasm`, as
|
||||
/// returned by `wasmtime_jit::finish_compile`.
|
||||
/// * Type information about the module returned. All returned modules have
|
||||
/// local type information with indices that refer to these returned
|
||||
/// tables.
|
||||
/// * A boolean value indicating whether forward-edge CFI has been applied
|
||||
/// to the compiled module.
|
||||
#[cfg(compiler)]
|
||||
pub(crate) fn build_artifacts(
|
||||
engine: &Engine,
|
||||
@@ -431,8 +428,14 @@ impl Module {
|
||||
// table lazy init.
|
||||
translation.try_func_table_init();
|
||||
|
||||
let (mmap, info) =
|
||||
wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?;
|
||||
let (mmap, info) = wasmtime_jit::finish_compile(
|
||||
translation,
|
||||
obj,
|
||||
funcs,
|
||||
trampolines,
|
||||
tunables,
|
||||
engine.compiler().is_branch_protection_enabled(),
|
||||
)?;
|
||||
|
||||
Ok((mmap, Some(info)))
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ where
|
||||
// Copy the results of JIT compilation into executable memory, and this will
|
||||
// also take care of unwind table registration.
|
||||
let mut code_memory = CodeMemory::new(obj);
|
||||
let code = code_memory.publish()?;
|
||||
let code = code_memory.publish(engine.compiler().is_branch_protection_enabled())?;
|
||||
|
||||
register_trampolines(engine.profiler(), &code.obj);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user