* Fixes to main

* Add librasan

* Party like it's 2024

* Fix snapshot module to work with guest asan

* Fix guest_asan module

* Fixes to runner

* Fix linking issues using a REL

* Fix qemu_launcher

* Change modify_mapping to a method

* Fix gasan_test

* Remove debug from Justfile

* Optimize release build of librasan

* Set ulimit for qasan and gasan tests

* Tidy up symbol renaming

* Add missing symbols for PPC

* Change to support rustix 1.0.0

* Canonicalize the CUSTOM_ASAN_PATH

* Review changes

* Restructure backends

* release_max_level_info

* More review changes

* Clippy fixes

* Changes to reduce the burden on the CI

* Fix macos clippy

---------

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
WorksButNotTested 2025-03-10 16:27:55 +00:00 committed by GitHub
parent f64554c5db
commit 728b1216bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
249 changed files with 15868 additions and 214 deletions

View File

@ -404,6 +404,43 @@ jobs:
shell: bash shell: bash
run: ARCH=${{ matrix.arch }} RUN_ON_CI=1 LLVM_CONFIG=llvm-config-${{env.MAIN_LLVM_VERSION}} ./scripts/test_fuzzer.sh ${{ matrix.fuzzer }} run: ARCH=${{ matrix.arch }} RUN_ON_CI=1 LLVM_CONFIG=llvm-config-${{env.MAIN_LLVM_VERSION}} ./scripts/test_fuzzer.sh ${{ matrix.fuzzer }}
librasan-build:
runs-on: ubuntu-24.04
needs:
- changes
if: ${{ needs.changes.outputs.qemu == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/librasan-prepare
- name: Build
if: runner.os == 'Linux'
shell: bash
run: |
RUN_ON_CI=1 \
LLVM_CONFIG=llvm-config-${{env.MAIN_LLVM_VERSION}} \
just \
-f ./libafl_qemu/librasan/Justfile \
build_everything_dev \
build_x86_64_release
librasan-test:
runs-on: ubuntu-24.04
needs:
- changes
if: ${{ needs.changes.outputs.qemu == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/librasan-prepare
- name: Build
if: runner.os == 'Linux'
shell: bash
run: |
RUN_ON_CI=1 \
LLVM_CONFIG=llvm-config-${{env.MAIN_LLVM_VERSION}} \
just \
-f ./libafl_qemu/librasan/Justfile \
test_everything
fuzzers-qemu-system: fuzzers-qemu-system:
needs: needs:
- changes - changes

View File

@ -0,0 +1,73 @@
name: Setup QEMU librasan environment
description: Sets up the QEMU librasan environment
runs:
using: composite
steps:
- name: Enable i386
shell: bash
run: sudo dpkg --add-architecture i386
- name: Install QEMU deps
shell: bash
run: |
sudo apt-get update && \
DEBIAN_FRONTEND=noninteractive \
sudo apt-get install -y \
build-essential \
clang-18 \
clang++-18 \
cmake \
curl \
g++-aarch64-linux-gnu \
g++-arm-linux-gnueabi \
g++-i686-linux-gnu \
g++-mipsel-linux-gnu \
g++-powerpc-linux-gnu \
gcc-aarch64-linux-gnu \
gcc-arm-linux-gnueabi \
gcc-i686-linux-gnu \
gcc-mipsel-linux-gnu \
gcc-powerpc-linux-gnu \
gdb \
gdb-multiarch \
git \
gnupg \
libc6-dev:i386 \
libclang-dev \
libgcc-13-dev:i386 \
libglib2.0-dev \
lsb-release \
ninja-build \
python3 \
python3-pip \
python3-venv \
qemu-user \
software-properties-common \
wget
- uses: dtolnay/rust-toolchain@nightly
- name: install just
uses: extractions/setup-just@v2
with:
just-version: 1.39.0
- name: Install cargo-binstall
shell: bash
run: |
curl -L --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | \
bash
- name: Install nextest
shell: bash
run: |
cargo binstall --no-confirm cargo-nextest
- name: Install Rust Targets
shell: bash
run: |
rustup target add armv7-unknown-linux-gnueabi && \
rustup target add aarch64-unknown-linux-gnu && \
rustup target add i686-unknown-linux-gnu && \
rustup target add powerpc-unknown-linux-gnu
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- uses: Swatinem/rust-cache@v2
with: { shared-key: "${{ runner.os }}-shared-fuzzer-cache" }

View File

@ -7,6 +7,10 @@ runs:
shell: bash shell: bash
run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential cmake run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential cmake
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: install just
uses: extractions/setup-just@v2
with:
just-version: 1.39.0
- name: Add stable clippy - name: Add stable clippy
shell: bash shell: bash
run: rustup toolchain install stable --component clippy --allow-downgrade run: rustup toolchain install stable --component clippy --allow-downgrade

View File

@ -96,6 +96,7 @@ cmake = "0.1.51"
document-features = "0.2.10" document-features = "0.2.10"
fastbloom = { version = "0.9.0", default-features = false } fastbloom = { version = "0.9.0", default-features = false }
hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible
just = "1.40.0"
libc = "0.2.159" # For (*nix) libc libc = "0.2.159" # For (*nix) libc
libipt = "0.3.0" libipt = "0.3.0"
log = "0.4.22" log = "0.4.22"

View File

@ -47,7 +47,10 @@ libafl = { path = "../../../libafl", features = ["tui_monitor"] }
libafl_bolts = { path = "../../../libafl_bolts", features = [ libafl_bolts = { path = "../../../libafl_bolts", features = [
"errors_backtrace", "errors_backtrace",
] } ] }
libafl_qemu = { path = "../../../libafl_qemu", features = ["usermode"] } libafl_qemu = { path = "../../../libafl_qemu", features = [
"usermode",
"rasan",
] }
libafl_targets = { path = "../../../libafl_targets" } libafl_targets = { path = "../../../libafl_targets" }
log = { version = "0.4.22", features = ["release_max_level_info"] } log = { version = "0.4.22", features = ["release_max_level_info"] }
nix = { version = "0.29.0", features = ["fs"] } nix = { version = "0.29.0", features = ["fs"] }

View File

@ -31,6 +31,10 @@ harness: libpng
[unix] [unix]
run: harness build run: harness build
#!/bin/bash
source {{ DOTENV }}
CUSTOM_QASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so \
{{ FUZZER }} \ {{ FUZZER }} \
--input ./corpus \ --input ./corpus \
--output {{ TARGET_DIR }}/output/ \ --output {{ TARGET_DIR }}/output/ \
@ -56,7 +60,12 @@ test_inner: harness build
# complie again with simple mgr # complie again with simple mgr
cargo build --profile={{PROFILE}} --features="simplemgr,{{ARCH}}" --target-dir={{ TARGET_DIR }} || exit 1 cargo build --profile={{PROFILE}} --features="simplemgr,{{ARCH}}" --target-dir={{ TARGET_DIR }} || exit 1
./tests/qasan/test.sh || exit 1
export CUSTOM_QASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so
./tests/qasan/qasan_test.sh || exit 1
export CUSTOM_GASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.so
./tests/qasan/gasan_test.sh || exit 1
[unix] [unix]
test: test:
@ -72,6 +81,10 @@ single: harness build
{{ HARNESS }} {{ HARNESS }}
asan: harness build asan: harness build
#!/bin/bash
source {{ DOTENV }}
CUSTOM_QASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so \
{{ FUZZER }} \ {{ FUZZER }} \
--input ./corpus \ --input ./corpus \
--output {{ TARGET_DIR }}/output/ \ --output {{ TARGET_DIR }}/output/ \
@ -82,6 +95,10 @@ asan: harness build
{{ HARNESS }} {{ HARNESS }}
asan_guest: harness build asan_guest: harness build
#!/bin/bash
source {{ DOTENV }}
CUSTOM_GASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.so \
{{ FUZZER }} \ {{ FUZZER }} \
--input ./corpus \ --input ./corpus \
--output {{ TARGET_DIR }}/output/ \ --output {{ TARGET_DIR }}/output/ \

View File

@ -126,6 +126,16 @@ impl Client<'_> {
unsafe { AsanModule::builder().env(&env).asan_report().build() } unsafe { AsanModule::builder().env(&env).asan_report().build() }
); );
instance_builder.build().run(args, modules, state)
} else if is_asan_guest {
let modules = tuple_list!(
DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build(),
AsanGuestModule::default(&env),
);
instance_builder.build().run(args, modules, state) instance_builder.build().run(args, modules, state)
} else { } else {
let modules = tuple_list!(DrCovModule::builder() let modules = tuple_list!(DrCovModule::builder()
@ -139,6 +149,10 @@ impl Client<'_> {
let modules = let modules =
tuple_list!(unsafe { AsanModule::builder().env(&env).asan_report().build() }); tuple_list!(unsafe { AsanModule::builder().env(&env).asan_report().build() });
instance_builder.build().run(args, modules, state)
} else if is_asan_guest {
let modules = tuple_list!(AsanGuestModule::default(&env));
instance_builder.build().run(args, modules, state) instance_builder.build().run(args, modules, state)
} else { } else {
let modules = tuple_list!(); let modules = tuple_list!();

View File

@ -8,9 +8,9 @@ use clap::Parser;
#[cfg(feature = "simplemgr")] #[cfg(feature = "simplemgr")]
use libafl::events::SimpleEventManager; use libafl::events::SimpleEventManager;
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager}; use libafl::events::{EventConfig, Launcher, LlmpEventManagerBuilder, MonitorTypedEventManager};
use libafl::{ use libafl::{
events::{ClientDescription, LlmpEventManagerBuilder}, events::ClientDescription,
monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
Error, Error,
}; };

View File

@ -44,7 +44,8 @@ use libafl_qemu::{
cmplog::CmpLogObserver, cmplog::CmpLogObserver,
edges::EdgeCoverageFullVariant, edges::EdgeCoverageFullVariant,
utils::filters::{HasAddressFilter, NopPageFilter, StdAddressFilter}, utils::filters::{HasAddressFilter, NopPageFilter, StdAddressFilter},
EdgeCoverageModule, EmulatorModuleTuple, SnapshotModule, StdEdgeCoverageModule, AsanGuestModule, EdgeCoverageModule, EmulatorModuleTuple, SnapshotModule,
StdEdgeCoverageModule,
}, },
Emulator, GuestAddr, Qemu, QemuExecutor, Emulator, GuestAddr, Qemu, QemuExecutor,
}; };
@ -153,7 +154,7 @@ impl<M: Monitor> Instance<'_, M> {
.map_observer(edges_observer.as_mut()) .map_observer(edges_observer.as_mut())
.build()?; .build()?;
let mut snapshot_module = SnapshotModule::new(); let mut snapshot_module = SnapshotModule::with_filters(AsanGuestModule::snapshot_filters());
/* /*
* Since the generics for the modules are already excessive when taking * Since the generics for the modules are already excessive when taking

View File

@ -0,0 +1 @@
/qasan

View File

@ -0,0 +1,75 @@
#!/bin/bash
set -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
if [[ ! -x "$QEMU_LAUNCHER" ]]; then
echo "env variable QEMU_LAUNCHER does not point to a valid executable"
echo "QEMU_LAUNCHER should point to qemu_launcher"
exit 1
fi
cd "$SCRIPT_DIR"
make
tests=(
"overflow"
"underflow"
"double_free"
"memset"
"uaf"
"test_limits"
)
tests_expected=(
"Segmentation fault"
"Segmentation fault"
"Panic!"
"Panic!"
"Segmentation fault"
"Test-Limits - No Error"
)
tests_not_expected=(
"dummy"
"dummy"
"dummy"
"dummy"
"dummy"
"Context:"
)
# We don't want any core dumps. They can potentially be quite large
ulimit -c 0
for i in "${!tests[@]}"
do
test="${tests[i]}"
expected="${tests_expected[i]}"
not_expected="${tests_not_expected[i]}"
echo "Running $test detection test..."
OUT=$("$QEMU_LAUNCHER" \
-r "inputs/$test.txt" \
--input dummy \
--output out \
--asan-guest-cores 0 \
--cores 0 \
-- qasan 2>&1 | tr -d '\0')
echo "$OUT"
if ! echo "$OUT" | grep -q "$expected"; then
echo "ERROR: Expected: $expected."
echo "Output is:"
echo "$OUT"
exit 1
elif echo "$OUT" | grep -q "$not_expected"; then
echo "ERROR: Did not expect: $not_expected."
echo "Output is:"
echo "$OUT"
exit 1
else
echo "OK."
fi
done

View File

@ -71,13 +71,7 @@ int main(int argc, char **argv) {
char input = '\0'; char input = '\0';
// if (read(STDIN_FILENO, &input, 1) < 0) { if (argc > 1) { input = argv[1][0]; }
// LOG("Failed to read stdin\n");
// return 1;
// }
LLVMFuzzerTestOneInput(&input, 1); LLVMFuzzerTestOneInput(&input, 1);
LOG("DONE\n"); LOG("DONE\n");

View File

@ -22,11 +22,11 @@ tests=(
) )
tests_expected=( tests_expected=(
"is 0 bytes to the right of the 10-byte chunk" "AddressSanitizer Error"
"is 1 bytes to the left of the 10-byte chunk" "AddressSanitizer Error"
"is 0 bytes inside the 10-byte chunk" "Panic!"
"Invalid 11 bytes write at" "AddressSanitizer Error"
"is 0 bytes inside the 10-byte chunk" "AddressSanitizer Error"
"Test-Limits - No Error" "Test-Limits - No Error"
) )
@ -39,6 +39,9 @@ tests_not_expected=(
"Context:" "Context:"
) )
# We don't want any core dumps. They can potentially be quite large
ulimit -c 0
for i in "${!tests[@]}" for i in "${!tests[@]}"
do do
test="${tests[i]}" test="${tests[i]}"

View File

@ -1,3 +1,7 @@
CROSS_TARGET="aarch64-unknown-linux-gnu"
CROSS_CC="aarch64-linux-gnu-gcc" CROSS_CC="aarch64-linux-gnu-gcc"
CROSS_CXX="aarch64-linux-gnu-g++" CROSS_CXX="aarch64-linux-gnu-g++"
CROSS_OBJCOPY="aarch64-linux-gnu-objcopy"
CROSS_STRIP="aarch64-linux-gnu-strip"
CROSS_CFLAGS="" CROSS_CFLAGS=""
LIBRASAN_CFLAGS="-no-pie -fno-stack-protector"

View File

@ -1,3 +1,7 @@
CROSS_TARGET="armv7-unknown-linux-gnueabi"
CROSS_CC="arm-linux-gnueabi-gcc" CROSS_CC="arm-linux-gnueabi-gcc"
CROSS_CXX="arm-linux-gnueabi-g++" CROSS_CXX="arm-linux-gnueabi-g++"
CROSS_OBJCOPY="arm-linux-gnueabi-objcopy"
CROSS_STRIP="arm-linux-gnueabi-strip"
CROSS_CFLAGS="" CROSS_CFLAGS=""
LIBRASAN_CFLAGS="-no-pie -fno-stack-protector -marm"

View File

@ -1,3 +1,7 @@
CROSS_TARGET="i686-unknown-linux-gnu"
CROSS_CC="i686-linux-gnu-gcc" CROSS_CC="i686-linux-gnu-gcc"
CROSS_CXX="i686-linux-gnu-g++" CROSS_CXX="i686-linux-gnu-g++"
CROSS_OBJCOPY="i686-linux-gnu-objcopy"
CROSS_STRIP="i686-linux-gnu-strip"
CROSS_CFLAGS="-m32" CROSS_CFLAGS="-m32"
LIBRASAN_CFLAGS="-m32 -no-pie -fno-stack-protector"

View File

@ -1,3 +1,7 @@
CROSS_TARGET="powerpc-unknown-linux-gnu"
CROSS_CC="powerpc-linux-gnu-gcc" CROSS_CC="powerpc-linux-gnu-gcc"
CROSS_CXX="powerpc-linux-gnu-g++" CROSS_CXX="powerpc-linux-gnu-g++"
CROSS_OBJCOPY="powerpc-linux-gnu-objcopy"
CROSS_STRIP="powerpc-linux-gnu-strip"
CROSS_CFLAGS="" CROSS_CFLAGS=""
LIBRASAN_CFLAGS="-no-pie -fno-stack-protector"

View File

@ -1,3 +1,7 @@
CROSS_TARGET="x86_64-unknown-linux-gnu"
CROSS_CC="x86_64-linux-gnu-gcc" CROSS_CC="x86_64-linux-gnu-gcc"
CROSS_CXX="x86_64-linux-gnu-g++" CROSS_CXX="x86_64-linux-gnu-g++"
CROSS_OBJCOPY="x86_64-linux-gnu-objcopy"
CROSS_STRIP="x86_64-linux-gnu-strip"
CROSS_CFLAGS="" CROSS_CFLAGS=""
LIBRASAN_CFLAGS="-m64 -no-pie -Wl,--no-relax -mcmodel=large -fno-stack-protector"

View File

@ -2,13 +2,10 @@ import "libafl-qemu.just"
# Useful rules to build libpng for multiple architecture. # Useful rules to build libpng for multiple architecture.
ARCH := env("ARCH", "x86_64")
OPTIMIZATIONS := env("OPTIMIZATIONS", if ARCH == "ppc" { "no" } else { "yes" }) OPTIMIZATIONS := env("OPTIMIZATIONS", if ARCH == "ppc" { "no" } else { "yes" })
DEPS_DIR := TARGET_DIR / "deps" DEPS_DIR := TARGET_DIR / "deps"
DOTENV := source_directory() / "envs" / ".env." + ARCH
[unix] [unix]
target_dir: target_dir:
mkdir -p {{ TARGET_DIR }} mkdir -p {{ TARGET_DIR }}

View File

@ -1,3 +1,5 @@
import "libafl.just" import "libafl.just"
export LIBAFL_QEMU_DIR_DEFAULT := BUILD_DIR / "qemu-libafl-bridge" export LIBAFL_QEMU_DIR_DEFAULT := BUILD_DIR / "qemu-libafl-bridge"
ARCH := env("ARCH", "x86_64")
DOTENV := source_directory() / "envs" / ".env." + ARCH

View File

@ -52,6 +52,8 @@ fork = ["libafl/fork"]
## Build libqasan for address sanitization ## Build libqasan for address sanitization
build_libgasan = [] build_libgasan = []
build_libqasan = [] build_libqasan = []
## Use Address Sanitizer implementation written in rust (rather than the older C version)
rasan = []
#! ## The following architecture features are mutually exclusive. #! ## The following architecture features are mutually exclusive.
@ -140,6 +142,7 @@ pyo3-build-config = { workspace = true, optional = true }
rustversion = { workspace = true } rustversion = { workspace = true }
bindgen = { workspace = true } bindgen = { workspace = true }
cc = { workspace = true } cc = { workspace = true }
just = { workspace = true }
[lib] [lib]
name = "libafl_qemu" name = "libafl_qemu"

View File

@ -212,7 +212,9 @@ pub fn build() {
nyx_bindings_file.as_path(), nyx_bindings_file.as_path(),
); );
if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) { let rasan = cfg!(feature = "rasan");
if cfg!(feature = "usermode") && !rasan && (qemu_asan || qemu_asan_guest) {
let qasan_dir = Path::new("libqasan"); let qasan_dir = Path::new("libqasan");
let qasan_dir = fs::canonicalize(qasan_dir).unwrap(); let qasan_dir = fs::canonicalize(qasan_dir).unwrap();
println!("cargo:rerun-if-changed={}", qasan_dir.display()); println!("cargo:rerun-if-changed={}", qasan_dir.display());
@ -232,4 +234,42 @@ pub fn build() {
.success() .success()
); );
} }
if cfg!(feature = "usermode") && rasan {
let rasan_dir = Path::new("librasan");
let rasan_dir = fs::canonicalize(rasan_dir).unwrap();
let just_file = rasan_dir.join("Justfile");
println!("cargo:rerun-if-changed={}", rasan_dir.display());
println!("cargo:rerun-if-changed={}", just_file.display());
let rasan_dir_str = rasan_dir.to_str().unwrap();
let just_file_str = just_file.to_str().unwrap();
let target_dir_str = target_dir.to_str().unwrap();
let profile = if cfg!(debug_assertions) {
"dev"
} else {
"release"
};
let gasan_args = [
"just",
"-d", rasan_dir_str,
"-f", just_file_str,
"--set", "ARCH", &cpu_target,
"--set", "PROFILE", profile,
"--set", "TARGET_DIR", target_dir_str,
"build_gasan"];
just::run(gasan_args.iter()).expect("Failed to build rust guest address sanitizer library");
let qasan_args = [
"just",
"-d", rasan_dir_str,
"-f", just_file_str,
"--set", "ARCH", &cpu_target,
"--set", "PROFILE", profile,
"--set", "TARGET_DIR", target_dir_str,
"build_qasan"];
just::run(qasan_args.iter()).expect("Failed to build rust address sanitizer library");
}
} }

View File

@ -1,6 +1,8 @@
#[cfg(feature = "python")] #[cfg(feature = "python")]
use core::convert::Infallible; use core::convert::Infallible;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use core::fmt::{self, Display, Formatter};
#[cfg(target_os = "linux")]
use core::{slice::from_raw_parts, str::from_utf8_unchecked}; use core::{slice::from_raw_parts, str::from_utf8_unchecked};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -34,6 +36,46 @@ pub struct MapInfo {
is_priv: i32, is_priv: i32,
} }
#[cfg(target_os = "linux")]
impl Display for MapInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:016x}-{:016x} , ", self.start, self.end)?;
write!(
f,
"{}",
if self.flags & i32::from(MmapPerms::Read) == i32::from(MmapPerms::Read) {
"r"
} else {
"-"
}
)?;
write!(
f,
"{}",
if self.flags & i32::from(MmapPerms::Write) == i32::from(MmapPerms::Write) {
"w"
} else {
"-"
}
)?;
write!(
f,
"{}",
if self.flags & i32::from(MmapPerms::Execute) == i32::from(MmapPerms::Execute) {
"x"
} else {
"-"
}
)?;
write!(f, "{}", if self.is_priv == 0 { "s" } else { "p" })?;
write!(f, " {:10}", self.offset)?;
if let Some(path) = &self.path {
write!(f, " {path}")?;
}
Ok(())
}
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[cfg_attr(feature = "python", pymethods)] #[cfg_attr(feature = "python", pymethods)]
impl MapInfo { impl MapInfo {

View File

@ -0,0 +1,15 @@
[target.i686-unknown-linux-gnu]
linker = "i686-linux-gnu-gcc"
[target.armv7-unknown-linux-gnueabi]
linker = "arm-linux-gnueabi-gcc"
runner = "qemu-arm -L /usr/arm-linux-gnueabi/"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
runner = "qemu-aarch64 -L /usr/aarch64-linux-gnu/"
[target.powerpc-unknown-linux-gnu]
linker = "powerpc-linux-gnu-gcc"
runner = "qemu-ppc -L /usr/powerpc-linux-gnu/"

2
libafl_qemu/librasan/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
core

View File

@ -0,0 +1,19 @@
[workspace]
members = ["asan", "gasan", "qasan", "zasan", "fuzz", "dummy_libc", "runner"]
resolver = "2"
[workspace.package]
version = "0.15.1"
license = "MIT OR Apache-2.0"
edition = "2024"
rust-version = "1.85"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
debug = true

View File

@ -0,0 +1,140 @@
import "../../just/libafl-qemu.just"
import "asan/Justfile"
import "dummy_libc/Justfile"
import "fuzz/Justfile"
import "gasan/Justfile"
import "qasan/Justfile"
import "runner/Justfile"
import "zasan/Justfile"
import "fuzzer_name.just"
build: build_asan build_dummy build_fuzz build_gasan build_qasan build_runner build_zasan
test: test_asan
pretty_rust:
#!/bin/bash
MAIN_LLVM_VERSION=$LLVM_VERSION cargo run --manifest-path ../../utils/libafl_fmt/Cargo.toml --release -- -v
pretty_toml:
#!/bin/bash
taplo fmt
pretty: pretty_rust pretty_toml
fix: fix_asan fix_dummy fix_fuzz fix_gasan fix_qasan fix_runner fix_zasan
clippy:
#!/bin/bash
cargo clippy
doc:
#!/bin/bash
cargo doc
all: fix pretty build test clippy doc
build_arm_dev:
#!/bin/bash
ARCH=arm PROFILE=dev just build
build_aarch64_dev:
#!/bin/bash
ARCH=aarch64 PROFILE=dev just build
build_x86_64_dev:
#!/bin/bash
ARCH=x86_64 PROFILE=dev just build
build_i386_dev:
#!/bin/bash
ARCH=i386 PROFILE=dev just build
build_ppc_dev:
#!/bin/bash
ARCH=ppc PROFILE=dev just build
build_arm_release:
#!/bin/bash
ARCH=arm PROFILE=release just build
build_aarch64_release:
#!/bin/bash
ARCH=aarch64 PROFILE=release just build
build_x86_64_release:
#!/bin/bash
ARCH=x86_64 PROFILE=release just build
build_i386_release:
#!/bin/bash
ARCH=i386 PROFILE=release just build
build_ppc_release:
#!/bin/bash
ARCH=ppc PROFILE=release just build
build_everything_dev: \
build_arm_dev \
build_aarch64_dev \
build_x86_64_dev \
build_i386_dev \
build_ppc_dev \
build_everything_release: \
build_arm_dev \
build_aarch64_dev \
build_x86_64_dev \
build_i386_dev \
build_ppc_dev \
build_everything: build_everything_dev build_everything_release
test_arm:
#!/bin/bash
ARCH=arm \
PROFILE=dev \
RUSTLOG=debug \
RUST_BACKTRACE=full \
just test
test_aarch64:
#!/bin/bash
ARCH=aarch64 \
PROFILE=dev \
RUSTLOG=debug \
RUST_BACKTRACE=full \
just test
test_x86_64:
#!/bin/bash
ARCH=x86_64 \
PROFILE=dev \
RUSTLOG=debug \
RUST_BACKTRACE=full \
just test
test_i386:
#!/bin/bash
ARCH=i386 \
PROFILE=dev \
RUSTLOG=debug \
RUST_BACKTRACE=full \
just test
test_ppc:
#!/bin/bash
ARCH=ppc \
PROFILE=dev \
RUSTLOG=debug \
RUST_BACKTRACE=full \
just test
test_everything: \
test_arm \
test_aarch64 \
test_x86_64 \
test_i386 \
test_ppc
everything: build_everything test_everything clippy

View File

@ -0,0 +1,38 @@
# rasan
`rasan` is a library intended to be used by a guest running in QEMU to
support address sanitizer.
It has a modular design intended to support different use cases and
environments. The following initial variants are implemented:
- `qasan` - Intended as a drop in replacement for the original libqasan,
this will interact with QEMU using the bespoke syscall interface to perform
memory tracking and shadow mapping.
- `gasan` - This is similar to `qasan`, but rather than having QEMU perform
the management of the shadow memory and memory tracking, this work will be
carried out purely in the guest (and hence should be more performant).
- `zasan` - This variant is intended to have no dependencies on libc, nor
any other libraries. It is intended to be used as a starting point for
bare-metal targets or targets which have statically linked `libc`.
The componentized nature of the design is intended to permit the user to
adapt `rasan` to their needs with minimal modification by selecting and
combining alternative implementations of the various key components.
## Features
- `dlmalloc` - Enable support for the dlmalloc allocator backend.
- `guest` - Enable support for shadow memory and tracking in the guest
- `host` - Enable support for shadow memory and tracking in the host
- `libc` - Enable use of `LibcMmap` to support creation of mappings using
`libc`
- `linux` - Enable use of `LinuxMmap` to support creation of mappings and
host interaction using `rustix`.
- `std` - Disable the magic used to support `no_std` environments
## Testing
This project makes use of a number of unit and integration tests to validate the
implementation.
## Fuzzing
The project also includes a couple of fuzzing harnesses supported by
`cargo-fuzz` in order to supplement unit and integration tests.

View File

@ -0,0 +1,64 @@
[package]
name = "asan"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
crate-type = ["rlib"]
[features]
default = [
"dlmalloc",
"guest",
"hooks",
"host",
"libc",
"linux",
"mimalloc",
"test",
"tracking",
]
dlmalloc = ["dep:dlmalloc"]
guest = []
hooks = []
host = ["dep:syscalls"]
libc = ["dep:libc"]
linux = ["dep:rustix"]
mimalloc = ["dep:baby-mimalloc"]
test = []
tracking = []
[dependencies]
baby-mimalloc = { version = "0.2.1", default-features = false, features = [
"spin_mutex",
], optional = true }
bitflags = { version = "2.8.0", default-features = false }
dlmalloc = { version = "0.2.7", default-features = false, optional = true }
itertools = { version = "0.14.0", default-features = false }
log = { version = "0.4.22", default-features = false, features = [
"release_max_level_info",
] }
libc = { version = "0.2.169", default-features = false, optional = true }
readonly = { version = "0.2.12", default-features = false }
rustix = { version = "1.0.0", default-features = false, features = [
"fs",
"mm",
"process",
"stdio",
], optional = true }
spin = { version = "0.9.8", default-features = false, features = [
"lazy",
"mutex",
"spin_mutex",
] }
syscalls = { version = "0.6.18", default-features = false, optional = true }
thiserror = { version = "2.0.11", default-features = false }
[build-dependencies]
cc = { version = "1.2.13" }
[dev-dependencies]
env_logger = { version = "0.11.6" }
mockall = { version = "0.13.1" }
widestring = { version = "1.1.0" }

View File

@ -0,0 +1,43 @@
import "../../../just/libafl-qemu.just"
import "../fuzzer_name.just"
ASAN_SOURCE_DIR := source_directory()
[unix]
compile_asan:
#!/bin/bash
source {{ DOTENV }}
cargo \
+nightly \
build \
--package asan \
--target ${CROSS_TARGET} \
--profile {{ PROFILE }} \
--target-dir {{ TARGET_DIR }}
[unix]
fix_asan:
#!/bin/bash
source {{ DOTENV }}
cargo \
+nightly \
fix \
--package asan \
--target ${CROSS_TARGET} \
--profile {{ PROFILE }} \
--target-dir {{ TARGET_DIR }} \
--allow-dirty
[unix]
test_asan:
#!/bin/bash
source {{ DOTENV }}
cd {{ ASAN_SOURCE_DIR }}
cargo \
+nightly \
nextest \
run \
--target ${CROSS_TARGET}
[unix]
build_asan: compile_asan

View File

@ -0,0 +1,45 @@
fn main() {
println!("cargo:rerun-if-changed=cc/include/hooks.h");
println!("cargo:rerun-if-changed=cc/include/trace.h");
println!("cargo:rerun-if-changed=cc/include/printf.h");
println!("cargo:rerun-if-changed=cc/src/asprintf.c");
println!("cargo:rerun-if-changed=cc/src/log.c");
println!("cargo:rerun-if-changed=cc/src/printf.c");
println!("cargo:rerun-if-changed=cc/src/vasprintf.c");
cc::Build::new()
.define("_GNU_SOURCE", None)
.flag("-Werror")
.flag("-fno-stack-protector")
.flag("-ffunction-sections")
.include("cc/include/")
.file("cc/src/asprintf.c")
.compile("asprintf");
cc::Build::new()
.define("_GNU_SOURCE", None)
.flag("-Werror")
.flag("-fno-stack-protector")
.flag("-ffunction-sections")
.include("cc/include/")
.file("cc/src/log.c")
.compile("log");
cc::Build::new()
.define("_GNU_SOURCE", None)
.flag("-Werror")
.flag("-fno-stack-protector")
.flag("-ffunction-sections")
.include("cc/include/")
.file("cc/src/printf.c")
.compile("printf");
cc::Build::new()
.define("_GNU_SOURCE", None)
.flag("-Werror")
.flag("-fno-stack-protector")
.flag("-ffunction-sections")
.include("cc/include/")
.file("cc/src/vasprintf.c")
.compile("vasprintf");
}

View File

@ -0,0 +1,9 @@
#include <stddef.h>
void asan_load(const void *addr, size_t size);
void asan_store(const void *addr, size_t size);
void *asan_alloc(size_t len, size_t align);
void asan_dealloc(const void *addr);
size_t asan_get_size(const void *addr);
size_t asan_sym(const char *name);
size_t asan_page_size();

View File

@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed
// on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Output a character to a custom device like UART, used by the printf()
* function This function is declared here only. You have to write your custom
* implementation somewhere \param character Character to output
*/
void _putchar(char character);
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* To avoid conflicts with the regular printf() API it is overridden by macro
* defines and internal underscore-appended functions like printf_() are used
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not
* counting the terminating null character
*/
#define printf printf_
int printf_(const char *format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING
* (V)SNPRINTF INSTEAD! \param buffer A pointer to the buffer where to store the
* formatted string. MUST be big enough to store the output! \param format A
* string that specifies the format of the output \return The number of
* characters that are WRITTEN into the buffer, not counting the terminating
* null character
*/
#define sprintf sprintf_
int sprintf_(char *buffer, const char *format, ...);
/**
* Tiny snprintf/vsnprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer,
* including a terminating null character \param format A string that specifies
* the format of the output \param va A value identifying a variable arguments
* list \return The number of characters that COULD have been written into the
* buffer, not counting the terminating null character. A value equal or larger
* than count indicates truncation. Only when the returned value is non-negative
* and less than count, the string has been completely written.
*/
#define snprintf snprintf_
#define vsnprintf vsnprintf_
int snprintf_(char *buffer, size_t count, const char *format, ...);
int vsnprintf_(char *buffer, size_t count, const char *format, va_list va);
/**
* Tiny vprintf implementation
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
* \return The number of characters that are WRITTEN into the buffer, not
* counting the terminating null character
*/
#define vprintf vprintf_
int vprintf_(const char *format, va_list va);
/**
* printf with output function
* You may use this as dynamic alternative to printf() with its fixed _putchar()
* output \param out An output function which takes one character and an
* argument pointer \param arg An argument pointer for user data passed to
* output function \param format A string that specifies the format of the
* output \return The number of characters that are sent to the output function,
* not counting the terminating null character
*/
int fctprintf(void (*out)(char character, void *arg), void *arg,
const char *format, ...);
#ifdef __cplusplus
}
#endif
#endif // _PRINTF_H_

View File

@ -0,0 +1,6 @@
#ifndef _LOG_H_
#define _LOG_H_
void trace(const char *fmt, ...);
#endif

View File

@ -0,0 +1,26 @@
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include "hooks.h"
#include "printf.h"
#include "trace.h"
int asprintf(char **restrict strp, const char *restrict fmt, ...) {
trace("asprintf - strp: %p, fmt: %p\n", strp, fmt);
if (strp == NULL) { return -1; }
if (fmt == NULL) { return -1; }
va_list va;
va_start(va, fmt);
int len = vsnprintf_(NULL, 0, fmt, va);
va_end(va);
if (len < 0) { return -1; }
void *buffer = asan_alloc(len + 1, 0);
if (buffer == NULL) { return -1; }
*strp = buffer;
return vsnprintf_(buffer, len, fmt, va);
}

View File

@ -0,0 +1,21 @@
#include <limits.h>
#include <stdarg.h>
#include "printf.h"
static char log_buffer[PATH_MAX] = {0};
extern void log_trace(char *msg);
void trace(const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int len = vsnprintf_(log_buffer, sizeof(log_buffer), fmt, va);
if (len > 0) { log_trace(log_buffer); }
va_end(va);
}
#ifdef __powerpc__
void _putchar(char c) {
(void)c;
}
#endif

View File

@ -0,0 +1,924 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for
// speed on
// embedded systems with a very limited resources. These routines are
// thread safe and reentrant! Use this instead of the bloated
// standard/newlib printf cause these use malloc for printf (and may not
// be thread safe).
//
///////////////////////////////////////////////////////////////////////////////
#include <stdbool.h>
#include <stdint.h>
#include "printf.h"
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
// default: undefined
#ifdef PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_NTOA_BUFFER_SIZE
#define PRINTF_NTOA_BUFFER_SIZE 32U
#endif
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_FTOA_BUFFER_SIZE
#define PRINTF_FTOA_BUFFER_SIZE 32U
#endif
// support for the floating point type (%f)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
#define PRINTF_SUPPORT_FLOAT
#endif
// support for exponential floating point notation (%e/%g)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
#define PRINTF_SUPPORT_EXPONENTIAL
#endif
// define the default floating point precision
// default: 6 digits
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
#endif
// define the largest float suitable to print with %f
// default: 1e9
#ifndef PRINTF_MAX_FLOAT
#define PRINTF_MAX_FLOAT 1e9
#endif
// support for the long long types (%llu or %p)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG
#endif
// support for the ptrdiff_t type (%t)
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
#define PRINTF_SUPPORT_PTRDIFF_T
#endif
///////////////////////////////////////////////////////////////////////////////
// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_LONG (1U << 8U)
#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
#define FLAGS_ADAPT_EXP (1U << 11U)
// import float.h for DBL_MAX
#if defined(PRINTF_SUPPORT_FLOAT)
#include <float.h>
#endif
// output function type
typedef void (*out_fct_type)(char character, void *buffer, size_t idx,
size_t maxlen);
// wrapper (used as buffer) for output function type
typedef struct {
void (*fct)(char character, void *arg);
void *arg;
} out_fct_wrap_type;
// internal buffer output
static inline void _out_buffer(char character, void *buffer, size_t idx,
size_t maxlen) {
if (idx < maxlen) { ((char *)buffer)[idx] = character; }
}
// internal null output
static inline void _out_null(char character, void *buffer, size_t idx,
size_t maxlen) {
(void)character;
(void)buffer;
(void)idx;
(void)maxlen;
}
// internal _putchar wrapper
static inline void _out_char(char character, void *buffer, size_t idx,
size_t maxlen) {
(void)buffer;
(void)idx;
(void)maxlen;
if (character) { _putchar(character); }
}
// internal output function wrapper
static inline void _out_fct(char character, void *buffer, size_t idx,
size_t maxlen) {
(void)idx;
(void)maxlen;
if (character) {
// buffer is the output fct pointer
((out_fct_wrap_type *)buffer)
->fct(character, ((out_fct_wrap_type *)buffer)->arg);
}
}
// internal secure strlen
// \return The length of the string (excluding the terminating 0) limited by
// 'maxsize'
static inline unsigned int _strnlen_s(const char *str, size_t maxsize) {
const char *s;
for (s = str; *s && maxsize--; ++s)
;
return (unsigned int)(s - str);
}
// internal test if char is a digit (0-9)
// \return true if char is a digit
static inline bool _is_digit(char ch) {
return (ch >= '0') && (ch <= '9');
}
// internal ASCII string to unsigned int conversion
static unsigned int _atoi(const char **str) {
unsigned int i = 0U;
while (_is_digit(**str)) {
i = i * 10U + (unsigned int)(*((*str)++) - '0');
}
return i;
}
// output the specified string in reverse, taking care of any zero-padding
static size_t _out_rev(out_fct_type out, char *buffer, size_t idx,
size_t maxlen, const char *buf, size_t len,
unsigned int width, unsigned int flags) {
const size_t start_idx = idx;
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (size_t i = len; i < width; i++) {
out(' ', buffer, idx++, maxlen);
}
}
// reverse string
while (len) {
out(buf[--len], buffer, idx++, maxlen);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) {
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
// internal itoa format
static size_t _ntoa_format(out_fct_type out, char *buffer, size_t idx,
size_t maxlen, char *buf, size_t len, bool negative,
unsigned int base, unsigned int prec,
unsigned int width, unsigned int flags) {
// pad leading zeros
if (!(flags & FLAGS_LEFT)) {
if (width && (flags & FLAGS_ZEROPAD) &&
(negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
while ((flags & FLAGS_ZEROPAD) && (len < width) &&
(len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
// handle hash
if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len &&
((len == prec) || (len == width))) {
len--;
if (len && (base == 16U)) { len--; }
}
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) &&
(len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'x';
} else if ((base == 16U) && (flags & FLAGS_UPPERCASE) &&
(len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'X';
} else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'b';
}
if (len < PRINTF_NTOA_BUFFER_SIZE) { buf[len++] = '0'; }
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
} else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
} else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
// internal itoa for 'long' type
static size_t _ntoa_long(out_fct_type out, char *buffer, size_t idx,
size_t maxlen, unsigned long value, bool negative,
unsigned long base, unsigned int prec,
unsigned int width, unsigned int flags) {
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) { flags &= ~FLAGS_HASH; }
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10
? '0' + digit
: (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative,
(unsigned int)base, prec, width, flags);
}
// internal itoa for 'long long' type
#if defined(PRINTF_SUPPORT_LONG_LONG)
static size_t _ntoa_long_long(out_fct_type out, char *buffer, size_t idx,
size_t maxlen, unsigned long long value,
bool negative, unsigned long long base,
unsigned int prec, unsigned int width,
unsigned int flags) {
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) { flags &= ~FLAGS_HASH; }
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10
? '0' + digit
: (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative,
(unsigned int)base, prec, width, flags);
}
#endif // PRINTF_SUPPORT_LONG_LONG
#if defined(PRINTF_SUPPORT_FLOAT)
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// forward declaration so that _ftoa can switch to exp notation for values >
// PRINTF_MAX_FLOAT
static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen,
double value, unsigned int prec, unsigned int width,
unsigned int flags);
#endif
// internal ftoa for fixed decimal floating point
static size_t _ftoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen,
double value, unsigned int prec, unsigned int width,
unsigned int flags) {
char buf[PRINTF_FTOA_BUFFER_SIZE];
size_t len = 0U;
double diff = 0.0;
// powers of 10
static const double pow10[] = {1, 10, 100, 1000,
10000, 100000, 1000000, 10000000,
100000000, 1000000000};
// test for special values
if (value != value)
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
if (value < -DBL_MAX)
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
if (value > DBL_MAX)
return _out_rev(out, buffer, idx, maxlen,
(flags & FLAGS_PLUS) ? "fni+" : "fni",
(flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
// test for very large values
// standard printf behavior is to print EVERY whole number digit -- which
// could be 100s of characters overflowing your buffers == bad
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
#else
return 0U;
#endif
}
// test for negative
bool negative = false;
if (value < 0) {
negative = true;
value = 0 - value;
}
// set default precision, if not set explicitly
if (!(flags & FLAGS_PRECISION)) { prec = PRINTF_DEFAULT_FLOAT_PRECISION; }
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
buf[len++] = '0';
prec--;
}
int whole = (int)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
diff = tmp - frac;
if (diff > 0.5) {
++frac;
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
if (frac >= pow10[prec]) {
frac = 0;
++whole;
}
} else if (diff < 0.5) {
} else if ((frac == 0U) || (frac & 1U)) {
// if halfway, round up if odd OR if last digit is 0
++frac;
}
if (prec == 0U) {
diff = value - (double)whole;
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++whole;
}
} else {
unsigned int count = prec;
// now do fractional part, as an unsigned number
while (len < PRINTF_FTOA_BUFFER_SIZE) {
--count;
buf[len++] = (char)(48U + (frac % 10U));
if (!(frac /= 10U)) { break; }
}
// add extra 0s
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
// add decimal
buf[len++] = '.';
}
}
// do whole part, number is reversed
while (len < PRINTF_FTOA_BUFFER_SIZE) {
buf[len++] = (char)(48 + (whole % 10));
if (!(whole /= 10)) { break; }
}
// pad leading zeros
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
} else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
} else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// internal ftoa variant for exponential floating-point type, contributed by
// Martijn Jasperse <m.jasperse@gmail.com>
static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen,
double value, unsigned int prec, unsigned int width,
unsigned int flags) {
// check for NaN and special values
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
}
// determine the sign
const bool negative = value < 0;
if (negative) { value = -value; }
// default precision
if (!(flags & FLAGS_PRECISION)) { prec = PRINTF_DEFAULT_FLOAT_PRECISION; }
// determine the decimal exponent
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
union {
uint64_t U;
double F;
} conv;
conv.F = value;
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
conv.U = (conv.U & ((1ULL << 52U) - 1U)) |
(1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
// now approximate log10 from the log2 integer part and an expansion of ln
// around 1.5
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 +
(conv.F - 1.5) * 0.289529654602168);
// now we want to compute 10^expval but we want to be sure it won't overflow
exp2 = (int)(expval * 3.321928094887362 + 0.5);
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
const double z2 = z * z;
conv.U = (uint64_t)(exp2 + 1023) << 52U;
// compute exp(z) using continued fractions, see
// https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
// correct for rounding errors
if (value < conv.F) {
expval--;
conv.F /= 10;
}
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5
// characters
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
// in "%g" mode, "prec" is the number of *significant figures* not decimals
if (flags & FLAGS_ADAPT_EXP) {
// do we want to fall-back to "%f" mode?
if ((value >= 1e-4) && (value < 1e6)) {
if ((int)prec > expval) {
prec = (unsigned)((int)prec - expval - 1);
} else {
prec = 0;
}
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
// no characters in exponent
minwidth = 0U;
expval = 0;
} else {
// we use one sigfig for the whole part
if ((prec > 0) && (flags & FLAGS_PRECISION)) { --prec; }
}
}
// will everything fit?
unsigned int fwidth = width;
if (width > minwidth) {
// we didn't fall-back so subtract the characters required for the exponent
fwidth -= minwidth;
} else {
// not enough characters, so go back to default sizing
fwidth = 0U;
}
if ((flags & FLAGS_LEFT) && minwidth) {
// if we're padding on the right, DON'T pad the floating part
fwidth = 0U;
}
// rescale the float value
if (expval) { value /= conv.F; }
// output the floating part
const size_t start_idx = idx;
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth,
flags & ~FLAGS_ADAPT_EXP);
// output the exponent part
if (minwidth) {
// output the exponential symbol
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
// output the exponent value
idx =
_ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval,
expval < 0, 10, 0, minwidth - 1, FLAGS_ZEROPAD | FLAGS_PLUS);
// might need to right-pad spaces
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width)
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
// internal vsnprintf
static int _vsnprintf(out_fct_type out, char *buffer, const size_t maxlen,
const char *format, va_list va) {
unsigned int flags, width, precision, n;
size_t idx = 0U;
if (!buffer) {
// use null output function
out = _out_null;
}
while (*format) {
// format specifier? %[flags][width][.precision][length]
if (*format != '%') {
// no
out(*format, buffer, idx++, maxlen);
format++;
continue;
} else {
// yes, evaluate it
format++;
}
// evaluate flags
flags = 0U;
do {
switch (*format) {
case '0':
flags |= FLAGS_ZEROPAD;
format++;
n = 1U;
break;
case '-':
flags |= FLAGS_LEFT;
format++;
n = 1U;
break;
case '+':
flags |= FLAGS_PLUS;
format++;
n = 1U;
break;
case ' ':
flags |= FLAGS_SPACE;
format++;
n = 1U;
break;
case '#':
flags |= FLAGS_HASH;
format++;
n = 1U;
break;
default:
n = 0U;
break;
}
} while (n);
// evaluate width field
width = 0U;
if (_is_digit(*format)) {
width = _atoi(&format);
} else if (*format == '*') {
const int w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = (unsigned int)-w;
} else {
width = (unsigned int)w;
}
format++;
}
// evaluate precision field
precision = 0U;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (_is_digit(*format)) {
precision = _atoi(&format);
} else if (*format == '*') {
const int prec = (int)va_arg(va, int);
precision = prec > 0 ? (unsigned int)prec : 0U;
format++;
}
}
// evaluate length field
switch (*format) {
case 'l':
flags |= FLAGS_LONG;
format++;
if (*format == 'l') {
flags |= FLAGS_LONG_LONG;
format++;
}
break;
case 'h':
flags |= FLAGS_SHORT;
format++;
if (*format == 'h') {
flags |= FLAGS_CHAR;
format++;
}
break;
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
case 't':
flags |=
(sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
#endif
case 'j':
flags |=
(sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'z':
flags |=
(sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
default:
break;
}
// evaluate specifier
switch (*format) {
case 'd':
case 'i':
case 'u':
case 'x':
case 'X':
case 'o':
case 'b': {
// set the base
unsigned int base;
if (*format == 'x' || *format == 'X') {
base = 16U;
} else if (*format == 'o') {
base = 8U;
} else if (*format == 'b') {
base = 2U;
} else {
base = 10U;
flags &= ~FLAGS_HASH; // no hash for dec format
}
// uppercase
if (*format == 'X') { flags |= FLAGS_UPPERCASE; }
// no plus or space flag for u, x, X, o, b
if ((*format != 'i') && (*format != 'd')) {
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
}
// ignore '0' flag when precision is given
if (flags & FLAGS_PRECISION) { flags &= ~FLAGS_ZEROPAD; }
// convert the integer
if ((*format == 'i') || (*format == 'd')) {
// signed
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
const long long value = va_arg(va, long long);
idx = _ntoa_long_long(
out, buffer, idx, maxlen,
(unsigned long long)(value > 0 ? value : 0 - value), value < 0,
base, precision, width, flags);
#endif
} else if (flags & FLAGS_LONG) {
const long value = va_arg(va, long);
idx = _ntoa_long(out, buffer, idx, maxlen,
(unsigned long)(value > 0 ? value : 0 - value),
value < 0, base, precision, width, flags);
} else {
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int)
: (flags & FLAGS_SHORT)
? (short int)va_arg(va, int)
: va_arg(va, int);
idx = _ntoa_long(out, buffer, idx, maxlen,
(unsigned int)(value > 0 ? value : 0 - value),
value < 0, base, precision, width, flags);
}
} else {
// unsigned
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
idx = _ntoa_long_long(out, buffer, idx, maxlen,
va_arg(va, unsigned long long), false, base,
precision, width, flags);
#endif
} else if (flags & FLAGS_LONG) {
idx =
_ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long),
false, base, precision, width, flags);
} else {
const unsigned int value =
(flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int)
: (flags & FLAGS_SHORT)
? (unsigned short int)va_arg(va, unsigned int)
: va_arg(va, unsigned int);
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base,
precision, width, flags);
}
}
format++;
break;
}
#if defined(PRINTF_SUPPORT_FLOAT)
case 'f':
case 'F':
if (*format == 'F') flags |= FLAGS_UPPERCASE;
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision,
width, flags);
format++;
break;
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
case 'e':
case 'E':
case 'g':
case 'G':
if ((*format == 'g') || (*format == 'G')) flags |= FLAGS_ADAPT_EXP;
if ((*format == 'E') || (*format == 'G')) flags |= FLAGS_UPPERCASE;
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision,
width, flags);
format++;
break;
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
case 'c': {
unsigned int l = 1U;
// pre padding
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// char output
out((char)va_arg(va, int), buffer, idx++, maxlen);
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 's': {
const char *p = va_arg(va, char *);
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
// pre padding
if (flags & FLAGS_PRECISION) { l = (l < precision ? l : precision); }
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// string output
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
out(*(p++), buffer, idx++, maxlen);
}
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 'p': {
width = sizeof(void *) * 2U;
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
if (is_ll) {
idx = _ntoa_long_long(out, buffer, idx, maxlen,
(uintptr_t)va_arg(va, void *), false, 16U,
precision, width, flags);
} else {
#endif
idx = _ntoa_long(out, buffer, idx, maxlen,
(unsigned long)((uintptr_t)va_arg(va, void *)),
false, 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
}
#endif
format++;
break;
}
case '%':
out('%', buffer, idx++, maxlen);
format++;
break;
default:
out(*format, buffer, idx++, maxlen);
format++;
break;
}
}
// termination
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return (int)idx;
}
///////////////////////////////////////////////////////////////////////////////
int printf_(const char *format, ...) {
va_list va;
va_start(va, format);
char buffer[1];
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int sprintf_(char *buffer, const char *format, ...) {
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int snprintf_(char *buffer, size_t count, const char *format, ...) {
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
va_end(va);
return ret;
}
int vprintf_(const char *format, va_list va) {
char buffer[1];
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}
int vsnprintf_(char *buffer, size_t count, const char *format, va_list va) {
return _vsnprintf(_out_buffer, buffer, count, format, va);
}
int fctprintf(void (*out)(char character, void *arg), void *arg,
const char *format, ...) {
va_list va;
va_start(va, format);
const out_fct_wrap_type out_fct_wrap = {out, arg};
const int ret = _vsnprintf(_out_fct, (char *)(uintptr_t)&out_fct_wrap,
(size_t)-1, format, va);
va_end(va);
return ret;
}

View File

@ -0,0 +1,23 @@
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include "hooks.h"
#include "printf.h"
#include "trace.h"
int vasprintf(char **restrict strp, const char *restrict fmt, va_list va) {
trace("asprintf - strp: %p, fmt: %p\n", strp, fmt);
if (strp == NULL) { return -1; }
if (fmt == NULL) { return -1; }
int len = vsnprintf_(NULL, 0, fmt, va);
if (len < 0) { return -1; }
void *buffer = asan_alloc(len + 1, 0);
if (buffer == NULL) { return -1; }
*strp = buffer;
return vsnprintf_(buffer, len, fmt, va);
}

View File

@ -0,0 +1,105 @@
//! # dlmalloc
//! This allocator makes use of the `dlmalloc` crate to manage memory. It in
//! turn uses pages of memory allocated by one of the implementations of the
//! `Mmap` trait described in the `mmap` module.
use alloc::{
alloc::{GlobalAlloc, Layout},
fmt::{self, Debug, Formatter},
};
use core::{marker::PhantomData, mem::forget, ptr::null_mut};
use dlmalloc::{Allocator, Dlmalloc};
use log::debug;
use spin::Mutex;
use crate::mmap::Mmap;
pub struct DlmallocBackendMap<M: Mmap> {
page_size: usize,
_phantom: PhantomData<M>,
}
unsafe impl<M: Mmap + Send> Allocator for DlmallocBackendMap<M> {
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
let map = M::map(size);
match map {
Ok(mut map) => {
let slice = map.as_mut_slice();
let result = (slice.as_mut_ptr(), slice.len(), 0);
forget(map);
result
}
Err(e) => {
debug!("alloc failed: {:#?}", e);
(null_mut(), 0, 0)
}
}
}
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
null_mut()
}
fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
false
}
fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
false
}
fn can_release_part(&self, _flags: u32) -> bool {
false
}
fn allocates_zeros(&self) -> bool {
true
}
fn page_size(&self) -> usize {
self.page_size
}
}
impl<M: Mmap> DlmallocBackendMap<M> {
pub const fn new(page_size: usize) -> DlmallocBackendMap<M> {
DlmallocBackendMap {
page_size,
_phantom: PhantomData,
}
}
}
pub struct DlmallocBackend<M: Mmap> {
dlmalloc: Mutex<Dlmalloc<DlmallocBackendMap<M>>>,
}
impl<M: Mmap + Send> DlmallocBackend<M> {
pub const fn new(page_size: usize) -> DlmallocBackend<M> {
let backend = DlmallocBackendMap::new(page_size);
let dlmalloc = Dlmalloc::<DlmallocBackendMap<M>>::new_with_allocator(backend);
Self {
dlmalloc: Mutex::new(dlmalloc),
}
}
}
impl<M: Mmap + Send> Debug for DlmallocBackend<M> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "DlmallocBackend")
}
}
unsafe impl<M: Mmap> GlobalAlloc for DlmallocBackend<M> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { self.dlmalloc.lock().malloc(layout.size(), layout.align()) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe {
self.dlmalloc
.lock()
.free(ptr, layout.size(), layout.align())
}
}
}

View File

@ -0,0 +1,26 @@
use alloc::alloc::{GlobalAlloc, Layout};
use baby_mimalloc::Mimalloc;
use spin::Mutex;
pub struct MimallocBackend<G: GlobalAlloc> {
mimalloc: Mutex<Mimalloc<G>>,
}
impl<G: GlobalAlloc> MimallocBackend<G> {
pub const fn new(global_allocator: G) -> Self {
MimallocBackend {
mimalloc: Mutex::new(Mimalloc::with_os_allocator(global_allocator)),
}
}
}
unsafe impl<G: GlobalAlloc> GlobalAlloc for MimallocBackend<G> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { self.mimalloc.lock().alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.mimalloc.lock().dealloc(ptr, layout) }
}
}

View File

@ -0,0 +1,18 @@
//! # backend
//! The backend is responsible for allocating the underlying memory used by the
//! application. A backend should implement the `GlobalAlloc` trait. At present
//! there are two implemented backends:
//!
//! - `dlmalloc` - A pure rust allocator based on the `dlmalloc` crate.
//! - `mimalloc` - A rust allocator using the baby_mimalloc crate which wraps
//! another backend
//!
//! A number other of possible implementations could be considered:
//! - A simple bump allocator allocating from a fixed memory buffer
//! - An allocator which calls down into the original `libc` implementation of `malloc`
#[cfg(feature = "dlmalloc")]
pub mod dlmalloc;
#[cfg(feature = "mimalloc")]
pub mod mimalloc;

View File

@ -0,0 +1,246 @@
//! # default
//! The default frontend is primarily designed for simplicity. Though called
//! the default, it may be subsequently replaced as the preferred frontend
//! should a more optimal design be implemented at a later date.
//!
//! This frontend stores all of it's metadata out-of-band, that is no meta-data
//! is stored adjacent to the user's buffers. The size of the red-zone applied
//! to each allocation is configurable. The frontend also supports the use of a
//! quarantine (whose size is configurable) to prevent user buffers from being
//! re-used for a period of time.
use alloc::{
alloc::{GlobalAlloc, Layout, LayoutError},
collections::{BTreeMap, VecDeque},
fmt::Debug,
};
use core::slice::from_raw_parts_mut;
use log::debug;
use thiserror::Error;
use crate::{
GuestAddr,
allocator::frontend::AllocatorFrontend,
shadow::{PoisonType, Shadow},
tracking::Tracking,
};
struct Allocation {
frontend_len: usize,
backend_addr: GuestAddr,
backend_len: usize,
backend_align: usize,
}
pub struct DefaultFrontend<B: GlobalAlloc + Send, S: Shadow, T: Tracking> {
backend: B,
shadow: S,
tracking: T,
red_zone_size: usize,
allocations: BTreeMap<GuestAddr, Allocation>,
quarantine: VecDeque<Allocation>,
quarantine_size: usize,
quaratine_used: usize,
}
impl<B: GlobalAlloc + Send, S: Shadow, T: Tracking> AllocatorFrontend for DefaultFrontend<B, S, T> {
type Error = DefaultFrontendError<S, T>;
fn alloc(&mut self, len: usize, align: usize) -> Result<GuestAddr, Self::Error> {
debug!("alloc - len: 0x{:x}, align: 0x{:x}", len, align);
if align % size_of::<GuestAddr>() != 0 {
Err(DefaultFrontendError::InvalidAlignment(align))?;
}
let size = len + align;
let allocated_size = (self.red_zone_size * 2) + Self::align_up(size);
assert!(allocated_size % Self::ALLOC_ALIGN_SIZE == 0);
let ptr = unsafe {
self.backend.alloc(
Layout::from_size_align(allocated_size, Self::ALLOC_ALIGN_SIZE)
.map_err(DefaultFrontendError::LayoutError)?,
)
};
if ptr.is_null() {
Err(DefaultFrontendError::AllocatorError)?;
}
let orig = ptr as GuestAddr;
debug!(
"alloc - buffer: 0x{:x}, len: 0x{:x}, align: 0x{:x}",
orig,
allocated_size,
Self::ALLOC_ALIGN_SIZE
);
let rz = orig + self.red_zone_size;
let data = if align == 0 {
rz
} else {
rz + align - (rz % align)
};
assert!(align == 0 || data % align == 0);
assert!(data + len <= orig + allocated_size);
self.allocations.insert(
data,
Allocation {
frontend_len: len,
backend_addr: orig,
backend_len: allocated_size,
backend_align: Self::ALLOC_ALIGN_SIZE,
},
);
self.tracking
.alloc(data, len)
.map_err(|e| DefaultFrontendError::TrackingError(e))?;
self.shadow
.poison(orig, data - orig, PoisonType::AsanHeapLeftRz)
.map_err(|e| DefaultFrontendError::ShadowError(e))?;
self.shadow
.unpoison(data, len)
.map_err(|e| DefaultFrontendError::ShadowError(e))?;
let poison_len = Self::align_up(len) - len + self.red_zone_size;
self.shadow
.poison(data + len, poison_len, PoisonType::AsanStackRightRz)
.map_err(|e| DefaultFrontendError::ShadowError(e))?;
let buffer = unsafe { from_raw_parts_mut(data as *mut u8, len) };
buffer.iter_mut().for_each(|b| *b = 0xff);
Ok(data)
}
fn dealloc(&mut self, addr: GuestAddr) -> Result<(), Self::Error> {
debug!("dealloc - addr: 0x{:x}", addr);
if addr == 0 {
return Ok(());
}
let alloc = self
.allocations
.remove(&addr)
.ok_or_else(|| DefaultFrontendError::InvalidAddress(addr))?;
self.shadow
.poison(
alloc.backend_addr,
alloc.backend_len,
PoisonType::AsanHeapFreed,
)
.map_err(|e| DefaultFrontendError::ShadowError(e))?;
self.tracking
.dealloc(addr)
.map_err(|e| DefaultFrontendError::TrackingError(e))?;
self.quaratine_used += alloc.backend_len;
self.quarantine.push_back(alloc);
self.purge_quarantine()?;
Ok(())
}
fn get_size(&self, addr: GuestAddr) -> Result<usize, Self::Error> {
debug!("get_size - addr: 0x{:x}", addr);
let alloc = self
.allocations
.get(&addr)
.ok_or_else(|| DefaultFrontendError::InvalidAddress(addr))?;
Ok(alloc.frontend_len)
}
}
impl<B: GlobalAlloc + Send, S: Shadow, T: Tracking> DefaultFrontend<B, S, T> {
#[cfg(target_pointer_width = "32")]
const ALLOC_ALIGN_SIZE: usize = 8;
#[cfg(target_pointer_width = "64")]
const ALLOC_ALIGN_SIZE: usize = 16;
pub const DEFAULT_REDZONE_SIZE: usize = 128;
pub const DEFAULT_QUARANTINE_SIZE: usize = 50 << 20;
pub fn new(
backend: B,
shadow: S,
tracking: T,
red_zone_size: usize,
quarantine_size: usize,
) -> Result<DefaultFrontend<B, S, T>, DefaultFrontendError<S, T>> {
if red_zone_size % Self::ALLOC_ALIGN_SIZE != 0 {
Err(DefaultFrontendError::InvalidRedZoneSize(red_zone_size))?;
}
Ok(DefaultFrontend::<B, S, T> {
backend,
shadow,
tracking,
red_zone_size,
allocations: BTreeMap::new(),
quarantine: VecDeque::new(),
quarantine_size,
quaratine_used: 0,
})
}
fn purge_quarantine(&mut self) -> Result<(), DefaultFrontendError<S, T>> {
while self.quaratine_used > self.quarantine_size {
let alloc = self
.quarantine
.pop_front()
.ok_or(DefaultFrontendError::QuarantineCorruption)?;
unsafe {
self.backend.dealloc(
alloc.backend_addr as *mut u8,
Layout::from_size_align(alloc.backend_len, alloc.backend_align)
.map_err(DefaultFrontendError::LayoutError)?,
)
};
self.quaratine_used -= alloc.backend_len;
}
Ok(())
}
fn align_up(size: usize) -> usize {
assert!(size <= GuestAddr::MAX - (Self::ALLOC_ALIGN_SIZE - 1));
let val = size + (Self::ALLOC_ALIGN_SIZE - 1);
val & !(Self::ALLOC_ALIGN_SIZE - 1)
}
pub fn shadow(&self) -> &S {
&self.shadow
}
pub fn shadow_mut(&mut self) -> &mut S {
&mut self.shadow
}
pub fn tracking(&self) -> &T {
&self.tracking
}
pub fn tracking_mut(&mut self) -> &mut T {
&mut self.tracking
}
pub fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
}
#[derive(Error, Debug, PartialEq)]
pub enum DefaultFrontendError<S: Shadow, T: Tracking> {
#[error("Invalid red_zone_size: {0}")]
InvalidRedZoneSize(usize),
#[error("Invalid alignment: {0}")]
InvalidAlignment(usize),
#[error("Allocator error")]
AllocatorError,
#[error("Layout error: {0:?}")]
LayoutError(LayoutError),
#[error("Shadow error: {0:?}")]
ShadowError(S::Error),
#[error("Tracking error: {0:?}")]
TrackingError(T::Error),
#[error("Invalid address: {0:x}")]
InvalidAddress(GuestAddr),
#[error("Quarantine corruption")]
QuarantineCorruption,
}

View File

@ -0,0 +1,16 @@
//! # frontend
//! The frontend of the allocator is responsible for applying the value-added
//! asan features on behalf of incoming user requests for allocations including
//! red-zones, poisoning and memory tracking.
use alloc::fmt::Debug;
use crate::GuestAddr;
pub mod default;
pub trait AllocatorFrontend: Sized + Send {
type Error: Debug;
fn alloc(&mut self, len: usize, align: usize) -> Result<GuestAddr, Self::Error>;
fn dealloc(&mut self, addr: GuestAddr) -> Result<(), Self::Error>;
fn get_size(&self, addr: GuestAddr) -> Result<usize, Self::Error>;
}

View File

@ -0,0 +1,9 @@
//! # allocator
//! The allocator is split into two parts:
//! - `backend` - The is the portion responsible for allocating the underlying
//! memory used by the application.
//! - `frontend` - The portion is responsible for applying the value-added asan
//! features on behalf of incoming user requests for allocations including
//! red-zones, poisoning and memory tracking.
pub mod backend;
pub mod frontend;

View File

@ -0,0 +1,4 @@
#[unsafe(no_mangle)]
extern "C" fn getauxval(_type: u64) -> u64 {
0
}

View File

@ -0,0 +1,15 @@
use log::error;
use crate::exit::abort;
#[unsafe(no_mangle)]
extern "C" fn __aeabi_unwind_cpp_pr0() {
error!("__aeabi_unwind_cpp_pr0");
abort();
}
#[unsafe(no_mangle)]
extern "C" fn __aeabi_unwind_cpp_pr1() {
error!("__aeabi_unwind_cpp_pr1");
abort();
}

View File

@ -0,0 +1,15 @@
use log::error;
use crate::exit::abort;
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "arm")]
mod arm;
#[unsafe(no_mangle)]
extern "C" fn _Unwind_Resume() {
error!("_Unwind_Resume");
abort();
}

View File

@ -0,0 +1,56 @@
use core::ffi::{CStr, c_char, c_int};
use libc::{SIGABRT, pid_t};
use crate::{
GuestAddr, asan_swap,
symbols::{Function, FunctionPointer},
};
#[derive(Debug)]
struct FunctionGetpid;
impl Function for FunctionGetpid {
type Func = unsafe extern "C" fn() -> pid_t;
const NAME: &'static CStr = c"getpid";
}
#[derive(Debug)]
struct FunctionKill;
impl Function for FunctionKill {
type Func = unsafe extern "C" fn(pid_t, c_int) -> c_int;
const NAME: &'static CStr = c"kill";
}
#[derive(Debug)]
struct FunctionExit;
impl Function for FunctionExit {
type Func = unsafe extern "C" fn(c_int) -> !;
const NAME: &'static CStr = c"_exit";
}
unsafe extern "C" {
fn asan_sym(name: *const c_char) -> GuestAddr;
}
pub fn abort() -> ! {
let getpid_addr = unsafe { asan_sym(FunctionGetpid::NAME.as_ptr() as *const c_char) };
let fn_getpid = FunctionGetpid::as_ptr(getpid_addr).unwrap();
let kill_addr = unsafe { asan_sym(FunctionKill::NAME.as_ptr() as *const c_char) };
let fn_kill = FunctionKill::as_ptr(kill_addr).unwrap();
unsafe { asan_swap(false) };
let pid = unsafe { fn_getpid() };
unsafe { fn_kill(pid, SIGABRT) };
unreachable!();
}
pub fn exit(status: c_int) -> ! {
let exit_addr = unsafe { asan_sym(FunctionExit::NAME.as_ptr() as *const c_char) };
let fn_exit = FunctionExit::as_ptr(exit_addr).unwrap();
unsafe { asan_swap(false) };
unsafe { fn_exit(status) };
}

View File

@ -0,0 +1,14 @@
use core::ffi::c_int;
use rustix::process::{Signal, kill_current_process_group};
use syscalls::{Sysno, syscall1};
pub fn abort() -> ! {
kill_current_process_group(Signal::ABORT).unwrap();
unreachable!();
}
pub fn exit(status: c_int) -> ! {
unsafe { syscall1(Sysno::exit_group, status as usize) }.unwrap();
unreachable!();
}

View File

@ -0,0 +1,16 @@
//! # die
//! This module supports exiting the process
#[cfg(feature = "libc")]
pub use crate::exit::libc::abort;
#[cfg(feature = "libc")]
pub use crate::exit::libc::exit;
#[cfg(all(feature = "linux", not(feature = "libc")))]
pub use crate::exit::linux::abort;
#[cfg(all(feature = "linux", not(feature = "libc")))]
pub use crate::exit::linux::exit;
#[cfg(feature = "libc")]
pub mod libc;
#[cfg(feature = "linux")]
pub mod linux;

View File

@ -0,0 +1,38 @@
use core::{
ffi::{c_char, c_void},
mem::size_of,
ptr::null_mut,
};
use log::trace;
use crate::{GuestAddr, asan_alloc, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_aligned_alloc")]
pub unsafe extern "C" fn aligned_alloc(alignment: size_t, size: size_t) -> *mut c_void {
unsafe {
trace!(
"aligned_alloc - alignment: {:#x}, size: {:#x}",
alignment, size
);
fn is_power_of_two(n: size_t) -> bool {
n != 0 && (n & (n - 1)) == 0
}
if alignment % size_of::<GuestAddr>() != 0 {
asan_panic(
c"aligned_alloc - alignment is not a multiple of pointer size".as_ptr()
as *const c_char,
);
} else if !is_power_of_two(alignment) {
asan_panic(c"aligned_alloc - alignment is not a power of two".as_ptr() as *const c_char);
} else if size == 0 {
null_mut()
} else {
asan_alloc(size, alignment)
}
}
}

View File

@ -0,0 +1,85 @@
use core::{
ffi::{c_char, c_int, c_uint, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_atoi")]
pub unsafe extern "C" fn atoi(s: *const c_char) -> c_int {
unsafe {
trace!("atoi - s: {:p}", s);
if s.is_null() {
asan_panic(c"atoi - s is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *s.add(len) != 0 {
len += 1;
}
asan_load(s as *const c_void, len + 1);
let slice = from_raw_parts(s, len);
let mut i = 0;
let ws = [
0x20, /* ' ' */
0xc, /* \f */
0xa, /* \n */
0xd, /* \r */
0x9, /* \t */
0xb, /* \v */
];
while ws.contains(&slice[i]) {
i += 1;
}
let mut negative = false;
if slice[i] == 0x2d
/* '-' */
{
negative = true;
i += 1;
} else if slice[i] == 0x2b
/* '+' */
{
i += 1;
}
let mut val: c_uint = 0;
for c in slice.iter().skip(i) {
if ('0' as c_char..='9' as c_char).contains(c) {
match val.checked_mul(10) {
Some(m) => val = m,
None => asan_panic(c"atoi - overflow #1".as_ptr() as *const c_char),
}
let digit = (c - '0' as c_char) as c_uint;
match val.checked_add(digit) {
Some(a) => val = a,
None => asan_panic(c"atoi - overflow #2".as_ptr() as *const c_char),
}
} else {
break;
}
}
if val == 0 {
0
} else if negative {
if val > (c_int::MAX as c_uint) + 1 {
asan_panic(c"atoi - overflow #3".as_ptr() as *const c_char);
}
-((val - 1) as c_int) - 1
} else {
if val > c_int::MAX as c_uint {
asan_panic(c"atoi - overflow #4".as_ptr() as *const c_char);
}
val as c_int
}
}
}

View File

@ -0,0 +1,85 @@
use core::{
ffi::{c_char, c_long, c_ulong, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_atol")]
pub unsafe extern "C" fn atol(s: *const c_char) -> c_long {
unsafe {
trace!("atol - s: {:p}", s);
if s.is_null() {
asan_panic(c"atol - s is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *s.add(len) != 0 {
len += 1;
}
asan_load(s as *const c_void, len + 1);
let slice = from_raw_parts(s, len);
let mut i = 0;
let ws = [
0x20, /* ' ' */
0xc, /* \f */
0xa, /* \n */
0xd, /* \r */
0x9, /* \t */
0xb, /* \v */
];
while ws.contains(&slice[i]) {
i += 1;
}
let mut negative = false;
if slice[i] == 0x2d
/* '-' */
{
negative = true;
i += 1;
} else if slice[i] == 0x2b
/* '+' */
{
i += 1;
}
let mut val = 0 as c_ulong;
for c in slice.iter().skip(i) {
if ('0' as c_char..='9' as c_char).contains(c) {
match val.checked_mul(10) {
Some(m) => val = m,
None => asan_panic(c"atoi - overflow #1".as_ptr() as *const c_char),
}
let digit = (c - '0' as c_char) as c_ulong;
match val.checked_add(digit) {
Some(a) => val = a,
None => asan_panic(c"atoi - overflow #2".as_ptr() as *const c_char),
}
} else {
break;
}
}
if val == 0 {
0
} else if negative {
if val > (c_long::MAX as c_ulong) + 1 {
asan_panic(c"atoi - overflow #3".as_ptr() as *const c_char);
}
-((val - 1) as c_long) - 1
} else {
if val > c_long::MAX as c_ulong {
asan_panic(c"atoi - overflow #4".as_ptr() as *const c_char);
}
val as c_long
}
}
}

View File

@ -0,0 +1,85 @@
use core::{
ffi::{c_char, c_longlong, c_ulonglong, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_atoll")]
pub unsafe extern "C" fn atoll(s: *const c_char) -> c_longlong {
unsafe {
trace!("atoll - s: {:p}", s);
if s.is_null() {
asan_panic(c"atol - s is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *s.add(len) != 0 {
len += 1;
}
asan_load(s as *const c_void, len + 1);
let slice = from_raw_parts(s, len);
let mut i = 0;
let ws = [
0x20, /* ' ' */
0xc, /* \f */
0xa, /* \n */
0xd, /* \r */
0x9, /* \t */
0xb, /* \v */
];
while ws.contains(&slice[i]) {
i += 1;
}
let mut negative = false;
if slice[i] == 0x2d
/* '-' */
{
negative = true;
i += 1;
} else if slice[i] == 0x2b
/* '+' */
{
i += 1;
}
let mut val = 0 as c_ulonglong;
for c in slice.iter().skip(i) {
if ('0' as c_char..='9' as c_char).contains(c) {
match val.checked_mul(10) {
Some(m) => val = m,
None => asan_panic(c"atoi - overflow #1".as_ptr() as *const c_char),
}
let digit = (c - '0' as c_char) as c_ulonglong;
match val.checked_add(digit) {
Some(a) => val = a,
None => asan_panic(c"atoi - overflow #2".as_ptr() as *const c_char),
}
} else {
break;
}
}
if val == 0 {
0
} else if negative {
if val > (c_longlong::MAX as c_ulonglong) + 1 {
asan_panic(c"atoi - overflow #3".as_ptr() as *const c_char);
}
-((val - 1) as c_longlong) - 1
} else {
if val > c_longlong::MAX as c_ulonglong {
asan_panic(c"atoi - overflow #4".as_ptr() as *const c_char);
}
val as c_longlong
}
}
}

View File

@ -0,0 +1,46 @@
use core::{
cmp::Ordering,
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_bcmp")]
pub unsafe extern "C" fn bcmp(cx: *const c_void, ct: *const c_void, n: size_t) -> c_int {
unsafe {
trace!("bcmp - cx: {:p}, ct: {:p}, n: {:#x}", cx, ct, n);
if n == 0 {
return 0;
}
if cx.is_null() {
asan_panic(c"bcmp - cx is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"bcmp - ct is null".as_ptr() as *const c_char);
}
asan_load(cx, n);
asan_load(ct, n);
let slice1 = from_raw_parts(cx as *const u8, n);
let slice2 = from_raw_parts(ct as *const u8, n);
for i in 0..n {
match slice1[i].cmp(&slice2[i]) {
Ordering::Equal => (),
Ordering::Less => return -1,
Ordering::Greater => return 1,
}
}
0
}
}

View File

@ -0,0 +1,28 @@
use core::{
ffi::{c_char, c_void},
ptr::write_bytes,
};
use log::trace;
use crate::{asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_bzero")]
pub unsafe extern "C" fn bzero(s: *mut c_void, len: size_t) {
unsafe {
trace!("bzero - s: {:p}, len: {:#x}", s, len);
if len == 0 {
return;
}
if s.is_null() {
asan_panic(c"bzero - s is null".as_ptr() as *const c_char);
}
asan_store(s, len);
write_bytes(s, 0, len);
}
}

View File

@ -0,0 +1,28 @@
use core::{
ffi::{c_char, c_void},
ptr::{null_mut, write_bytes},
};
use log::trace;
use crate::{asan_alloc, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_calloc")]
pub unsafe extern "C" fn calloc(nobj: size_t, size: size_t) -> *mut c_void {
unsafe {
trace!("calloc - nobj: {:#x}, size: {:#x}", nobj, size);
match nobj.checked_mul(size) {
Some(0) => null_mut(),
Some(size) => {
let ptr = asan_alloc(size, 0);
write_bytes(ptr, 0, size);
ptr
}
None => {
asan_panic(c"calloc - size would overflow".as_ptr() as *const c_char);
}
}
}
}

View File

@ -0,0 +1,28 @@
use core::{
ffi::{c_char, c_void},
ptr::write_bytes,
};
use log::trace;
use crate::{asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_explicit_bzero")]
pub unsafe extern "C" fn explicit_bzero(s: *mut c_void, len: size_t) {
unsafe {
trace!("explicit_bzero - s: {:p}, len: {:#x}", s, len);
if len == 0 {
return;
}
if s.is_null() {
asan_panic(b"explicit_bzero - s is null".as_ptr() as *const c_char);
}
asan_store(s, len);
write_bytes(s, 0, len);
}
}

View File

@ -0,0 +1,47 @@
use core::ffi::{CStr, c_char, c_int, c_void};
use libc::FILE;
use log::trace;
use crate::{
asan_load, asan_panic, asan_store, asan_swap, asan_sym,
symbols::{AtomicGuestAddr, Function, FunctionPointer},
};
#[derive(Debug)]
struct FunctionFgets;
impl Function for FunctionFgets {
type Func = unsafe extern "C" fn(buf: *mut c_char, n: c_int, stream: *mut FILE) -> *mut c_char;
const NAME: &'static CStr = c"fgets";
}
static FGETS_ADDR: AtomicGuestAddr = AtomicGuestAddr::new();
/// # Safety
/// See man pages
#[cfg_attr(not(feature = "test"), unsafe(no_mangle))]
#[cfg_attr(feature = "test", unsafe(export_name = "patch_fgets"))]
pub unsafe extern "C" fn fgets(buf: *mut c_char, n: c_int, stream: *mut FILE) -> *mut c_char {
unsafe {
trace!("fgets - buf: {:p}, n: {:#x}, stream: {:p}", buf, n, stream);
if buf.is_null() && n != 0 {
asan_panic(c"fgets - buf is null".as_ptr() as *const c_char);
}
if stream.is_null() {
asan_panic(c"fgets - stream is null".as_ptr() as *const c_char);
}
asan_store(buf as *const c_void, n as usize);
asan_load(stream as *const c_void, size_of::<FILE>());
let addr = FGETS_ADDR
.get_or_insert_with(|| asan_sym(FunctionFgets::NAME.as_ptr() as *const c_char));
let fn_fgets = FunctionFgets::as_ptr(addr).unwrap();
asan_swap(false);
let ret = fn_fgets(buf, n, stream);
asan_swap(true);
ret
}
}

View File

@ -0,0 +1,15 @@
use core::ffi::c_void;
use log::trace;
use crate::asan_dealloc;
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_free")]
pub unsafe extern "C" fn free(p: *mut c_void) {
unsafe {
trace!("free - p: {:p}", p);
asan_dealloc(p);
}
}

View File

@ -0,0 +1,19 @@
use core::{ffi::c_void, ptr::null_mut};
use log::trace;
use crate::{asan_alloc, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_malloc")]
pub unsafe extern "C" fn malloc(size: size_t) -> *mut c_void {
unsafe {
trace!("malloc - size: {:#x}", size);
if size == 0 {
null_mut()
} else {
asan_alloc(size, 0)
}
}
}

View File

@ -0,0 +1,15 @@
use core::ffi::c_void;
use log::trace;
use crate::{asan_get_size, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_malloc_usable_size")]
pub unsafe extern "C" fn malloc_usable_size(ptr: *mut c_void) -> size_t {
unsafe {
trace!("malloc_usable_size - ptr: {:p}", ptr);
if ptr.is_null() { 0 } else { asan_get_size(ptr) }
}
}

View File

@ -0,0 +1,33 @@
use core::{
ffi::{c_char, c_void},
mem::size_of,
ptr::null_mut,
};
use log::trace;
use crate::{GuestAddr, asan_alloc, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memalign")]
pub unsafe extern "C" fn memalign(align: size_t, size: size_t) -> *mut c_void {
unsafe {
trace!("memalign - align: {:#x}, size: {:#x}", align, size);
fn is_power_of_two(n: size_t) -> bool {
n != 0 && (n & (n - 1)) == 0
}
if align % size_of::<GuestAddr>() != 0 {
asan_panic(
c"memalign - align is not a multiple of pointer size".as_ptr() as *const c_char,
);
} else if !is_power_of_two(align) {
asan_panic(c"memalign - align is not a power of two".as_ptr() as *const c_char);
} else if size == 0 {
null_mut()
} else {
asan_alloc(size, align)
}
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_int, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memchr")]
pub unsafe extern "C" fn memchr(cx: *const c_void, c: c_int, n: size_t) -> *mut c_void {
unsafe {
trace!("memchr - cx: {:p}, c: {:#x}, n: {:#x}", cx, c, n);
if n == 0 {
return null_mut();
}
if cx.is_null() && n != 0 {
asan_panic(c"memchr - cx is null".as_ptr() as *const c_char);
}
asan_load(cx, n);
let slice = from_raw_parts(cx as *const u8, n);
let pos = slice.iter().position(|&x| x as c_int == c);
match pos {
Some(pos) => cx.add(pos) as *mut c_void,
None => null_mut(),
}
}
}

View File

@ -0,0 +1,46 @@
use core::{
cmp::Ordering,
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memcmp")]
pub unsafe extern "C" fn memcmp(cx: *const c_void, ct: *const c_void, n: size_t) -> c_int {
unsafe {
trace!("memcmp - cx: {:p}, ct: {:p}, n: {:#x}", cx, ct, n);
if n == 0 {
return 0;
}
if cx.is_null() {
asan_panic(c"memcmp - cx is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"memcmp - ct is null".as_ptr() as *const c_char);
}
asan_load(cx, n);
asan_load(ct, n);
let slice1 = from_raw_parts(cx as *const u8, n);
let slice2 = from_raw_parts(ct as *const u8, n);
for i in 0..n {
match slice1[i].cmp(&slice2[i]) {
Ordering::Equal => (),
Ordering::Less => return -1,
Ordering::Greater => return 1,
}
}
0
}
}

View File

@ -0,0 +1,40 @@
use core::{
ffi::{c_char, c_void},
ptr::copy_nonoverlapping,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memcpy")]
pub unsafe extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void {
unsafe {
trace!("memcpy - dest: {:p}, src: {:p}, n: {:#x}", dest, src, n);
if n == 0 {
return dest;
}
if dest.is_null() {
asan_panic(c"memcpy - dest is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"memcpy - src is null".as_ptr() as *const c_char);
}
let src_end = src.add(n);
let dest_end = dest.add(n) as *const c_void;
if src_end > dest && dest_end > src {
asan_panic(c"memcpy - overlap".as_ptr() as *const c_char);
}
asan_load(src, n);
asan_store(dest, n);
copy_nonoverlapping(src, dest, n);
dest
}
}

View File

@ -0,0 +1,56 @@
use core::{
ffi::{c_char, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memmem")]
pub unsafe extern "C" fn memmem(
haystack: *const c_void,
haystacklen: size_t,
needle: *const c_void,
needlelen: size_t,
) -> *mut c_void {
unsafe {
trace!(
"memmem - haystack: {:p}, haystacklen: {:#x}, needle: {:p}, needlelen: {:#x}",
haystack, haystacklen, needle, needlelen
);
if needlelen == 0 {
return haystack as *mut c_void;
}
if needlelen > haystacklen {
return null_mut();
}
if haystack.is_null() {
asan_panic(c"memmem - haystack is null".as_ptr() as *const c_char);
}
if needle.is_null() {
asan_panic(c"memmem - needle is null".as_ptr() as *const c_char);
}
asan_load(haystack, haystacklen);
asan_load(needle, needlelen);
let haystack_buffer = from_raw_parts(haystack as *const u8, haystacklen);
let needle_buffer = from_raw_parts(needle as *const u8, needlelen);
for i in 0..(haystacklen - needlelen + 1) {
if &haystack_buffer[i..i + needlelen] == needle_buffer {
return haystack.add(i) as *mut c_void;
}
}
null_mut()
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memmove")]
pub unsafe extern "C" fn memmove(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void {
unsafe {
trace!("memmove - dest: {:p}, src: {:p}, n: {:#x}", dest, src, n);
if n == 0 {
return dest;
}
if dest.is_null() {
asan_panic(c"memmove - dest is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"memmove - src is null".as_ptr() as *const c_char);
}
asan_load(src, n);
asan_store(dest, n);
copy(src, dest, n);
dest
}
}

View File

@ -0,0 +1,40 @@
use core::{
ffi::{c_char, c_void},
ptr::copy_nonoverlapping,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_mempcpy")]
pub unsafe extern "C" fn mempcpy(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void {
unsafe {
trace!("mempcpy - dest: {:p}, src: {:p}, n: {:#x}", dest, src, n);
if n == 0 {
return dest;
}
if dest.is_null() {
asan_panic(c"mempcpy - dest is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"mempcpy - src is null".as_ptr() as *const c_char);
}
let src_end = src.add(n);
let dest_end = dest.add(n) as *const c_void;
if src_end > dest && dest_end > src {
asan_panic(c"memcpy - overlap".as_ptr() as *const c_char);
}
asan_load(src, n);
asan_store(dest, n);
copy_nonoverlapping(src, dest, n);
dest.add(n)
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_int, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memrchr")]
pub unsafe extern "C" fn memrchr(cx: *const c_void, c: c_int, n: size_t) -> *mut c_void {
unsafe {
trace!("memrchr - cx: {:p}, c: {:#x}, n: {:#x}", cx, c, n);
if n == 0 {
return null_mut();
}
if cx.is_null() {
asan_panic(c"memrchr - cx is null".as_ptr() as *const c_char);
}
asan_load(cx, n);
let slice = from_raw_parts(cx as *const u8, n);
let pos = slice.iter().rev().position(|&x| x as c_int == c);
match pos {
Some(pos) => cx.add(n - pos - 1) as *mut c_void,
None => null_mut(),
}
}
}

View File

@ -0,0 +1,29 @@
use core::{
ffi::{c_char, c_int, c_void},
ptr::write_bytes,
};
use log::trace;
use crate::{asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_memset")]
pub unsafe extern "C" fn memset(dest: *mut c_void, c: c_int, n: size_t) -> *mut c_void {
unsafe {
trace!("memset - dest: {:p}, c: {:#x}, n: {:#x}", dest, c, n);
if n == 0 {
return dest;
}
if dest.is_null() {
asan_panic(c"memset - dest is null".as_ptr() as *const c_char);
}
asan_store(dest, n);
write_bytes(dest, c as u8, n);
dest
}
}

View File

@ -0,0 +1,58 @@
use core::ffi::{CStr, c_char};
use libc::{c_int, c_void};
use log::trace;
use crate::{
asan_swap, asan_sym, asan_track, asan_unpoison, off_t, size_t,
symbols::{AtomicGuestAddr, Function, FunctionPointer},
};
#[derive(Debug)]
struct FunctionMmap;
impl Function for FunctionMmap {
type Func = unsafe extern "C" fn(
addr: *mut c_void,
len: size_t,
prot: c_int,
flags: c_int,
fd: c_int,
offset: off_t,
) -> *mut c_void;
const NAME: &'static CStr = c"mmap";
}
static MMAP_ADDR: AtomicGuestAddr = AtomicGuestAddr::new();
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_mmap")]
pub unsafe extern "C" fn mmap(
addr: *mut c_void,
len: size_t,
prot: c_int,
flags: c_int,
fd: c_int,
offset: off_t,
) -> *mut c_void {
unsafe {
trace!(
"mmap - addr: {:p}, len: {:#x}, prot: {:#x}, flags: {:#x}, fd: {:#x}, offset: {:#x}",
addr, len, prot, flags, fd, offset
);
let mmap_addr =
MMAP_ADDR.get_or_insert_with(|| asan_sym(FunctionMmap::NAME.as_ptr() as *const c_char));
asan_swap(false);
let fn_mmap = FunctionMmap::as_ptr(mmap_addr).unwrap();
asan_swap(true);
let map = fn_mmap(addr, len, prot, flags, fd, offset);
if map == libc::MAP_FAILED {
return libc::MAP_FAILED;
}
asan_unpoison(map, len);
asan_track(map, len);
map
}
}

View File

@ -0,0 +1,38 @@
use core::ffi::{c_int, c_void};
use log::trace;
use rustix::{
fd::BorrowedFd,
mm::{MapFlags, ProtFlags, mmap as rmmap},
};
use crate::{GuestAddr, asan_track, asan_unpoison, off_t, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_mmap")]
pub unsafe extern "C" fn mmap(
addr: *mut c_void,
len: size_t,
prot: c_int,
flags: c_int,
fd: c_int,
offset: off_t,
) -> *mut c_void {
unsafe {
trace!(
"mmap - addr: {:p}, len: {:#x}, prot: {:#x}, flags: {:#x}, fd: {:#x}, offset: {:#x}",
addr, len, prot, flags, fd, offset
);
let file = BorrowedFd::borrow_raw(fd);
let mmap_prot = ProtFlags::from_bits_retain(prot as u32);
let mmap_flags = MapFlags::from_bits_retain(flags as u32);
if let Ok(map) = rmmap(addr, len, mmap_prot, mmap_flags, file, offset as u64) {
asan_unpoison(map, len);
asan_track(map, len);
map
} else {
GuestAddr::MAX as *mut c_void
}
}
}

View File

@ -0,0 +1,5 @@
#[cfg(feature = "libc")]
pub mod libc;
#[cfg(all(feature = "linux", not(feature = "libc")))]
pub mod linux;

View File

@ -0,0 +1,208 @@
//! # hooks
//!
//! This module provides the implementation of various functions implemented by
//! the standard C library which are used by applications. These functions are
//! are modified to provide the additional memory safety checks provided by
//! `asan`.
pub mod aligned_alloc;
pub mod atoi;
pub mod atol;
pub mod atoll;
pub mod bcmp;
pub mod bzero;
pub mod calloc;
pub mod explicit_bzero;
pub mod free;
pub mod malloc;
pub mod malloc_usable_size;
pub mod memalign;
pub mod memchr;
pub mod memcmp;
pub mod memcpy;
pub mod memmem;
pub mod memmove;
pub mod mempcpy;
pub mod memrchr;
pub mod memset;
pub mod mmap;
pub mod munmap;
pub mod posix_memalign;
pub mod pvalloc;
pub mod read;
pub mod realloc;
pub mod reallocarray;
pub mod stpcpy;
pub mod strcasecmp;
pub mod strcasestr;
pub mod strcat;
pub mod strchr;
pub mod strcmp;
pub mod strcpy;
pub mod strdup;
pub mod strlen;
pub mod strncasecmp;
pub mod strncmp;
pub mod strncpy;
pub mod strndup;
pub mod strnlen;
pub mod strrchr;
pub mod strstr;
pub mod valloc;
pub mod wcscmp;
pub mod wcscpy;
pub mod wcslen;
pub mod write;
#[cfg(feature = "libc")]
pub mod fgets;
use alloc::vec::Vec;
use core::ffi::{CStr, c_char, c_int, c_void};
use crate::{GuestAddr, hooks, size_t, wchar_t};
unsafe extern "C" {
pub fn asprintf(strp: *mut *mut c_char, fmt: *const c_char, ...) -> c_int;
pub fn vasprintf(strp: *mut *mut c_char, fmt: *const c_char, va: *const c_void) -> c_int;
}
#[derive(Clone)]
pub struct PatchedHook {
pub name: &'static CStr,
pub destination: GuestAddr,
}
impl PatchedHook {
const fn new<F: Copy>(name: &'static CStr, func: F) -> Self {
let pf = (&func) as *const F as *const GuestAddr;
let destination = unsafe { *pf };
Self { name, destination }
}
pub fn all() -> Vec<Self> {
[
PatchedHook::new::<unsafe extern "C" fn(size_t, size_t) -> *mut c_void>(
c"aligned_alloc",
hooks::aligned_alloc::aligned_alloc,
),
PatchedHook::new::<unsafe extern "C" fn(*mut *mut c_char, *const c_char, ...) -> c_int>(
c"asprintf",
hooks::asprintf,
),
PatchedHook::new::<
unsafe extern "C" fn(*const c_void, *const c_void, n: size_t) -> c_int,
>(c"bcmp", hooks::bcmp::bcmp),
PatchedHook::new::<unsafe extern "C" fn(*mut c_void, size_t)>(
c"bzero",
hooks::bzero::bzero,
),
PatchedHook::new::<unsafe extern "C" fn(*mut c_void, size_t)>(
c"explicit_bzero",
hooks::explicit_bzero::explicit_bzero,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_void, c_int, size_t) -> *mut c_void>(
c"memchr",
hooks::memchr::memchr,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_void, *const c_void, size_t) -> c_int>(
c"memcmp",
hooks::memcmp::memcmp,
),
PatchedHook::new::<
unsafe extern "C" fn(*mut c_void, *const c_void, size_t) -> *mut c_void,
>(c"memcpy", hooks::memcpy::memcpy),
PatchedHook::new::<
unsafe extern "C" fn(*const c_void, size_t, *const c_void, size_t) -> *mut c_void,
>(c"memmem", hooks::memmem::memmem),
PatchedHook::new::<
unsafe extern "C" fn(*mut c_void, *const c_void, size_t) -> *mut c_void,
>(c"memmove", hooks::memmove::memmove),
PatchedHook::new::<
unsafe extern "C" fn(*mut c_void, *const c_void, size_t) -> *mut c_void,
>(c"mempcpy", hooks::mempcpy::mempcpy),
PatchedHook::new::<unsafe extern "C" fn(*const c_void, c_int, size_t) -> *mut c_void>(
c"memrchr",
hooks::memrchr::memrchr,
),
PatchedHook::new::<unsafe extern "C" fn(*mut c_char, *const c_char) -> *mut c_char>(
c"stpcpy",
hooks::stpcpy::stpcpy,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char) -> c_int>(
c"strcasecmp",
hooks::strcasecmp::strcasecmp,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char) -> *mut c_char>(
c"strcasestr",
hooks::strcasestr::strcasestr,
),
PatchedHook::new::<unsafe extern "C" fn(*mut c_char, *const c_char) -> *mut c_char>(
c"strcat",
hooks::strcat::strcat,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, c_int) -> *mut c_char>(
c"strchr",
hooks::strchr::strchr,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char) -> c_int>(
c"strcmp",
hooks::strcmp::strcmp,
),
PatchedHook::new::<unsafe extern "C" fn(*mut c_char, *const c_char) -> *mut c_char>(
c"strcpy",
hooks::strcpy::strcpy,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char) -> *mut c_char>(
c"strdup",
hooks::strdup::strdup,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char) -> size_t>(
c"strlen",
hooks::strlen::strlen,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char, size_t) -> c_int>(
c"strncasecmp",
hooks::strncasecmp::strncasecmp,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char, size_t) -> c_int>(
c"strncmp",
hooks::strncmp::strncmp,
),
PatchedHook::new::<
unsafe extern "C" fn(*mut c_char, *const c_char, size_t) -> *mut c_char,
>(c"strncpy", hooks::strncpy::strncpy),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, size_t) -> *mut c_char>(
c"strndup",
hooks::strndup::strndup,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, size_t) -> size_t>(
c"strnlen",
hooks::strnlen::strnlen,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, c_int) -> *mut c_char>(
c"strrchr",
hooks::strrchr::strrchr,
),
PatchedHook::new::<unsafe extern "C" fn(*const c_char, *const c_char) -> *mut c_char>(
c"strstr",
hooks::strstr::strstr,
),
PatchedHook::new::<
unsafe extern "C" fn(*mut *mut c_char, *const c_char, *const c_void) -> c_int,
>(c"vasprintf", hooks::vasprintf),
PatchedHook::new::<unsafe extern "C" fn(*const wchar_t, *const wchar_t) -> c_int>(
c"wcscmp",
hooks::wcscmp::wcscmp,
),
PatchedHook::new::<unsafe extern "C" fn(*mut wchar_t, *const wchar_t) -> *mut wchar_t>(
c"wcscpy",
hooks::wcscpy::wcscpy,
),
PatchedHook::new::<unsafe extern "C" fn(*const wchar_t) -> size_t>(
c"wcslen",
hooks::wcslen::wcslen,
),
]
.to_vec()
}
}

View File

@ -0,0 +1,40 @@
use core::ffi::{CStr, c_char};
use libc::{c_int, c_void};
use log::trace;
use crate::{
asan_swap, asan_sym, asan_untrack, size_t,
symbols::{AtomicGuestAddr, Function, FunctionPointer},
};
#[derive(Debug)]
struct FunctionMunmap;
impl Function for FunctionMunmap {
type Func = unsafe extern "C" fn(addr: *mut c_void, len: size_t) -> c_int;
const NAME: &'static CStr = c"munmap";
}
static MUNMAP_ADDR: AtomicGuestAddr = AtomicGuestAddr::new();
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_munmap")]
pub unsafe extern "C" fn munmap(addr: *mut c_void, len: size_t) -> c_int {
unsafe {
trace!("munmap - addr: {:p}, len: {:#x}", addr, len);
let mmap_addr = MUNMAP_ADDR
.get_or_insert_with(|| asan_sym(FunctionMunmap::NAME.as_ptr() as *const c_char));
asan_swap(false);
let fn_munmap = FunctionMunmap::as_ptr(mmap_addr).unwrap();
asan_swap(true);
let ret = fn_munmap(addr, len);
if ret < 0 {
return ret;
}
asan_untrack(addr);
ret
}
}

View File

@ -0,0 +1,22 @@
use core::ffi::{c_int, c_void};
use log::trace;
use rustix::mm::munmap as rmunmap;
use crate::{asan_untrack, hooks::size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_munmap")]
pub unsafe extern "C" fn munmap(addr: *mut c_void, len: size_t) -> c_int {
unsafe {
trace!("munmap - addr: {:p}, len: {:#x}", addr, len);
if rmunmap(addr, len).is_ok() {
asan_untrack(addr);
0
} else {
-1
}
}
}

View File

@ -0,0 +1,5 @@
#[cfg(feature = "libc")]
pub mod libc;
#[cfg(all(feature = "linux", not(feature = "libc")))]
pub mod linux;

View File

@ -0,0 +1,49 @@
use core::{
ffi::{c_char, c_int, c_void},
mem::size_of,
ptr::null_mut,
};
use log::trace;
use crate::{GuestAddr, asan_alloc, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_posix_memalign")]
pub unsafe extern "C" fn posix_memalign(
memptr: *mut *mut c_void,
align: size_t,
size: size_t,
) -> c_int {
unsafe {
trace!(
"posix_memalign - memptr: {:p}, align: {:#x}, size: {:#x}",
memptr, align, size
);
if memptr.is_null() {
asan_panic(c"posix_memalign - memptr is null".as_ptr() as *const c_char);
}
fn is_power_of_two(n: size_t) -> bool {
n != 0 && (n & (n - 1)) == 0
}
if align % size_of::<GuestAddr>() != 0 {
asan_panic(
c"posix_memalign - align is not a multiple of pointer size".as_ptr()
as *const c_char,
);
} else if !is_power_of_two(align) {
asan_panic(c"posix_memalign - align is not a power of two".as_ptr() as *const c_char);
} else if size == 0 {
*memptr = null_mut();
0
} else {
let p = asan_alloc(size, align);
*memptr = p;
0
}
}
}

View File

@ -0,0 +1,22 @@
use core::ffi::c_void;
use log::trace;
use crate::{asan_alloc, asan_page_size, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_pvalloc")]
pub unsafe extern "C" fn pvalloc(size: size_t) -> *mut c_void {
unsafe {
trace!("pvalloc - size: {:#x}", size);
let page_size = asan_page_size();
let aligned_size = if size == 0 {
page_size
} else {
(size + page_size - 1) & !(page_size - 1)
};
assert_ne!(aligned_size, 0);
asan_alloc(aligned_size, page_size)
}
}

View File

@ -0,0 +1,41 @@
use core::ffi::{CStr, c_char, c_long};
use libc::{SYS_read, c_int, c_void};
use log::trace;
use crate::{
asan_panic, asan_store, asan_swap, asan_sym, size_t, ssize_t,
symbols::{AtomicGuestAddr, Function, FunctionPointer},
};
#[derive(Debug)]
struct FunctionSyscall;
impl Function for FunctionSyscall {
type Func = unsafe extern "C" fn(num: c_long, ...) -> c_long;
const NAME: &'static CStr = c"syscall";
}
static SYSCALL_ADDR: AtomicGuestAddr = AtomicGuestAddr::new();
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_read")]
pub unsafe extern "C" fn read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t {
unsafe {
trace!("read - fd: {:#x}, buf: {:p}, count: {:#x}", fd, buf, count);
if buf.is_null() && count != 0 {
asan_panic(c"read - buf is null".as_ptr() as *const c_char);
}
asan_store(buf, count);
let addr = SYSCALL_ADDR
.get_or_insert_with(|| asan_sym(FunctionSyscall::NAME.as_ptr() as *const c_char));
let fn_syscall = FunctionSyscall::as_ptr(addr).unwrap();
asan_swap(false);
let ret = fn_syscall(SYS_read, fd, buf, count);
asan_swap(true);
ret as ssize_t
}
}

View File

@ -0,0 +1,31 @@
use core::{
ffi::{c_char, c_int, c_void},
slice::from_raw_parts_mut,
};
use log::trace;
use rustix::{fd::BorrowedFd, io};
use crate::{asan_panic, asan_store, size_t, ssize_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_read")]
pub unsafe extern "C" fn read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t {
unsafe {
trace!("read - fd: {:#x}, buf: {:p}, count: {:#x}", fd, buf, count);
if buf.is_null() && count != 0 {
asan_panic(c"read - buf is null".as_ptr() as *const c_char);
}
asan_store(buf, count);
let file = BorrowedFd::borrow_raw(fd);
let data = from_raw_parts_mut(buf as *mut u8, count as usize);
if let Ok(ret) = io::read(file, data) {
return ret as ssize_t;
} else {
return -1;
}
}
}

View File

@ -0,0 +1,5 @@
#[cfg(feature = "libc")]
pub mod libc;
#[cfg(all(feature = "linux", not(feature = "libc")))]
pub mod linux;

View File

@ -0,0 +1,33 @@
use core::{
ffi::c_void,
ptr::{copy_nonoverlapping, null_mut},
};
use log::trace;
use crate::{asan_alloc, asan_dealloc, asan_get_size, asan_load, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_realloc")]
pub unsafe extern "C" fn realloc(p: *mut c_void, size: size_t) -> *mut c_void {
unsafe {
trace!("realloc - p: {:p}, size: {:#x}", p, size);
if p.is_null() && size == 0 {
null_mut()
} else if p.is_null() {
asan_alloc(size, 0)
} else if size == 0 {
asan_dealloc(p);
null_mut()
} else {
let old_size = asan_get_size(p);
asan_load(p, old_size);
let q = asan_alloc(size, 0);
let min = old_size.min(size);
copy_nonoverlapping(p as *const u8, q as *mut u8, min);
asan_dealloc(p);
q
}
}
}

View File

@ -0,0 +1,45 @@
use core::{
ffi::{c_char, c_void},
ptr::{copy_nonoverlapping, null_mut},
};
use log::trace;
use crate::{asan_alloc, asan_dealloc, asan_get_size, asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_reallocarray")]
pub unsafe extern "C" fn reallocarray(
ptr: *mut c_void,
nmemb: size_t,
size: size_t,
) -> *mut c_void {
unsafe {
trace!(
"reallocarray - ptr: {:p}, nmemb: {:#x}, size: {:#x}",
ptr, nmemb, size
);
match nmemb.checked_mul(size) {
Some(size) => {
if ptr.is_null() && size == 0 {
null_mut()
} else if ptr.is_null() {
asan_alloc(size, 0)
} else if size == 0 {
asan_dealloc(ptr);
null_mut()
} else {
let old_size = asan_get_size(ptr);
asan_load(ptr, old_size);
let q = asan_alloc(size, 0);
let min = old_size.min(size);
copy_nonoverlapping(ptr as *const u8, q as *mut u8, min);
asan_dealloc(ptr);
q
}
}
None => asan_panic(c"reallocarray - size would overflow".as_ptr() as *const c_char),
}
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_stpcpy")]
pub unsafe extern "C" fn stpcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
unsafe {
trace!("stpcpy - dst: {:p}, src: {:p}", dst, src);
if dst.is_null() {
asan_panic(c"stpcpy - dst is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"stpcpy - src is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *src.add(len) != 0 {
len += 1;
}
asan_load(src as *const c_void, len + 1);
asan_store(dst as *const c_void, len + 1);
copy(src, dst, len + 1);
dst.add(len)
}
}

View File

@ -0,0 +1,70 @@
use core::{
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strcasecmp")]
pub unsafe extern "C" fn strcasecmp(s1: *const c_char, s2: *const c_char) -> c_int {
unsafe {
trace!("strcasecmp - s1: {:p}, s2: {:p}", s1, s2);
if s1.is_null() {
asan_panic(c"strcasecmp - s1 is null".as_ptr() as *const c_char);
}
if s2.is_null() {
asan_panic(c"strcasecmp - s2 is null".as_ptr() as *const c_char);
}
let mut s1_len = 0;
while *s1.add(s1_len) != 0 {
s1_len += 1;
}
let mut s2_len = 0;
while *s2.add(s2_len) != 0 {
s2_len += 1;
}
asan_load(s1 as *const c_void, s1_len + 1);
asan_load(s2 as *const c_void, s2_len + 1);
let to_upper = |c: c_char| -> c_char {
if ('a' as c_char..='z' as c_char).contains(&c) {
c - 'a' as c_char + 'A' as c_char
} else {
c
}
};
let s1_slice = from_raw_parts(s1, s1_len);
let s2_slice = from_raw_parts(s2, s2_len);
for i in 0..s1_len.max(s2_len) {
if i >= s1_len {
return -1;
}
if i >= s2_len {
return 1;
}
let c1u = to_upper(s1_slice[i]);
let c2u = to_upper(s2_slice[i]);
if c1u < c2u {
return -1;
}
if c1u > c2u {
return 1;
}
}
0
}
}

View File

@ -0,0 +1,72 @@
use alloc::vec::Vec;
use core::{
ffi::{c_char, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strcasestr")]
pub unsafe extern "C" fn strcasestr(cs: *const c_char, ct: *const c_char) -> *mut c_char {
unsafe {
trace!("strcasestr - cs: {:p}, ct: {:p}", cs, ct);
if cs.is_null() {
asan_panic(c"strcasestr - cs is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"strcasestr - ct is null".as_ptr() as *const c_char);
}
let mut cs_len = 0;
while *cs.add(cs_len) != 0 {
cs_len += 1;
}
let mut ct_len = 0;
while *ct.add(ct_len) != 0 {
ct_len += 1;
}
asan_load(cs as *const c_void, cs_len + 1);
asan_load(ct as *const c_void, ct_len + 1);
if ct_len == 0 {
return cs as *mut c_char;
}
if ct_len > cs_len {
return null_mut();
}
let to_upper = |c: c_char| -> c_char {
if ('a' as c_char..='z' as c_char).contains(&c) {
c - 'a' as c_char + 'A' as c_char
} else {
c
}
};
let cs_slice = from_raw_parts(cs, cs_len)
.iter()
.cloned()
.map(to_upper)
.collect::<Vec<c_char>>();
let ct_slice = from_raw_parts(ct, ct_len)
.iter()
.cloned()
.map(to_upper)
.collect::<Vec<c_char>>();
for i in 0..(cs_len - ct_len + 1) {
if cs_slice[i..i + ct_len] == ct_slice {
return cs.add(i) as *mut c_char;
}
}
null_mut()
}
}

View File

@ -0,0 +1,38 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strcat")]
pub unsafe extern "C" fn strcat(s: *mut c_char, ct: *const c_char) -> *mut c_char {
unsafe {
trace!("strcat - s: {:p}, ct: {:p}", s, ct);
if s.is_null() {
asan_panic(c"strcat - s is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"strcat - ct is null".as_ptr() as *const c_char);
}
let mut s_len = 0;
while *s.add(s_len) != 0 {
s_len += 1;
}
let mut ct_len = 0;
while *ct.add(ct_len) != 0 {
ct_len += 1;
}
asan_load(s as *const c_void, s_len + 1);
asan_load(ct as *const c_void, ct_len + 1);
copy(ct, s.add(s_len), ct_len + 1);
s
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_int, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strchr")]
pub unsafe extern "C" fn strchr(cs: *const c_char, c: c_int) -> *mut c_char {
unsafe {
trace!("strchr - cs: {:p}, c: {:#x}", cs, c);
if cs.is_null() {
asan_panic(c"strchr - cs is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *cs.add(len) != 0 {
len += 1;
}
asan_load(cs as *const c_void, len + 1);
let cs_slice = from_raw_parts(cs, len);
let pos = cs_slice.iter().position(|&x| x as c_int == c);
match pos {
Some(pos) => cs.add(pos) as *mut c_char,
None => null_mut(),
}
}
}

View File

@ -0,0 +1,58 @@
use core::{
cmp::Ordering,
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strcmp")]
pub unsafe extern "C" fn strcmp(cs: *const c_char, ct: *const c_char) -> c_int {
unsafe {
trace!("strcmp - cs: {:p}, ct: {:p}", cs, ct);
if cs.is_null() {
asan_panic(c"strcmp - cs is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"strcmp - ct is null".as_ptr() as *const c_char);
}
let mut cs_len = 0;
while *cs.add(cs_len) != 0 {
cs_len += 1;
}
let mut ct_len = 0;
while *ct.add(ct_len) != 0 {
ct_len += 1;
}
asan_load(cs as *const c_void, cs_len + 1);
asan_load(ct as *const c_void, ct_len + 1);
let slice1 = from_raw_parts(cs as *const u8, cs_len);
let slice2 = from_raw_parts(ct as *const u8, ct_len);
for i in 0..cs_len.max(ct_len) {
if i >= cs_len {
return -1;
}
if i >= ct_len {
return 1;
}
match slice1[i].cmp(&slice2[i]) {
Ordering::Equal => (),
Ordering::Less => return -1,
Ordering::Greater => return 1,
}
}
0
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strcpy")]
pub unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
unsafe {
trace!("strcpy - dst: {:p}, src: {:p}", dst, src);
if dst.is_null() {
asan_panic(c"strcpy - dst is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"strcpy - src is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *src.add(len) != 0 {
len += 1;
}
asan_load(src as *const c_void, len + 1);
asan_store(dst as *const c_void, len + 1);
copy(src, dst, len + 1);
dst
}
}

View File

@ -0,0 +1,31 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_alloc, asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strdup")]
pub unsafe extern "C" fn strdup(cs: *const c_char) -> *mut c_char {
unsafe {
trace!("strdup - cs: {:p}", cs);
if cs.is_null() {
asan_panic(c"strdup - cs is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *cs.add(len) != 0 {
len += 1;
}
asan_load(cs as *const c_void, len + 1);
let dest = asan_alloc(len + 1, 0) as *mut c_char;
copy(cs, dest, len + 1);
dest
}
}

View File

@ -0,0 +1,25 @@
use core::ffi::{c_char, c_void};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strlen")]
pub unsafe extern "C" fn strlen(cs: *const c_char) -> size_t {
unsafe {
trace!("strlen - cs: {:p}", cs);
if cs.is_null() {
asan_panic(c"strlen - cs is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *cs.add(len) != 0 {
len += 1;
}
asan_load(cs as *const c_void, len + 1);
len
}
}

View File

@ -0,0 +1,73 @@
use core::{
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strncasecmp")]
pub unsafe extern "C" fn strncasecmp(s1: *const c_char, s2: *const c_char, n: size_t) -> c_int {
unsafe {
trace!("strncasecmp - s1: {:p}, s2: {:p}, n: {:#x}", s1, s2, n);
if n == 0 {
return 0;
}
if s1.is_null() {
asan_panic(c"strncasecmp - s1 is null".as_ptr() as *const c_char);
}
if s2.is_null() {
asan_panic(c"strncasecmp - s2 is null".as_ptr() as *const c_char);
}
let mut s1_len = 0;
while s1_len < n && *s1.add(s1_len) != 0 {
s1_len += 1;
}
let mut s2_len = 0;
while s2_len < n && *s2.add(s2_len) != 0 {
s2_len += 1;
}
asan_load(s1 as *const c_void, s1_len + 1);
asan_load(s2 as *const c_void, s2_len + 1);
let to_upper = |c: c_char| -> c_char {
if ('a' as c_char..='z' as c_char).contains(&c) {
c - 'a' as c_char + 'A' as c_char
} else {
c
}
};
let s1_slice = from_raw_parts(s1, s1_len);
let s2_slice = from_raw_parts(s2, s2_len);
for i in 0..s1_len.max(s2_len) {
if i >= s1_len {
return -1;
}
if i >= s2_len {
return 1;
}
let c1u = to_upper(s1_slice[i]);
let c2u = to_upper(s2_slice[i]);
if c1u < c2u {
return -1;
}
if c1u > c2u {
return 1;
}
}
0
}
}

View File

@ -0,0 +1,62 @@
use core::{
cmp::Ordering,
ffi::{c_char, c_int, c_void},
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strncmp")]
pub unsafe extern "C" fn strncmp(cs: *const c_char, ct: *const c_char, n: size_t) -> c_int {
unsafe {
trace!("strncmp - cs: {:p}, ct: {:p}, n: {:#x}", cs, ct, n);
if n == 0 {
return 0;
}
if cs.is_null() {
asan_panic(c"strncmp - cs is null".as_ptr() as *const c_char);
}
if ct.is_null() {
asan_panic(c"strncmp - ct is null".as_ptr() as *const c_char);
}
let mut cs_len = 0;
while cs_len < n && *cs.add(cs_len) != 0 {
cs_len += 1;
}
let mut ct_len = 0;
while ct_len < n && *ct.add(ct_len) != 0 {
ct_len += 1;
}
asan_load(cs as *const c_void, cs_len + 1);
asan_load(ct as *const c_void, ct_len + 1);
let slice1 = from_raw_parts(cs as *const u8, cs_len);
let slice2 = from_raw_parts(ct as *const u8, ct_len);
for i in 0..cs_len.max(ct_len) {
if i >= cs_len {
return -1;
}
if i >= ct_len {
return 1;
}
match slice1[i].cmp(&slice2[i]) {
Ordering::Equal => (),
Ordering::Less => return -1,
Ordering::Greater => return 1,
}
}
0
}
}

View File

@ -0,0 +1,40 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_load, asan_panic, asan_store, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strncpy")]
pub unsafe extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: size_t) -> *mut c_char {
unsafe {
trace!("strncpy - dst: {:p}, src: {:p}, n: {:#x}", dst, src, n);
if n == 0 {
return dst;
}
if dst.is_null() {
asan_panic(c"strncpy - dst is null".as_ptr() as *const c_char);
}
if src.is_null() {
asan_panic(c"strncpy - src is null".as_ptr() as *const c_char);
}
let mut len = 0;
while len < n && *src.add(len) != 0 {
len += 1;
}
asan_store(dst as *const c_void, len);
asan_load(src as *const c_void, len);
copy(src, dst, len);
dst
}
}

View File

@ -0,0 +1,38 @@
use core::{
ffi::{c_char, c_void},
ptr::copy,
};
use log::trace;
use crate::{asan_alloc, asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strndup")]
pub unsafe extern "C" fn strndup(cs: *const c_char, n: size_t) -> *mut c_char {
unsafe {
trace!("strndup - cs: {:p}, n: {:#x}", cs, n);
if cs.is_null() {
if n == 0 {
let dest = asan_alloc(1, 0) as *mut c_char;
*dest = 0;
return dest;
} else {
asan_panic(c"strndup - cs is null".as_ptr() as *const c_char);
}
}
let mut len = 0;
while len < n && *cs.add(len) != 0 {
len += 1;
}
asan_load(cs as *const c_void, len + 1);
let dest = asan_alloc(len + 1, 0) as *mut c_char;
copy(cs, dest, len + 1);
*dest.add(len) = 0;
dest
}
}

View File

@ -0,0 +1,35 @@
use core::ffi::{c_char, c_void};
use log::trace;
use crate::{asan_load, asan_panic, size_t};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strnlen")]
pub unsafe extern "C" fn strnlen(cs: *const c_char, maxlen: size_t) -> size_t {
unsafe {
trace!("strnlen - cs: {:p}, maxlen: {:#x}", cs, maxlen);
if maxlen == 0 {
return 0;
}
if cs.is_null() {
asan_panic(c"strnlen - cs is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *cs.add(len) != 0 {
len += 1;
}
if len < maxlen {
asan_load(cs as *const c_void, len + 1);
len
} else {
asan_load(cs as *const c_void, maxlen);
maxlen
}
}
}

View File

@ -0,0 +1,34 @@
use core::{
ffi::{c_char, c_int, c_void},
ptr::null_mut,
slice::from_raw_parts,
};
use log::trace;
use crate::{asan_load, asan_panic};
/// # Safety
/// See man pages
#[unsafe(export_name = "patch_strrchr")]
pub unsafe extern "C" fn strrchr(cs: *const c_char, c: c_int) -> *mut c_char {
unsafe {
trace!("strrchr - cs: {:p}, c: {:#x}", cs, c);
if cs.is_null() {
asan_panic(c"strrchr - cs is null".as_ptr() as *const c_char);
}
let mut len = 0;
while *cs.add(len) != 0 {
len += 1;
}
asan_load(cs as *const c_void, len + 1);
let cs_slice = from_raw_parts(cs, len);
let pos = cs_slice.iter().rev().position(|&x| x as c_int == c);
match pos {
Some(pos) => cs.add(len - pos - 1) as *mut c_char,
None => null_mut(),
}
}
}

Some files were not shown because too many files have changed in this diff Show More