diff --git a/Cargo.lock b/Cargo.lock index 25f143e58b819..1daa8ddcbc245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,10 @@ name = "bsan-rt" version = "0.1.0" dependencies = [ "cbindgen", - "env_logger", + "hashbrown 0.15.2", "libc", - "log", + "rustc-hash 2.1.1", + "smallvec", ] [[package]] @@ -1610,10 +1611,11 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", "foldhash", ] @@ -1870,7 +1872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "rustc-rayon", "serde", ] @@ -2012,7 +2014,7 @@ dependencies = [ "anyhow", "clap", "fs-err", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustdoc-json-types", "serde", "serde_json", @@ -2561,7 +2563,7 @@ checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", "flate2", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "indexmap", "memchr", "ruzstd", @@ -3178,7 +3180,7 @@ dependencies = [ "proc-macro2", "quote", "rinja_parser", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "serde", "syn 2.0.87", ] @@ -3242,9 +3244,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-main" @@ -3626,7 +3628,7 @@ dependencies = [ "memmap2", "parking_lot", "portable-atomic", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustc-rayon", "rustc-stable-hash", "rustc_arena", @@ -4327,7 +4329,7 @@ dependencies = [ name = "rustc_pattern_analysis" version = "0.0.0" dependencies = [ - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustc_abi", "rustc_apfloat", "rustc_arena", @@ -4723,7 +4725,7 @@ name = "rustdoc-json-types" version = "0.1.0" dependencies = [ "bincode", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "serde", "serde_json", ] @@ -4986,9 +4988,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -5367,7 +5369,7 @@ dependencies = [ "ignore", "miropt-test-tools", "regex", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "semver", "similar", "termcolor", diff --git a/compiler/rustc_codegen_llvm/src/retag.rs b/compiler/rustc_codegen_llvm/src/retag.rs deleted file mode 100644 index 8b137891791fe..0000000000000 --- a/compiler/rustc_codegen_llvm/src/retag.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/llvm-project b/src/llvm-project index 726f06435db05..5c3457300084a 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 726f06435db05ba03a26cd8a6c3aa53d0cf7cca9 +Subproject commit 5c3457300084a1c47d3d555b24984a7a55a2fcb1 diff --git a/src/tools/bsan/bsan-rt/Cargo.toml b/src/tools/bsan/bsan-rt/Cargo.toml index 92a6a0f7cb8fe..6c2b424c4e077 100644 --- a/src/tools/bsan/bsan-rt/Cargo.toml +++ b/src/tools/bsan/bsan-rt/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" [dependencies] libc = { version = "0.2.169", default-features = false } +hashbrown = { version = "0.15.2", default-features = false, features = ["default-hasher", "nightly", "inline-more"] } +rustc-hash = { version = "2.1.1", default-features = false } +smallvec = { version = "1.14.0" } [lib] name = "bsan_rt" @@ -12,5 +15,8 @@ crate-type = ["staticlib"] test = true # we have unit tests doctest = false # but no doc tests +[features] +ui_test = [] + [build-dependencies] cbindgen = "0.28.0" \ No newline at end of file diff --git a/src/tools/bsan/bsan-rt/src/bsan_alloc.rs b/src/tools/bsan/bsan-rt/src/bsan_alloc.rs deleted file mode 100644 index 8a4d5d0e529ff..0000000000000 --- a/src/tools/bsan/bsan-rt/src/bsan_alloc.rs +++ /dev/null @@ -1,73 +0,0 @@ -use core::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; -use core::mem::{self, zeroed}; -use core::ptr::NonNull; - -use libc::{c_int, c_void, free, malloc, off_t}; - -pub type MMap = unsafe extern "C" fn(*mut c_void, usize, c_int, c_int, c_int, i64) -> *mut c_void; -pub type MUnmap = unsafe extern "C" fn(*mut c_void, usize) -> c_int; -pub type Malloc = unsafe extern "C" fn(usize) -> *mut c_void; -pub type Free = unsafe extern "C" fn(*mut c_void); - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct BsanAllocator { - malloc: Malloc, - free: Free, - mmap: MMap, - munmap: MUnmap, -} - -unsafe impl Send for BsanAllocator {} -unsafe impl Sync for BsanAllocator {} - -unsafe impl Allocator for BsanAllocator { - fn allocate(&self, layout: Layout) -> Result, AllocError> { - unsafe { - match layout.size() { - 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - // SAFETY: `layout` is non-zero in size, - size => unsafe { - let raw_ptr: *mut u8 = mem::transmute((self.malloc)(layout.size())); - let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?; - Ok(NonNull::slice_from_raw_parts(ptr, size)) - }, - } - } - } - - unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { - (self.free)(mem::transmute(ptr.as_ptr())) - } -} - -#[cfg(test)] -pub static TEST_ALLOC: BsanAllocator = BsanAllocator { - malloc: libc::malloc, - free: libc::free, - mmap: libc::mmap, - munmap: libc::munmap, -}; - -/// We need to declare a global allocator to be able to use `alloc` in a `#[no_std]` -/// crate. Anything other than the `BsanAllocator` object will clash with the interceptors, -/// so we use a placeholder that panics when it is used. -#[cfg(not(test))] -mod global_alloc { - use core::alloc::{GlobalAlloc, Layout}; - - #[derive(Default)] - struct DummyAllocator; - - unsafe impl GlobalAlloc for DummyAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - panic!() - } - unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - panic!() - } - } - - #[global_allocator] - static GLOBAL_ALLOCATOR: DummyAllocator = DummyAllocator; -} diff --git a/src/tools/bsan/bsan-rt/src/global.rs b/src/tools/bsan/bsan-rt/src/global.rs index 3d114aad6e54f..ef2b6ab08e594 100644 --- a/src/tools/bsan/bsan-rt/src/global.rs +++ b/src/tools/bsan/bsan-rt/src/global.rs @@ -1,29 +1,271 @@ +use alloc::collections::VecDeque; +use alloc::vec::Vec; use core::cell::SyncUnsafeCell; +use core::ffi::CStr; +use core::fmt; +use core::fmt::{Write, write}; +use core::mem::{self, zeroed}; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; use core::sync::atomic::AtomicUsize; -use crate::BsanAllocator; -#[cfg(test)] -use crate::TEST_ALLOC; +use hashbrown::{DefaultHashBuilder, HashMap}; +use rustc_hash::FxBuildHasher; + +use crate::*; +/// Every action that requires a heap allocation must be performed through a globally +/// accessible, singleton instance of `GlobalCtx`. Initializing or obtaining +/// a reference to this instance is unsafe, since it requires having been initialized +/// with a valid set of `BsanHooks`, which is provided from across the FFI. +/// Only shared references (&self) can be obtained, since this object will be accessed concurrently. +/// All of its API endpoints are free from undefined behavior, under +/// that these invariants hold. This design pattern requires us to pass the `GlobalCtx` instance +/// around explicitly, but it prevents us from relying on implicit global state and limits the spread +/// of unsafety throughout the library. #[derive(Debug)] -pub struct GlobalContext { - allocator: BsanAllocator, +pub struct GlobalCtx { + hooks: BsanHooks, next_alloc_id: AtomicUsize, } -impl GlobalContext { - fn new(allocator: BsanAllocator) -> Self { - Self { allocator, next_alloc_id: AtomicUsize::new(1) } +impl GlobalCtx { + /// Creates a new instance of `GlobalCtx` using the given `BsanHooks`. + /// This function will also initialize our shadow heap + fn new(hooks: BsanHooks) -> Self { + Self { hooks, next_alloc_id: AtomicUsize::new(1) } + } + + fn alloc(&self) -> BsanAllocHooks { + self.hooks.alloc + } + + fn exit(&self) -> ! { + unsafe { (self.hooks.exit)() } + } + + /// Prints a given set of formatted arguments. This function is not meant + /// to be called directly; instead, it should be used with the `print!`, + /// `println!`, and `ui_test!` macros. + pub fn print(&self, args: fmt::Arguments<'_>) { + let mut w = BVec::new(self); + let _ = write!(&mut w, "{}", args); + unsafe { + (self.hooks.print)(mem::transmute(w.as_ptr())); + } + } +} + +/// Prints to stdout. +macro_rules! print { + ($ctx:expr, $($arg:tt)*) => {{ + $ctx.print(core::format_args!($($arg)*)); + }}; +} +pub(crate) use print; + +/// Prints to stdout, appending a newline. +macro_rules! println { + ($ctx:expr) => { + $crate::print!($ctx, "\n") + }; + ($ctx:expr, $($arg:tt)*) => {{ + $ctx.print(core::format_args_nl!($($arg)*)); + }}; +} +pub(crate) use println; + +// General-purpose debug logging, which is only enabled in debug builds. +macro_rules! debug { + ($ctx:expr, $($arg:tt)*) => { + #[cfg(debug_assertions)] + $crate::println!($ctx, $($arg)*); + }; +} +pub(crate) use debug; + +// Logging for UI testing, which is enabled by the `ui_test` feature. +macro_rules! ui_test { + ($ctx:expr, $($arg:tt)*) => { + #[cfg(feature = "ui_test")] + $crate::println!($ctx, $($arg)*); + }; +} +pub(crate) use ui_test; + +/// A thin wrapper around `Vec` that uses `GlobalCtx` as its allocator +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BVec(Vec); + +impl Deref for BVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl BVec { + fn new(ctx: &GlobalCtx) -> Self { + unsafe { Self(Vec::new_in(ctx.alloc())) } + } +} + +/// We provide this trait implementation so that we can use `BVec` to +/// store the temporary results of formatting a string in the implementation +/// of `GlobalCtx::print` +impl core::fmt::Write for BVec { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let bytes = s.bytes(); + if self.try_reserve_exact(bytes.len()).is_err() { + Err(core::fmt::Error) + } else { + self.extend(bytes); + Ok(()) + } + } +} + +/// A thin wrapper around `VecDeque` that uses `GlobalCtx` as its allocator +#[derive(Debug, Clone)] +pub struct BVecDeque(VecDeque); + +impl Deref for BVecDeque { + type Target = VecDeque; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BVecDeque { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl BVecDeque { + fn new(ctx: &GlobalCtx) -> Self { + unsafe { Self(VecDeque::new_in(ctx.alloc())) } + } +} + +/// The seed for the random state of the hash function for `BHashMap`. +/// Equal to the decimal encoding of the ascii for "BSAN". +static BSAN_HASH_SEED: usize = 1112752462; + +/// A thin wrapper around `HashMap` that uses `GlobalCtx` as its allocator +#[derive(Debug, Clone)] +pub struct BHashMap(HashMap); + +impl Deref for BHashMap { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BHashMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl BHashMap { + fn new(ctx: &GlobalCtx) -> Self { + unsafe { Self(HashMap::with_hasher_in(FxBuildHasher, ctx.alloc())) } + } +} + +/// We need to declare a global allocator to be able to use `alloc` in a `#[no_std]` +/// crate. Anything other than the `GlobalCtx` object will clash with the interceptors, +/// so we provide a placeholder that panics when it is used. +#[cfg(not(test))] +mod global_alloc { + use core::alloc::{GlobalAlloc, Layout}; + + #[derive(Default)] + struct DummyAllocator; + + unsafe impl GlobalAlloc for DummyAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + panic!() + } + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + panic!() + } } + + #[global_allocator] + static GLOBAL_ALLOCATOR: DummyAllocator = DummyAllocator; } -pub static GLOBAL_CTX: SyncUnsafeCell> = SyncUnsafeCell::new(None); +pub static GLOBAL_CTX: SyncUnsafeCell> = SyncUnsafeCell::new(None); -pub unsafe fn init_global_ctx(alloc: BsanAllocator) { - *GLOBAL_CTX.get() = Some(GlobalContext::new(alloc)); +/// Initializes the global context object. +/// This function must only be called once: when the program is first initialized. +/// It is marked as `unsafe`, because it relies on the set of function pointers in +/// `BsanHooks` to be valid. +#[inline] +pub unsafe fn init_global_ctx(hooks: BsanHooks) -> &'static GlobalCtx { + *GLOBAL_CTX.get() = Some(GlobalCtx::new(hooks)); + global_ctx() } +/// Deinitializes the global context object. +/// This function must only be called once: when the program is terminating. +/// It is marked as `unsafe`, since all other API functions except for `bsan_init` rely +/// on the assumption that this function has not been called yet. #[inline] -pub unsafe fn global_ctx() -> &'static GlobalContext { +pub unsafe fn deinit_global_ctx() { + drop((&mut *GLOBAL_CTX.get()).take()); +} + +/// Accessing the global context is unsafe since the user needs to ensure that +/// the context is initialized, e.g. `bsan_init` has been called and `bsan_deinit` +/// has not yet been called. +#[inline] +pub unsafe fn global_ctx() -> &'static GlobalCtx { (&(*GLOBAL_CTX.get())).as_ref().unwrap_unchecked() } + +#[cfg(test)] +mod test { + use crate::*; + + unsafe extern "C" fn test_print(ptr: *const c_char) { + std::println!("{}", std::ffi::CStr::from_ptr(ptr).to_str().expect("Invalid UTF-8")); + } + + unsafe extern "C" fn test_exit() -> ! { + std::process::exit(0); + } + + unsafe extern "C" fn test_mmap( + addr: *mut c_void, + len: usize, + prot: i32, + flags: i32, + fd: i32, + offset: u64, + ) -> *mut c_void { + // LLVM's sanitizer API uses u64 for OFF_T, but libc uses i64 + // We use this wrapper function to avoid having to manually update + // the bindings. + libc::mmap(addr, len, prot, flags, fd, offset as i64) + } + + pub static TEST_HOOKS: BsanHooks = BsanHooks { + alloc: BsanAllocHooks { malloc: libc::malloc, free: libc::free }, + mmap: test_mmap, + munmap: libc::munmap, + print: test_print, + exit: test_exit, + }; +} diff --git a/src/tools/bsan/bsan-rt/src/lib.rs b/src/tools/bsan/bsan-rt/src/lib.rs index ba2e438d8d233..7cbbc956b4981 100644 --- a/src/tools/bsan/bsan-rt/src/lib.rs +++ b/src/tools/bsan/bsan-rt/src/lib.rs @@ -1,52 +1,299 @@ #![cfg_attr(not(test), no_std)] -#![feature(allocator_api)] #![feature(sync_unsafe_cell)] -#![feature(alloc_layout_extra)] #![feature(strict_overflow_ops)] +#![feature(thread_local)] +#![feature(allocator_api)] +#![feature(alloc_layout_extra)] +#![feature(format_args_nl)] #![allow(unused)] extern crate alloc; - +use core::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; use core::cell::UnsafeCell; -use core::ffi::c_void; +use core::ffi::{c_char, c_ulonglong, c_void}; +use core::mem::MaybeUninit; use core::num::NonZero; #[cfg(not(test))] use core::panic::PanicInfo; +use core::ptr::NonNull; +use core::{fmt, mem, ptr}; mod global; -use global::{global_ctx, init_global_ctx}; - -mod bsan_alloc; -pub use bsan_alloc::BsanAllocator; -#[cfg(test)] -pub use bsan_alloc::TEST_ALLOC; +pub use global::*; mod shadow; +pub type MMap = unsafe extern "C" fn(*mut c_void, usize, i32, i32, i32, c_ulonglong) -> *mut c_void; +pub type MUnmap = unsafe extern "C" fn(*mut c_void, usize) -> i32; +pub type Malloc = unsafe extern "C" fn(usize) -> *mut c_void; +pub type Free = unsafe extern "C" fn(*mut c_void); +pub type Print = unsafe extern "C" fn(*const c_char); +pub type Exit = unsafe extern "C" fn() -> !; + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct BsanHooks { + alloc: BsanAllocHooks, + mmap: MMap, + munmap: MUnmap, + print: Print, + exit: Exit, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct BsanAllocHooks { + malloc: Malloc, + free: Free, +} + +unsafe impl Allocator for BsanAllocHooks { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + unsafe { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), + // SAFETY: `layout` is non-zero in size, + size => unsafe { + let raw_ptr: *mut u8 = mem::transmute((self.malloc)(layout.size())); + let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, size)) + }, + } + } + } + + unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { + (self.free)(mem::transmute(ptr.as_ptr())) + } +} + +/// Unique identifier for an allocation +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct AllocId(usize); + +impl AllocId { + pub fn new(i: usize) -> Self { + AllocId(i) + } + pub fn get(&self) -> usize { + self.0 + } + /// An invalid allocation + pub const fn null() -> Self { + AllocId(0) + } + + /// Represents any valid allocation + pub const fn wildcard() -> Self { + AllocId(1) + } + + /// A global or stack allocation, which cannot be manually freed + pub const fn sticky() -> Self { + AllocId(2) + } + + pub const fn min() -> Self { + AllocId(3) + } +} + +impl fmt::Debug for AllocId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { write!(f, "a{}", self.0) } else { write!(f, "alloc{}", self.0) } + } +} + +/// Unique identifier for a node within the tree +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct BorTag(usize); + +impl BorTag { + pub const fn new(i: usize) -> Self { + BorTag(i) + } + pub fn get(&self) -> usize { + self.0 + } +} + +impl fmt::Debug for BorTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{}>", self.0) + } +} + +/// Unique identifier for a source location. Every update to the tree +/// is associated with a `Span`, which allows us to provide a detailed history +/// of the actions that lead to an aliasing violation. +pub type Span = usize; + +/// Pointers have provenance (RFC #3559). In Tree Borrows, this includes an allocation ID +/// and a borrow tag. We also include a pointer to the "lock" location for the allocation, +/// which contains all other metadata used to detect undefined behavior. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Provenance { + pub alloc_id: AllocId, + pub bor_tag: BorTag, + pub alloc_info: *mut c_void, +} + +impl Provenance { + /// The default provenance value, which is assigned to dangling or invalid + /// pointers. + const fn null() -> Self { + Provenance { + alloc_id: AllocId::null(), + bor_tag: BorTag::new(0), + alloc_info: core::ptr::null_mut(), + } + } + + /// Pointers cast from integers receive a "wildcard" provenance value, which permits + /// any access. + const fn wildcard() -> Self { + Provenance { + alloc_id: AllocId::wildcard(), + bor_tag: BorTag::new(0), + alloc_info: core::ptr::null_mut(), + } + } +} + +/// Every allocation is associated with a "lock" object, which is an instance of `AllocInfo`. +/// Provenance is the "key" to this lock. To validate a memory access, we compare the allocation ID +/// of a pointer's provenance with the value stored in its corresponding `AllocInfo` object. If the values +/// do not match, then the access is invalid. If they do match, then we proceed to validate the access against +/// the tree for the allocation. +#[repr(C)] +struct AllocInfo { + pub alloc_id: AllocId, + pub base_addr: usize, + pub size: usize, + pub align: usize, + pub tree: *mut c_void, +} + +impl AllocInfo { + /// When we deallocate an allocation, we need to invalidate its metadata. + /// so that any uses-after-free are detectable. + fn dealloc(&mut self) { + self.alloc_id = AllocId::null(); + self.base_addr = 0; + self.size = 0; + self.align = 1; + // FIXME: free the tree + } +} + +/// Initializes the global state of the runtime library. +/// The safety of this library is entirely dependent on this +/// function having been executed. We assume the global invariant that +/// no other API functions will be called prior to that point. +#[no_mangle] +unsafe extern "C" fn bsan_init(hooks: BsanHooks) { + let ctx = init_global_ctx(hooks); + ui_test!(ctx, "bsan_init"); +} + +/// Deinitializes the global state of the runtime library. +/// We assume the global invariant that no other API functions +/// will be called after this function has executed. +#[no_mangle] +unsafe extern "C" fn bsan_deinit() { + ui_test!(global_ctx(), "bsan_deinit"); + deinit_global_ctx(); +} + +/// Creates a new borrow tag for the given provenance object. +#[no_mangle] +extern "C" fn bsan_retag(span: Span, prov: *mut Provenance, retag_kind: u8, place_kind: u8) { + debug_assert!(prov != ptr::null_mut()); +} + +/// Records a read access of size `access_size` at the given address `addr` using the provenance `prov`. +#[no_mangle] +extern "C" fn bsan_read(span: Span, prov: *const Provenance, addr: usize, access_size: u64) { + debug_assert!(prov != ptr::null_mut()); +} + +/// Records a write access of size `access_size` at the given address `addr` using the provenance `prov`. #[no_mangle] -unsafe extern "C" fn bsan_init(alloc: BsanAllocator) { - init_global_ctx(alloc); +extern "C" fn bsan_write(span: Span, prov: *const Provenance, addr: usize, access_size: u64) { + debug_assert!(prov != ptr::null_mut()); } +/// Copies the provenance stored in the range `[src_addr, src_addr + access_size)` within the shadow heap +/// to the address `dst_addr`. This function will silently fail, so it should only be called in conjunction with +/// `bsan_read` and `bsan_write` or as part of an interceptor. +#[no_mangle] +extern "C" fn bsan_shadow_copy(dst_addr: usize, src_addr: usize, access_size: usize) {} + +/// Clears the provenance stored in the range `[dst_addr, dst_addr + access_size)` within the +/// shadow heap. This function will silently fail, so it should only be called in conjunction with +/// `bsan_read` and `bsan_write` or as part of an interceptor. +#[no_mangle] +extern "C" fn bsan_shadow_clear(addr: usize, access_size: usize) {} + +/// Loads the provenance of a given address from shadow memory and stores +/// the result in the return pointer. #[no_mangle] -extern "C" fn bsan_expose_tag(ptr: *mut c_void) {} +extern "C" fn bsan_load_prov(prov: *mut MaybeUninit, addr: usize) { + debug_assert!(prov != ptr::null_mut()); + unsafe { + (*prov).write(Provenance::null()); + } +} +/// Stores the given provenance value into shadow memory at the location for the given address. #[no_mangle] -extern "C" fn bsan_retag(ptr: *mut c_void, retag_kind: u8, place_kind: u8) -> u64 { - 0 +extern "C" fn bsan_store_prov(prov: *const Provenance, addr: usize) { + debug_assert!(prov != ptr::null_mut()); } +/// Pushes a shadow stack frame +#[no_mangle] +extern "C" fn bsan_push_frame(span: Span) {} + +/// Pops a shadow stack frame, deallocating all shadow allocations created by `bsan_alloc_stack` #[no_mangle] -extern "C" fn bsan_read(ptr: *mut c_void, access_size: u64) {} +extern "C" fn bsan_pop_frame(span: Span) {} +// Registers a heap allocation of size `size` #[no_mangle] -extern "C" fn bsan_write(ptr: *mut c_void, access_size: u64) {} +extern "C" fn bsan_alloc(span: Span, prov: *mut MaybeUninit, addr: usize) { + debug_assert!(prov != ptr::null_mut()); + unsafe { + (*prov).write(Provenance::null()); + } +} + +/// Registers a stack allocation of size `size`. +#[no_mangle] +extern "C" fn bsan_alloc_stack(span: Span, prov: *mut MaybeUninit, size: usize) { + debug_assert!(prov != ptr::null_mut()); + + unsafe { + (*prov).write(Provenance::null()); + } +} + +/// Deregisters a heap allocation #[no_mangle] -extern "C" fn bsan_func_entry() {} +extern "C" fn bsan_dealloc(span: Span, prov: *mut Provenance) { + debug_assert!(prov != ptr::null_mut()); +} +/// Marks the borrow tag for `prov` as "exposed," allowing it to be resolved to +/// validate accesses through "wildcard" pointers. #[no_mangle] -extern "C" fn bsan_func_exit() {} +extern "C" fn bsan_expose_tag(prov: *const Provenance) { + debug_assert!(prov != ptr::null_mut()); +} #[cfg(not(test))] #[panic_handler] diff --git a/src/tools/bsan/bsan-rt/src/shadow.rs b/src/tools/bsan/bsan-rt/src/shadow.rs index 59f93cc941878..d170823d5fd2e 100644 --- a/src/tools/bsan/bsan-rt/src/shadow.rs +++ b/src/tools/bsan/bsan-rt/src/shadow.rs @@ -1,14 +1,17 @@ use core::alloc::Layout; +use core::iter::repeat_n; use core::marker::PhantomData; use core::mem; use core::ops::{Add, BitAnd, Deref, DerefMut, Shr}; +use crate::Provenance; + /// Different targets have a different number /// of significant bits in their pointer representation. /// On 32-bit platforms, all 32-bits are addressable. Most /// 64-bit platforms only use 48-bits. Following the LLVM Project, /// we hard-code these values based on the underlying architecture. -/// Most, if not all 64 bit architectures use 48-bits. However, a the +/// Most, if not all 64 bit architectures use 48-bits. However, the /// Armv8-A spec allows addressing 52 or 56 bits as well. No processors /// implement this yet, though, so we can use target_pointer_width. @@ -56,46 +59,41 @@ fn table_indices(address: usize) -> (usize, usize) { (l1_index, l2_index) } -// Provenance values must be sized so that we can allocate an array of them -// for the L1 page table. We can make provenance values Copy since they should -// fit within 128 bits and they are not "owned" by any particular object. -pub trait Provenance: Copy + Sized {} - #[repr(C)] -pub struct L2 { - bytes: [T; L2_LEN], +pub struct L2 { + bytes: [Provenance; L2_LEN], } -impl L2 { +impl L2 { #[inline(always)] - unsafe fn lookup_mut(&mut self, index: usize) -> &mut T { + unsafe fn lookup_mut(&mut self, index: usize) -> &mut Provenance { self.bytes.get_unchecked_mut(index) } #[inline(always)] - unsafe fn lookup(&mut self, index: usize) -> &T { + unsafe fn lookup(&mut self, index: usize) -> &Provenance { self.bytes.get_unchecked(index) } } #[repr(C)] -pub struct L1 { - entries: [*mut L2; L1_LEN], +pub struct L1 { + entries: [*mut L2; L1_LEN], } -impl L1 { +impl L1 { fn new() -> Self { Self { entries: [core::ptr::null_mut(); L1_LEN] } } #[inline(always)] - unsafe fn lookup_mut(&mut self, index: usize) -> Option<&mut T> { + unsafe fn lookup_mut(&mut self, index: usize) -> Option<&mut Provenance> { let (l1_index, l2_index) = table_indices(index); let l2 = self.entries.get_unchecked_mut(l1_index); if l2.is_null() { None } else { Some((**l2).lookup_mut(l2_index)) } } #[inline(always)] - unsafe fn lookup(&mut self, index: usize) -> Option<&T> { + unsafe fn lookup(&mut self, index: usize) -> Option<&Provenance> { let (l1_index, l2_index) = table_indices(index); let l2 = self.entries.get_unchecked(l1_index); if l2.is_null() { None } else { Some((**l2).lookup(l2_index)) } @@ -106,39 +104,34 @@ impl L1 { /// the interior, unsafe implementation, providing debug assertions /// for each method. #[repr(transparent)] -pub struct ShadowHeap { - l1: L1, +pub struct ShadowHeap { + l1: L1, } -impl Default for ShadowHeap { +impl Default for ShadowHeap { fn default() -> Self { - let l1 = L1::::new(); + let l1 = L1::new(); Self { l1 } } } -impl Deref for ShadowHeap { - type Target = L1; +impl Deref for ShadowHeap { + type Target = L1; fn deref(&self) -> &Self::Target { &self.l1 } } -impl DerefMut for ShadowHeap { +impl DerefMut for ShadowHeap { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.l1 } } -#[cfg(test)] mod tests { use super::*; - type TestProv = u8; - - impl Provenance for TestProv {} - #[test] fn create_and_drop() { - let _ = ShadowHeap::::default(); + let _ = ShadowHeap::default(); } }