-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feat] Control Flow Integrity (CFI) Features (#603)
This PR contains the following changes: 1. CFI library with following features: a. CFI counter to detect out-of-flow code execution. b. XOR based Random number generator. c. Procedural macro to add random delays before and after decorated functions and to detect out-of-flow code execution. d. Variable launder function for assisting with duplicate checks. 2. Enablement of ROM code with CFI features.
- Loading branch information
Showing
30 changed files
with
942 additions
and
31 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Licensed under the Apache-2.0 license | ||
|
||
[package] | ||
name = "caliptra-cfi-derive" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
proc-macro = true | ||
doctest = false | ||
|
||
[dependencies] | ||
syn = { version = "1.0.107", features = ["extra-traits", "full"] } | ||
quote = "1.0.23" | ||
proc-macro2 = "1.0.51" | ||
paste = "1.0.11" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/*++ | ||
Licensed under the Apache-2.0 license. | ||
File Name: | ||
lib.rs | ||
Abstract: | ||
File contains CFI procedural macro. | ||
References: | ||
https://tf-m-user-guide.trustedfirmware.org/design_docs/tfm_physical_attack_mitigation.html | ||
https://github.com/rust-embedded/riscv/blob/master/src/asm.rs | ||
--*/ | ||
|
||
use proc_macro::TokenStream; | ||
use quote::{format_ident, quote, ToTokens}; | ||
use syn::__private::TokenStream2; | ||
use syn::parse_macro_input; | ||
use syn::parse_quote; | ||
use syn::FnArg; | ||
use syn::ItemFn; | ||
|
||
#[proc_macro_attribute] | ||
pub fn cfi_mod_fn(_args: TokenStream, input: TokenStream) -> TokenStream { | ||
cfi_fn(true, input) | ||
} | ||
|
||
#[proc_macro_attribute] | ||
pub fn cfi_impl_fn(_args: TokenStream, input: TokenStream) -> TokenStream { | ||
cfi_fn(false, input) | ||
} | ||
|
||
fn cfi_fn(mod_fn: bool, input: TokenStream) -> TokenStream { | ||
let mut wrapper_fn: ItemFn = parse_macro_input!(input as ItemFn); | ||
let mut orig_fn = wrapper_fn.clone(); | ||
orig_fn.sig.ident = format_ident!("__cfi_{}", wrapper_fn.sig.ident); | ||
orig_fn.attrs.clear(); | ||
orig_fn.vis = syn::Visibility::Inherited; | ||
|
||
let fn_name = format_ident!("{}", orig_fn.sig.ident); | ||
|
||
let param_names: Vec<TokenStream2> = orig_fn | ||
.sig | ||
.inputs | ||
.iter() | ||
.map(|input| match input { | ||
FnArg::Receiver(r) => r.self_token.to_token_stream(), | ||
FnArg::Typed(p) => p.pat.to_token_stream(), | ||
}) | ||
.collect(); | ||
|
||
let fn_call = if mod_fn { | ||
quote!(#fn_name( #(#param_names,)* )) | ||
} else { | ||
quote!(Self::#fn_name( #(#param_names,)* )) | ||
}; | ||
|
||
wrapper_fn.block.stmts.clear(); | ||
wrapper_fn.block.stmts = parse_quote!( | ||
let saved_ctr = caliptra_cfi_lib::CfiCounter::increment(); | ||
caliptra_cfi_lib::CfiCounter::delay(); | ||
let ret = #fn_call; | ||
caliptra_cfi_lib::CfiCounter::delay(); | ||
let new_ctr = caliptra_cfi_lib::CfiCounter::decrement(); | ||
caliptra_cfi_lib::CfiCounter::assert_eq(saved_ctr, new_ctr); | ||
ret | ||
); | ||
|
||
let code = quote! { | ||
#wrapper_fn | ||
#orig_fn | ||
}; | ||
|
||
code.into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Licensed under the Apache-2.0 license | ||
|
||
[package] | ||
name = "caliptra-cfi-lib" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
doctest = false | ||
|
||
[dependencies] | ||
caliptra_common = { workspace = true, default-features = false } | ||
caliptra-drivers.workspace = true | ||
caliptra-registers.workspace = true | ||
ufmt.workspace = true | ||
|
||
[dev-dependencies] | ||
caliptra-cfi-derive.workspace = true | ||
|
||
[features] | ||
default = ["cfi", "cfi-counter", "cfi-test"] | ||
cfi = [] | ||
cfi-counter = [] | ||
cfi-test = [] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/*++ | ||
Licensed under the Apache-2.0 license. | ||
File Name: | ||
cfi.rs | ||
Abstract: | ||
File contains CFI launder implementation. | ||
References: | ||
https://github.com/lowRISC/opentitan/blob/7a61300cf7c409fa68fd892942c1d7b58a7cd4c0/sw/device/lib/base/hardened.h#L260 | ||
--*/ | ||
|
||
use caliptra_drivers::CaliptraError; | ||
use core::cfg; | ||
use core::cmp::{Eq, Ord, PartialEq, PartialOrd}; | ||
use core::marker::Copy; | ||
|
||
/// CFI Panic Information | ||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] | ||
pub enum CfiPanicInfo { | ||
/// CFI Counter decode error | ||
CounterCorrupt, | ||
|
||
/// CFI Counter overflow | ||
CounterOverflow, | ||
|
||
/// CFI Counter underflow | ||
CounterUnderflow, | ||
|
||
/// CFI Counter mismatch | ||
CounterMismatch, | ||
|
||
/// CFI Assert Equal failed | ||
AssertEqFail, | ||
|
||
/// CFI Assert Not Equal failed | ||
AssertNeFail, | ||
|
||
/// CFI Greater Than failed | ||
AssertGtFail, | ||
|
||
/// CFI Less Than failed | ||
AssertLtFail, | ||
|
||
/// CFI Greater Than Equal failed | ||
AssertGeFail, | ||
|
||
/// CFI Less Than Equal failed | ||
AssertLeFail, | ||
|
||
/// Random number generator error | ||
TrngError, | ||
|
||
/// Unknown error | ||
UnknownError, | ||
} | ||
|
||
impl From<CfiPanicInfo> for CaliptraError { | ||
/// Converts to this type from the input type. | ||
fn from(info: CfiPanicInfo) -> CaliptraError { | ||
match info { | ||
CfiPanicInfo::CounterCorrupt => CaliptraError::ROM_CFI_PANIC_COUNTER_CORRUPT, | ||
CfiPanicInfo::CounterOverflow => CaliptraError::ROM_CFI_PANIC_COUNTER_OVERFLOW, | ||
CfiPanicInfo::CounterUnderflow => CaliptraError::ROM_CFI_PANIC_COUNTER_UNDERFLOW, | ||
CfiPanicInfo::CounterMismatch => CaliptraError::ROM_CFI_PANIC_COUNTER_MISMATCH, | ||
CfiPanicInfo::AssertEqFail => CaliptraError::ROM_CFI_PANIC_ASSERT_EQ_FAILURE, | ||
CfiPanicInfo::AssertNeFail => CaliptraError::ROM_CFI_PANIC_ASSERT_NE_FAILURE, | ||
CfiPanicInfo::AssertGtFail => CaliptraError::ROM_CFI_PANIC_ASSERT_GT_FAILURE, | ||
CfiPanicInfo::AssertLtFail => CaliptraError::ROM_CFI_PANIC_ASSERT_LT_FAILURE, | ||
CfiPanicInfo::AssertGeFail => CaliptraError::ROM_CFI_PANIC_ASSERT_GE_FAILURE, | ||
CfiPanicInfo::AssertLeFail => CaliptraError::ROM_CFI_PANIC_ASSERT_LE_FAILURE, | ||
CfiPanicInfo::TrngError => CaliptraError::ROM_CFI_PANIC_TRNG_FAILURE, | ||
_ => CaliptraError::ROM_CFI_PANIC_UNKNOWN, | ||
} | ||
} | ||
} | ||
|
||
/// Launder the value to prevent compiler optimization | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `val` - Value to launder | ||
/// | ||
/// # Returns | ||
/// | ||
/// `T` - Same value | ||
pub fn cfi_launder<T>(val: T) -> T { | ||
if cfg!(feature = "cfi") { | ||
// Note: The black box seems to be disabling more optimization | ||
// than necessary and results in larger binary size | ||
core::hint::black_box(val) | ||
} else { | ||
val | ||
} | ||
} | ||
|
||
/// Control flow integrity panic | ||
/// | ||
/// This panic is raised when the control flow integrity error is detected | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `info` - Panic information | ||
/// | ||
/// # Returns | ||
/// | ||
/// `!` - Never returns | ||
#[inline(never)] | ||
pub fn cfi_panic(info: CfiPanicInfo) -> ! { | ||
// Prevent the compiler from optimizing the reason | ||
let _ = cfi_launder(info); | ||
|
||
#[cfg(feature = "cfi")] | ||
{ | ||
#[cfg(feature = "cfi-test")] | ||
{ | ||
panic!("CFI Panic = {:04x?}", info); | ||
} | ||
|
||
#[cfg(not(feature = "cfi-test"))] | ||
{ | ||
extern "C" { | ||
fn cfi_panic_handler(code: u32) -> !; | ||
} | ||
unsafe { | ||
cfi_panic_handler(CaliptraError::from(info).into()); | ||
} | ||
} | ||
} | ||
|
||
#[cfg(not(feature = "cfi"))] | ||
{ | ||
unimplemented!() | ||
} | ||
} | ||
|
||
macro_rules! cfi_assert_macro { | ||
($name: ident, $op: tt, $trait1: path, $trait2: path, $panic_info: ident) => { | ||
/// CFI Binary Condition Assertion | ||
/// | ||
/// # Arguments | ||
/// | ||
/// `a` - Left hand side | ||
/// `b` - Right hand side | ||
#[inline(never)] | ||
#[allow(unused)] | ||
pub fn $name<T>(lhs: T, rhs: T) | ||
where | ||
T: $trait1 + $trait2, | ||
{ | ||
if cfg!(feature = "cfi") { | ||
if !(lhs $op rhs) { | ||
cfi_panic(CfiPanicInfo::$panic_info); | ||
} | ||
} else { | ||
lhs $op rhs; | ||
} | ||
} | ||
}; | ||
} | ||
|
||
cfi_assert_macro!(cfi_assert_eq, ==, Eq, PartialEq, AssertEqFail); | ||
cfi_assert_macro!(cfi_assert_ne, !=, Eq, PartialEq, AssertNeFail); | ||
cfi_assert_macro!(cfi_assert_gt, >, Ord, PartialOrd, AssertGtFail); | ||
cfi_assert_macro!(cfi_assert_lt, <, Ord, PartialOrd, AssertLtFail); | ||
cfi_assert_macro!(cfi_assert_ge, >=, Ord, PartialOrd, AssertGeFail); | ||
cfi_assert_macro!(cfi_assert_le, <=, Ord, PartialOrd, AssertLeFail); | ||
|
||
#[macro_export] | ||
macro_rules! cfi_assert { | ||
($cond: expr) => { | ||
cfi_assert_eq($cond, true) | ||
}; | ||
} |
Oops, something went wrong.