Introduction

BorrowSanitizer is a dynamic analysis tool for detecting Rust-specific aliasing bugs in multi-language applications.

the BorrowSanitizer project logo

The Rust compiler provides powerful, static safety guarantees by restricting aliasing and mutability. However, developers can bypass these restrictions by using a subset of unsafe features, which are necessary to interoperate with other languages. If these features are used incorrectly, then they can break the rules of Rust’s aliasing model, which the compiler relies on to be able to optimize programs. Incorrect optimizations can introduce security vulnerabilities.

Rust developers can find aliasing bugs using Miri, an interpreter. Miri is the only tool that can find violations of Rust’s latest Tree Borrows aliasing model, but it cannot find these bugs in foreign code. Miri is also significantly slower than native execution, which makes it impractical to use techniques like fuzzing or property-based testing to find these Rust-specific bugs.

BorrowSanitizer is an LLVM sanitizer for finding aliasing violations. Our goal is for it to be fast enough for use with fuzzing tools and to have support for Rust, C, and C++ in interoperation. We intend for it to be a production-ready tool.

🚧 Our project is still in early stages. BorrowSanitizer is not functional yet. 🚧

Join our Zulip if you are interested in contributing or if you have any additional questions about our project. You can build and test our sanitizer by following the setup instructions in the next section. All of our code is open-source and publicly available on GitHub.

Setup

Currently, the only way to try BorrowSanitizer is to build our fork of Rust from source. We only guarantee support for the following compilation targets:

targetdescription
aarch64-apple-darwinARM64 macOS (M-series)
aarch64-unknown-linux-gnuARM64 Linux
x86_64-unknown-linux-gnuX86 Linux

You can try to use our tool with other platforms, but there is no guarantee that it will work as expected, since we are only developing and testing it for these targets at the moment.

Building from Source

You will need the following dependencies:

Start by cloning our git repository. If you plan on contributing to BorrowSanitizer, we recommend either a full clone (without any additional flags) or passing --filter='blob:none', which will speed up the initial download by fetching blob objects on-demand. Alternatively, you can pass --depth 1 for a shallow clone, but you’ll need to switch to a full clone if you want to contribute.

git clone https://github.com/BorrowSanitizer/rust.git
cd rust

Production Builds

If you want to install our toolchain, or if you do not plan on contributing, then use the default distribution settings.

./x.py setup dist

Next, create a directory for the installation and set the variable DESTDIR to its absolute path.

mkdir [path] && export DESTDIR=[path]

Then, build and install the toolchain. This will copy the distribution binaries into $DESTDIR.

./x.py build && ./x.py install

When the build is finished, link the toolchain and set it as the default.

rustup toolchain link bsan $DESTDIR 
rustup default bsan

Development Builds

If you plan on contributing to BorrowSanitizer, you should copy our development config file into the root directory.

cp ./src/bootstrap/defaults/config.bsan.dev.toml config.toml

Then, build our toolchain.

./x.py build

When the build is complete, link the toolchain and set it as the default.

rustup toolchain link bsan build/host/stage1 
rustup default bsan

Visit the Rust Compiler Development Guide for additional tips and tricks.

Usage

You can manually enable BorrowSanitizer by passing the flag -Zsanitizer=borrow to the Rust compiler, like so:

RUSTFLAGS="-Zsanitizer=borrow" cargo build

Replace <host> with your current target. You can find out what this is executing the command rustc -vV. If a crate has doctests, then you will need to enable BorrowSanitizer within both RUSTFLAGS and RUSTDOCFLAGS for it to compile correctly.

The distribution build of our toolchain includes a Cargo plugin that will handle this step automatically.

cargo bsan <build/test/run>

Our plugin also links Rust programs against an instrumented sysroot, so that you can avoid rebuilding the standard library when switching between projects.

Development Guide

BorrowSanitizer uses several extensions to the Rust and LLVM toolchains.

ToolchainComponent./x.py buildLink
RustCargo Plugincargo-bsan
RustRustc Pluginbsan
LLVMInstrumentation Pass-
LLVMSanitizer Interfacebsan-rt
RustSanitizer Runtime Librarybsan-rt

System Overview

Our Cargo plugin builds an instrumented sysroot by redirecting compiler invocations through our Rustc plugin, which adds the flags for enabling BorrowSanitizer. This saves time when switching between projects. Otherwise, users would need to rebuild Rust’s standard library by passing -Zbuild-std for every project that they want to use with BorrowSanitizer. Any static optimizations that require Rust-specific type information will be implemented within the Rustc plugin.

We modified the Rust compiler to accept borrow as one of the options for -Z sanitizer=.... If this option is enabled, then the compiler will emit “retag” instructions, which create new permissions for references. This has a effect similar to the flag -Zmir-emit-retag, which Miri uses to emit retags. However, Miri only emits retags for a subset of operations, the rest are executed implicitly as side-effects of the interpreter. We emit retags everywhere that they are necessary for Tree Borrows. Our retags are lowered to LLVM as calls to a new @llvm.retag intrinsic function. Our LLVM insturmentation pass replaces this intrinsic with a call to our runtime library. Its arguments will capture all of the Rust-specific type information that we need to instrument LLVM IR for detecting aliasing violations. All other run-time checks are inserted in the backend by the LLVM pass.

Our runtime library is implemented as two components. The first component is implemented in C++ using the LLVM Sanitizer API. This component redirects our runtime checks to a Rust runtime library, where we implement the semantics of Rust’s aliasing model and all necessary operations for initializing and accessing values in shadow memory. This design allows us to reuse Miri’s implementation of Tree Borrows. It will also make it easier to port our approach to other Rust backends, since developers will only need to reimplement our instrumentation pass. Eventually, we will distribute these libraries as LLVM IR, which our Cargo plugin will statically link against the source program using cross-language LTO. This will allows us to gain performance through inlining. They are dynamically linked for now, though.

Configuration

Builds of the Rust compiler are configured through a config.toml file in the root directory of the repository. This file is auto-generated by running ./x.py setup. BorrowSanitizer, you need to set the following configuration options:

[build]
extended = true
tools = ["bsan-rt", "bsan", "cargo-bsan"]

[llvm]
download-ci-llvm = false

[rust]
llvm-tools = true

This ensures that LLVM is built from source so that you have access to our instrumentation pass. We also recommend setting clang=true under this section. This builds our modified version of Clang, which can be configured to enable BorrowSanitizer for C/C++ programs.

We provide a sample configuration file for you to modify. If you are creating a distribution build, then you should reuse Rust’s default configuration.

Testing & CI

We have unit tests for our runtime library and UI tests for the entire toolchain. Our unit tests are within bsan-rt, and our UI tests are within bsan-driver. You can execute each test suite by passing the relative path to these components as an argument to ./x.py test, like so:

./x.py test src/tools/bsan/bsan-rt
./x.py test src/tools/bsan/bsan-driver

Our CI pipeline checks for formatting and executes our tests on each of our supported architectures. Run ./x.py fmt before opening a pull request. All pull requests should be made using the branch BorrowSanitizer/bsan as the base for comparison. Make sure to select the correct branch when using GitHub, since the interface defaults to submitting a pull request against the main branch of the Rust toolchain! All pull requests must pass our CI pipline. External pull requests must be reviewed by a member of the team.

About

Reach out to us on Zulip if you have questions or are interested in contributing. BorrowSanitizer is open-source and publicly available on GitHub.

Team

BorrowSanitizer is currently being developed by:

License

BorrowSanitizer is dual-licensed under Apache and MIT.