Skip to content

Implemented Computatationally Efficient Shadowheap #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,26 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Switch to nightly
run: |
rustup default ${{ vars.BSAN_TOOLCHAIN }}
rustup component add rust-src rustc-dev llvm-tools-preview clippy
- name: Upstream
run: src/ci/scripts/setup-upstream-remote.sh
- name: Format
run: ./x.py fmt --check
- name: Clippy (bsan-rt)
run: |
cd src/tools/bsan/bsan-rt
RUSTFLAGS='-C panic=abort -Zpanic_abort_tests' cargo clippy --all-targets --all-features -- -D warnings
- name: Clippy (bsan-driver)
run: |
cd src/tools/bsan/bsan-driver
cargo clippy --all-targets --all-features -- -D warnings
- name: Clippy (cargo-bsan)
run: |
cd src/tools/bsan/bsan-driver/cargo-bsan
cargo clippy --all-targets --all-features -- -D warnings
build:
needs: [fmt]
strategy:
Expand Down Expand Up @@ -83,4 +99,5 @@ jobs:
- name: Unit Tests
run: ./x.py test --stage 1 src/tools/bsan/bsan-rt
- name: UI Tests
run: ./x.py test --stage 2 src/tools/bsan/bsan-driver
run: ./x.py test --stage 2 src/tools/bsan/bsan-driver

2 changes: 1 addition & 1 deletion .github/workflows/image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,4 @@ jobs:
while read -r id; do
gh api -X DELETE "$ENDPOINT/$id"
done


1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ jobs:
echo "$RUSTC_VERSION"
echo "$RUSTC_SHA"
echo "$RELEASE_NAME"

- name: Check if updates have been published
id: should_run
continue-on-error: true
Expand Down
34 changes: 34 additions & 0 deletions src/ci/borsan/Dockerfile.borsan
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM ubuntu:latest

WORKDIR /usr/borsan

ARG TARGETPLATFORM

ARG PREFIX
ARG URL
ARG TARGET

ARG TOOLCHAIN_DEST_DIR
ENV DEBIAN_FRONTEND=noninteractive
RUN which ldconfig
RUN apt update \
&& apt upgrade -y \
&& apt install curl -y

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh\
&& chmod +x rustup.sh \
&& ./rustup.sh -y --default-toolchain none \
&& rm rustup.sh

ENV PATH="$PATH:/root/.cargo/bin"

RUN TOOLCHAIN_DEST_DIR="/root/.rustup/toolchains/${PREFIX}-${TARGET}" \
&& mkdir -p "${TOOLCHAIN_DEST_DIR}" \
&& curl -L -o rust.tar.gz "${URL}" \
&& mkdir rust \
&& tar -xzf rust.tar.gz -C rust --strip-components=1 \
&& rm rust.tar.gz \
&& ./rust/install.sh --verbose --prefix="" --destdir="${TOOLCHAIN_DEST_DIR}" \
&& rm -rf rust

ENV BORSAN_VERSION="${PREFIX}-${TARGET}"
2 changes: 1 addition & 1 deletion src/tools/bsan/bsan-driver/cargo-bsan/src/phases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn phase_cargo_bsan(mut args: impl Iterator<Item = String>) {
return;
}

setup(&subcommand, &rustc_version.host.as_str(), &rustc_version, verbose, quiet);
setup(&subcommand, rustc_version.host.as_str(), &rustc_version, verbose, quiet);

let bsan_sysroot = get_target_sysroot_dir();
let bsan_path = find_bsan();
Expand Down
2 changes: 1 addition & 1 deletion src/tools/bsan/bsan-driver/cargo-bsan/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub fn setup(
} else {
// Keep all output on a single line.
eprint!("... ");
after_build_output = format!("done\n");
after_build_output = "done\n".to_string();
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/tools/bsan/bsan-driver/cargo-bsan/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pub fn exec_stdout(mut cmd: Command) -> String {
if output.status.success() {
String::from_utf8(output.stdout).expect("output bytes should be valid utf8")
} else {
panic!("failed to run command: {:?}", output)
panic!("failed to run command: {output:?}")
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/tools/bsan/bsan-driver/src/bin/bsan-driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main() {
// to be instrumented, while "host" indicates that it is a build script or procedural
// macro, which we can skip.

if let Some(crate_kind) = env::var("BSAN_BE_RUSTC").ok() {
if let Ok(crate_kind) = env::var("BSAN_BE_RUSTC") {
let is_target = match crate_kind.as_str() {
"host" => false,
"target" => true,
Expand Down
3 changes: 1 addition & 2 deletions src/tools/bsan/bsan-driver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![feature(rustc_private)]
#![warn(clippy::pedantic)]

extern crate rustc_driver;

Expand All @@ -26,7 +25,7 @@ pub fn run_compiler(
BSAN_DEFAULT_ARGS.iter().map(ToString::to_string).collect::<Vec<_>>();
if let Some(runtime) = env::var_os("BSAN_RT_SYSROOT") {
let rt = runtime.to_string_lossy();
additional_args.push(format!("-L{}/lib", rt));
additional_args.push(format!("-L{rt}/lib"));
}
args.splice(1..1, additional_args);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tools/bsan/bsan-rt/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ fn main() {
.with_crate(crate_dir.clone())
.generate()
.expect("Unable to generate bindings")
.write_to_file(Path::new(&out_dir).join(format!("bsan_rt.h")));
.write_to_file(Path::new(&out_dir).join("bsan_rt.h"));
}
14 changes: 10 additions & 4 deletions src/tools/bsan/bsan-rt/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::*;

/// Types that implement this trait can act as elements
/// of a singly-linked list. For this implementation to be sound,
/// the pointer that is returned must not be mutated concurrently.
/// of a singly-linked list.
///
/// # Safety
///
/// The pointer that is returned by `next` must not be mutated concurrently.
pub unsafe trait Linkable<T: Sized> {
fn next(&self) -> *mut *mut T;
}
Expand Down Expand Up @@ -38,7 +41,10 @@ impl<T> Drop for Block<T> {
// SAFETY: our munmap pointer will be valid by construction of the GlobalCtx.
// We can safely transmute it to c_void since that's what it was originally when
// it was allocated by mmap
let success = unsafe { (self.munmap)(mem::transmute(self.base.as_ptr()), self.size.get()) };
let success = unsafe {
let ptr = mem::transmute::<*mut T, *mut libc::c_void>(self.base.as_ptr());
(self.munmap)(ptr, self.size.get())
};
if success != 0 {
panic!("Failed to unmap block!");
}
Expand Down Expand Up @@ -155,7 +161,7 @@ mod test {
#[test]
fn allocate_from_page_in_parallel() {
let ctx = unsafe { init_global_ctx(TEST_HOOKS.clone()) };
let ctx = unsafe { &*ctx };
let ctx = unsafe { ctx };
let block = ctx.new_block::<Link>(unsafe { NonZero::new_unchecked(200) });
let page = Arc::new(BlockAllocator::<Link>::new(block));
let mut threads = Vec::new();
Expand Down
16 changes: 16 additions & 0 deletions src/tools/bsan/bsan-rt/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use core::fmt::Display;

pub type Result<T> = core::result::Result<T, BsanError>;

#[derive(Debug, Clone)]
pub enum BsanError {
ShadowStackOverflow,
}

impl Display for BsanError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BsanError::ShadowStackOverflow => write!(f, "Shadow stack overflow"),
}
}
}
45 changes: 33 additions & 12 deletions src/tools/bsan/bsan-rt/src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use block::*;
use hashbrown::{DefaultHashBuilder, HashMap};
use rustc_hash::FxBuildHasher;

use crate::shadow::ShadowHeap;
use crate::*;

/// Every action that requires a heap allocation must be performed through a globally
Expand All @@ -32,6 +33,7 @@ pub struct GlobalCtx {
hooks: BsanHooks,
next_alloc_id: AtomicUsize,
next_thread_id: AtomicUsize,
shadow_heap: ShadowHeap<Provenance>,
}

const BSAN_MMAP_PROT: i32 = libc::PROT_READ | libc::PROT_WRITE;
Expand All @@ -42,12 +44,21 @@ impl GlobalCtx {
/// This function will also initialize our shadow heap
fn new(hooks: BsanHooks) -> Self {
Self {
hooks,
hooks: hooks.clone(),
next_alloc_id: AtomicUsize::new(AllocId::min().get()),
next_thread_id: AtomicUsize::new(0),
shadow_heap: ShadowHeap::new(&hooks),
}
}

pub fn shadow_heap(&self) -> &ShadowHeap<Provenance> {
&self.shadow_heap
}

pub fn hooks(&self) -> &BsanHooks {
&self.hooks
}

pub fn new_block<T>(&self, num_elements: NonZeroUsize) -> Block<T> {
let layout = Layout::array::<T>(num_elements.into()).unwrap();
let size = NonZeroUsize::new(layout.size()).unwrap();
Expand All @@ -65,7 +76,8 @@ impl GlobalCtx {
if base.is_null() {
panic!("Allocation failed");
}
let base = unsafe { NonNull::new_unchecked(mem::transmute(base)) };
let base = unsafe { mem::transmute::<*mut c_void, *mut T>(base) };
let base = unsafe { NonNull::new_unchecked(base) };
let munmap = self.hooks.munmap;
Block { size, base, munmap }
}
Expand All @@ -91,14 +103,19 @@ impl GlobalCtx {
/// 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);
let mut buffer = BVec::new(self);
let _ = write!(&mut buffer, "{}", args);
unsafe {
(self.hooks.print)(mem::transmute(w.as_ptr()));
let buffer = mem::transmute::<*const u8, *const i8>(buffer.as_ptr());
(self.hooks.print)(buffer);
}
}
}

impl Drop for GlobalCtx {
fn drop(&mut self) {}
}

/// Prints to stdout.
macro_rules! print {
($ctx:expr, $($arg:tt)*) => {{
Expand Down Expand Up @@ -253,16 +270,20 @@ pub static GLOBAL_CTX: SyncUnsafeCell<MaybeUninit<GlobalCtx>> =
SyncUnsafeCell::new(MaybeUninit::uninit());

/// Initializes the global context object.
///
/// # Safety
///
/// 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) -> *mut GlobalCtx {
pub unsafe fn init_global_ctx<'a>(hooks: BsanHooks) -> &'a GlobalCtx {
(*GLOBAL_CTX.get()).write(GlobalCtx::new(hooks));
global_ctx()
}

/// Deinitializes the global context object.
/// # Safety
/// 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.
Expand All @@ -271,13 +292,13 @@ pub unsafe fn deinit_global_ctx() {
drop(ptr::replace(GLOBAL_CTX.get(), MaybeUninit::uninit()).assume_init());
}

/// 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.
/// # Safety
/// 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() -> *mut GlobalCtx {
let ctx: *mut MaybeUninit<GlobalCtx> = GLOBAL_CTX.get();
mem::transmute(ctx)
pub unsafe fn global_ctx<'a>() -> &'a GlobalCtx {
let ctx = GLOBAL_CTX.get();
&*mem::transmute::<*mut MaybeUninit<GlobalCtx>, *mut GlobalCtx>(ctx)
}

#[cfg(test)]
Expand Down
Loading