Skip to content

Commit

Permalink
Add indirect branch tracking (#15122)
Browse files Browse the repository at this point in the history
Adds support for indirect branch tracking for X86[_64] (CET) and AArch64 targets through the following compile time flags (taken from gcc/clang/rust):

- `-Dcf-protection=branch` (or `=return` or `=full`) for X86
- `-Dbranch-protection=bti` for AArch64

These flags are automatically set for OpenBSD, that enforces IBT or BTI on all user land applications. The patch also removes the `-Wl-znobtcfi` linker option since we don't need to disable it anymore.

OpenBSD is the only OS I know to support _and_ enforce IBT or BTI in user land. Linux for example only supports it for kernel code (for the time being). I manually tested IBT in an OpenBSD VM on x86_64 with a supported CPU (Intel Raptor Lake). I can compile & recompile crystal as well as run `gmake std_spec` without running into IBT issues 👍 

Notes:
- I expected to have to add the ASM instructions to the fiber context switch ASM... but messing with the stack pointer isn't considered as a conditional jump apparently 🤷
- I'm using the genius idea from @straight-shoota that we can pass `-Dkey=value` then test for `flag?("key=value")` and it just worked 😲 
- I can't test BTI on AArch64: I have no hardware and there are no bindings for the `aarch64-unknown-openbsd` target; there are little reasons it wouldn't work though;
- I added support for shadow stack (SHSTK) on X86 (`-Dcf-protection=return`). I'm not sure we really support it though, since fibers are messing with the stacks?
  • Loading branch information
ysbaddaden authored Oct 30, 2024
1 parent 04ace04 commit fd44c08
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 23 deletions.
32 changes: 25 additions & 7 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ module Crystal
@llvm_context : LLVM::Context = LLVM::Context.new)
@abi = @program.target_machine.abi
# LLVM::Context.register(@llvm_context, "main")
@llvm_mod = @llvm_context.new_module("main_module")
@llvm_mod = configure_module(@llvm_context.new_module("main_module"))
@main_mod = @llvm_mod
@main_llvm_context = @main_mod.context
@llvm_typer = LLVMTyper.new(@program, @llvm_context)
Expand Down Expand Up @@ -345,8 +345,6 @@ module Crystal
@unused_fun_defs = [] of FunDef
@proc_counts = Hash(String, Int32).new(0)

@llvm_mod.data_layout = self.data_layout

# We need to define __crystal_malloc and __crystal_realloc as soon as possible,
# to avoid some memory being allocated with plain malloc.
codegen_well_known_functions @node
Expand All @@ -367,6 +365,30 @@ module Crystal

getter llvm_context

def configure_module(llvm_mod)
llvm_mod.data_layout = @program.target_machine.data_layout

# enable branch authentication instructions (BTI)
if @program.has_flag?("aarch64")
if @program.has_flag?("branch-protection=bti")
llvm_mod.add_flag(:override, "branch-target-enforcement", 1)
end
end

# enable control flow enforcement protection (CET): IBT and/or SHSTK
if @program.has_flag?("x86_64") || @program.has_flag?("i386")
if @program.has_flag?("cf-protection=branch") || @program.has_flag?("cf-protection=full")
llvm_mod.add_flag(:override, "cf-protection-branch", 1)
end

if @program.has_flag?("cf-protection=return") || @program.has_flag?("cf-protection=full")
llvm_mod.add_flag(:override, "cf-protection-return", 1)
end
end

llvm_mod
end

def new_builder(llvm_context)
wrap_builder(llvm_context.new_builder)
end
Expand Down Expand Up @@ -419,10 +441,6 @@ module Crystal
global.initializer = llvm_element_type.const_array(llvm_elements)
end

def data_layout
@program.target_machine.data_layout
end

class CodegenWellKnownFunctions < Visitor
@codegen : CodeGenVisitor

Expand Down
8 changes: 2 additions & 6 deletions src/compiler/crystal/codegen/debug.cr
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,13 @@ module Crystal

if @program.has_flag?("msvc")
# Windows uses CodeView instead of DWARF
mod.add_flag(
LibLLVM::ModuleFlagBehavior::Warning,
"CodeView",
mod.context.int32.const_int(1)
)
mod.add_flag(LibLLVM::ModuleFlagBehavior::Warning, "CodeView", 1)
end

mod.add_flag(
LibLLVM::ModuleFlagBehavior::Warning,
"Debug Info Version",
mod.context.int32.const_int(LLVM::DEBUG_METADATA_VERSION)
LLVM::DEBUG_METADATA_VERSION
)
end

Expand Down
3 changes: 1 addition & 2 deletions src/compiler/crystal/codegen/fun.cr
Original file line number Diff line number Diff line change
Expand Up @@ -626,8 +626,7 @@ class Crystal::CodeGenVisitor
# LLVM::Context.register(llvm_context, type_name)

llvm_typer = LLVMTyper.new(@program, llvm_context)
llvm_mod = llvm_context.new_module(type_name)
llvm_mod.data_layout = self.data_layout
llvm_mod = configure_module(llvm_context.new_module(type_name))
llvm_builder = new_builder(llvm_context)

define_symbol_table llvm_mod, llvm_typer
Expand Down
6 changes: 0 additions & 6 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,6 @@ module Crystal
link_flags += " -L/usr/local/lib"
end

if program.has_flag?("openbsd")
# OpenBSD requires Indirect Branch Tracking by default, but we're not
# compatible (yet), so we disable it for now:
link_flags += " -Wl,-znobtcfi"
end

{DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names}
end
end
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/crystal/semantic/flags.cr
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ class Crystal::Program
flags.add "freebsd#{target.freebsd_version}"
end
flags.add "netbsd" if target.netbsd?
flags.add "openbsd" if target.openbsd?

if target.openbsd?
flags.add "openbsd"

case target.architecture
when "aarch64"
flags.add "branch-protection=bti" unless flags.any?(&.starts_with?("branch-protection="))
when "x86_64", "i386"
flags.add "cf-protection=branch" unless flags.any?(&.starts_with?("cf-protection="))
end
end

flags.add "dragonfly" if target.dragonfly?
flags.add "solaris" if target.solaris?
flags.add "android" if target.android?
Expand Down
7 changes: 6 additions & 1 deletion src/llvm/lib_llvm/core.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ lib LibLLVM
# counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`)

enum ModuleFlagBehavior
Warning = 1
Error = 0
Warning = 1
Require = 2
Override = 3
Append = 4
AppendUnique = 5
end

alias AttributeIndex = UInt
Expand Down
4 changes: 4 additions & 0 deletions src/llvm/module.cr
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class LLVM::Module
GlobalCollection.new(self)
end

def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Int32)
add_flag(module_flag, key, @context.int32.const_int(val))
end

def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Value)
LibLLVM.add_module_flag(
self,
Expand Down

0 comments on commit fd44c08

Please sign in to comment.