diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 3f170b887f..51d5d11d24 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -62,10 +62,10 @@ jobs: if: runner.os == 'MacOS' run: cd docs && mdbook test -L ../target/debug/deps $(python3-config --ldflags | cut -d ' ' -f1) - name: Run tests (Windows) - if: runner.os == 'Windows' + if: runner.os == 'Windows' run: cargo test -- --test-threads 1 - name: Run tests (Linux) - if: runner.os != 'Windows' + if: runner.os != 'Windows' run: cargo test -- --test-threads 1 - name: Test libafl no_std run: cd libafl && cargo test --no-default-features @@ -404,6 +404,43 @@ jobs: shell: bash 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: needs: - changes diff --git a/.github/workflows/librasan-prepare/action.yml b/.github/workflows/librasan-prepare/action.yml new file mode 100644 index 0000000000..e70e66913a --- /dev/null +++ b/.github/workflows/librasan-prepare/action.yml @@ -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" } diff --git a/.github/workflows/ubuntu-prepare/action.yml b/.github/workflows/ubuntu-prepare/action.yml index a1f5e439a7..cfabece533 100644 --- a/.github/workflows/ubuntu-prepare/action.yml +++ b/.github/workflows/ubuntu-prepare/action.yml @@ -7,6 +7,10 @@ runs: 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 - uses: dtolnay/rust-toolchain@stable + - name: install just + uses: extractions/setup-just@v2 + with: + just-version: 1.39.0 - name: Add stable clippy shell: bash run: rustup toolchain install stable --component clippy --allow-downgrade diff --git a/Cargo.toml b/Cargo.toml index 179510f19e..1fc0c94d0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ cmake = "0.1.51" document-features = "0.2.10" fastbloom = { version = "0.9.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible +just = "1.40.0" libc = "0.2.159" # For (*nix) libc libipt = "0.3.0" log = "0.4.22" diff --git a/fuzzers/binary_only/qemu_launcher/Cargo.toml b/fuzzers/binary_only/qemu_launcher/Cargo.toml index 92560f9b20..d65aaf713d 100644 --- a/fuzzers/binary_only/qemu_launcher/Cargo.toml +++ b/fuzzers/binary_only/qemu_launcher/Cargo.toml @@ -47,7 +47,10 @@ libafl = { path = "../../../libafl", features = ["tui_monitor"] } libafl_bolts = { path = "../../../libafl_bolts", features = [ "errors_backtrace", ] } -libafl_qemu = { path = "../../../libafl_qemu", features = ["usermode"] } +libafl_qemu = { path = "../../../libafl_qemu", features = [ + "usermode", + "rasan", +] } libafl_targets = { path = "../../../libafl_targets" } log = { version = "0.4.22", features = ["release_max_level_info"] } nix = { version = "0.29.0", features = ["fs"] } diff --git a/fuzzers/binary_only/qemu_launcher/Justfile b/fuzzers/binary_only/qemu_launcher/Justfile index 149d4690a2..1fdcfcf122 100644 --- a/fuzzers/binary_only/qemu_launcher/Justfile +++ b/fuzzers/binary_only/qemu_launcher/Justfile @@ -31,6 +31,10 @@ harness: libpng [unix] run: harness build + #!/bin/bash + + source {{ DOTENV }} + CUSTOM_QASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so \ {{ FUZZER }} \ --input ./corpus \ --output {{ TARGET_DIR }}/output/ \ @@ -56,7 +60,12 @@ test_inner: harness build # complie again with simple mgr 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] test: @@ -72,6 +81,10 @@ single: harness build {{ HARNESS }} asan: harness build + #!/bin/bash + + source {{ DOTENV }} + CUSTOM_QASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so \ {{ FUZZER }} \ --input ./corpus \ --output {{ TARGET_DIR }}/output/ \ @@ -82,6 +95,10 @@ asan: harness build {{ HARNESS }} asan_guest: harness build + #!/bin/bash + + source {{ DOTENV }} + CUSTOM_GASAN_PATH={{ BUILD_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.so \ {{ FUZZER }} \ --input ./corpus \ --output {{ TARGET_DIR }}/output/ \ @@ -93,4 +110,4 @@ asan_guest: harness build [unix] clean: - cargo clean \ No newline at end of file + cargo clean diff --git a/fuzzers/binary_only/qemu_launcher/src/client.rs b/fuzzers/binary_only/qemu_launcher/src/client.rs index d70ad67ba5..9a7f762cce 100644 --- a/fuzzers/binary_only/qemu_launcher/src/client.rs +++ b/fuzzers/binary_only/qemu_launcher/src/client.rs @@ -126,6 +126,16 @@ impl Client<'_> { 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) } else { let modules = tuple_list!(DrCovModule::builder() @@ -139,6 +149,10 @@ impl Client<'_> { let modules = 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) } else { let modules = tuple_list!(); diff --git a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs index ac5c37ed00..0f4eef3a18 100644 --- a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs @@ -8,9 +8,9 @@ use clap::Parser; #[cfg(feature = "simplemgr")] use libafl::events::SimpleEventManager; #[cfg(not(feature = "simplemgr"))] -use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager}; +use libafl::events::{EventConfig, Launcher, LlmpEventManagerBuilder, MonitorTypedEventManager}; use libafl::{ - events::{ClientDescription, LlmpEventManagerBuilder}, + events::ClientDescription, monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, Error, }; diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index 5d7981dc3b..6ec9d28ad5 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -44,7 +44,8 @@ use libafl_qemu::{ cmplog::CmpLogObserver, edges::EdgeCoverageFullVariant, utils::filters::{HasAddressFilter, NopPageFilter, StdAddressFilter}, - EdgeCoverageModule, EmulatorModuleTuple, SnapshotModule, StdEdgeCoverageModule, + AsanGuestModule, EdgeCoverageModule, EmulatorModuleTuple, SnapshotModule, + StdEdgeCoverageModule, }, Emulator, GuestAddr, Qemu, QemuExecutor, }; @@ -153,7 +154,7 @@ impl Instance<'_, M> { .map_observer(edges_observer.as_mut()) .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 diff --git a/fuzzers/binary_only/qemu_launcher/tests/qasan/.gitignore b/fuzzers/binary_only/qemu_launcher/tests/qasan/.gitignore new file mode 100644 index 0000000000..7356b2ffd1 --- /dev/null +++ b/fuzzers/binary_only/qemu_launcher/tests/qasan/.gitignore @@ -0,0 +1 @@ +/qasan diff --git a/fuzzers/binary_only/qemu_launcher/tests/qasan/gasan_test.sh b/fuzzers/binary_only/qemu_launcher/tests/qasan/gasan_test.sh new file mode 100755 index 0000000000..789071dee2 --- /dev/null +++ b/fuzzers/binary_only/qemu_launcher/tests/qasan/gasan_test.sh @@ -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 diff --git a/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c b/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c index 23b0f6819e..9086d1aeea 100644 --- a/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c +++ b/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c @@ -71,13 +71,7 @@ int main(int argc, char **argv) { char input = '\0'; - // if (read(STDIN_FILENO, &input, 1) < 0) { - - // LOG("Failed to read stdin\n"); - // return 1; - - // } - + if (argc > 1) { input = argv[1][0]; } LLVMFuzzerTestOneInput(&input, 1); LOG("DONE\n"); diff --git a/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh b/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan_test.sh similarity index 84% rename from fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh rename to fuzzers/binary_only/qemu_launcher/tests/qasan/qasan_test.sh index 64164eca72..4efa378b1e 100755 --- a/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh +++ b/fuzzers/binary_only/qemu_launcher/tests/qasan/qasan_test.sh @@ -22,11 +22,11 @@ tests=( ) tests_expected=( - "is 0 bytes to the right of the 10-byte chunk" - "is 1 bytes to the left of the 10-byte chunk" - "is 0 bytes inside the 10-byte chunk" - "Invalid 11 bytes write at" - "is 0 bytes inside the 10-byte chunk" + "AddressSanitizer Error" + "AddressSanitizer Error" + "Panic!" + "AddressSanitizer Error" + "AddressSanitizer Error" "Test-Limits - No Error" ) @@ -39,6 +39,9 @@ tests_not_expected=( "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]}" diff --git a/just/envs/.env.aarch64 b/just/envs/.env.aarch64 index 46d6e57160..7dd2816505 100644 --- a/just/envs/.env.aarch64 +++ b/just/envs/.env.aarch64 @@ -1,3 +1,7 @@ +CROSS_TARGET="aarch64-unknown-linux-gnu" CROSS_CC="aarch64-linux-gnu-gcc" CROSS_CXX="aarch64-linux-gnu-g++" -CROSS_CFLAGS="" \ No newline at end of file +CROSS_OBJCOPY="aarch64-linux-gnu-objcopy" +CROSS_STRIP="aarch64-linux-gnu-strip" +CROSS_CFLAGS="" +LIBRASAN_CFLAGS="-no-pie -fno-stack-protector" diff --git a/just/envs/.env.arm b/just/envs/.env.arm index dd480ad816..e13a6413fc 100644 --- a/just/envs/.env.arm +++ b/just/envs/.env.arm @@ -1,3 +1,7 @@ +CROSS_TARGET="armv7-unknown-linux-gnueabi" CROSS_CC="arm-linux-gnueabi-gcc" CROSS_CXX="arm-linux-gnueabi-g++" -CROSS_CFLAGS="" \ No newline at end of file +CROSS_OBJCOPY="arm-linux-gnueabi-objcopy" +CROSS_STRIP="arm-linux-gnueabi-strip" +CROSS_CFLAGS="" +LIBRASAN_CFLAGS="-no-pie -fno-stack-protector -marm" diff --git a/just/envs/.env.i386 b/just/envs/.env.i386 index 8b4afd471e..cc9e8d28d0 100644 --- a/just/envs/.env.i386 +++ b/just/envs/.env.i386 @@ -1,3 +1,7 @@ +CROSS_TARGET="i686-unknown-linux-gnu" CROSS_CC="i686-linux-gnu-gcc" CROSS_CXX="i686-linux-gnu-g++" +CROSS_OBJCOPY="i686-linux-gnu-objcopy" +CROSS_STRIP="i686-linux-gnu-strip" CROSS_CFLAGS="-m32" +LIBRASAN_CFLAGS="-m32 -no-pie -fno-stack-protector" diff --git a/just/envs/.env.mips b/just/envs/.env.mips index 436109bd06..ce72ff48db 100644 --- a/just/envs/.env.mips +++ b/just/envs/.env.mips @@ -1,3 +1,3 @@ CROSS_CC="mipsel-linux-gnu-gcc" CROSS_CXX="mipsel-linux-gnu-g++" -CROSS_CFLAGS="" \ No newline at end of file +CROSS_CFLAGS="" diff --git a/just/envs/.env.ppc b/just/envs/.env.ppc index f6fa2090b8..21ec176168 100644 --- a/just/envs/.env.ppc +++ b/just/envs/.env.ppc @@ -1,3 +1,7 @@ +CROSS_TARGET="powerpc-unknown-linux-gnu" CROSS_CC="powerpc-linux-gnu-gcc" CROSS_CXX="powerpc-linux-gnu-g++" +CROSS_OBJCOPY="powerpc-linux-gnu-objcopy" +CROSS_STRIP="powerpc-linux-gnu-strip" CROSS_CFLAGS="" +LIBRASAN_CFLAGS="-no-pie -fno-stack-protector" diff --git a/just/envs/.env.x86_64 b/just/envs/.env.x86_64 index a1eef0648e..3c55ee725b 100644 --- a/just/envs/.env.x86_64 +++ b/just/envs/.env.x86_64 @@ -1,3 +1,7 @@ +CROSS_TARGET="x86_64-unknown-linux-gnu" CROSS_CC="x86_64-linux-gnu-gcc" CROSS_CXX="x86_64-linux-gnu-g++" -CROSS_CFLAGS="" \ No newline at end of file +CROSS_OBJCOPY="x86_64-linux-gnu-objcopy" +CROSS_STRIP="x86_64-linux-gnu-strip" +CROSS_CFLAGS="" +LIBRASAN_CFLAGS="-m64 -no-pie -Wl,--no-relax -mcmodel=large -fno-stack-protector" diff --git a/just/libafl-qemu-libpng.just b/just/libafl-qemu-libpng.just index 644718c590..95fa0c4034 100644 --- a/just/libafl-qemu-libpng.just +++ b/just/libafl-qemu-libpng.just @@ -2,13 +2,10 @@ import "libafl-qemu.just" # Useful rules to build libpng for multiple architecture. -ARCH := env("ARCH", "x86_64") OPTIMIZATIONS := env("OPTIMIZATIONS", if ARCH == "ppc" { "no" } else { "yes" }) DEPS_DIR := TARGET_DIR / "deps" -DOTENV := source_directory() / "envs" / ".env." + ARCH - [unix] target_dir: mkdir -p {{ TARGET_DIR }} diff --git a/just/libafl-qemu.just b/just/libafl-qemu.just index cb299816f9..8391363a8e 100644 --- a/just/libafl-qemu.just +++ b/just/libafl-qemu.just @@ -1,3 +1,5 @@ import "libafl.just" -export LIBAFL_QEMU_DIR_DEFAULT := BUILD_DIR / "qemu-libafl-bridge" \ No newline at end of file +export LIBAFL_QEMU_DIR_DEFAULT := BUILD_DIR / "qemu-libafl-bridge" +ARCH := env("ARCH", "x86_64") +DOTENV := source_directory() / "envs" / ".env." + ARCH diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 09b68877e7..b6e917f974 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -52,6 +52,8 @@ fork = ["libafl/fork"] ## Build libqasan for address sanitization build_libgasan = [] build_libqasan = [] +## Use Address Sanitizer implementation written in rust (rather than the older C version) +rasan = [] #! ## The following architecture features are mutually exclusive. @@ -140,6 +142,7 @@ pyo3-build-config = { workspace = true, optional = true } rustversion = { workspace = true } bindgen = { workspace = true } cc = { workspace = true } +just = { workspace = true } [lib] name = "libafl_qemu" diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index af4fffec82..8b478893b5 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -212,7 +212,9 @@ pub fn build() { 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 = fs::canonicalize(qasan_dir).unwrap(); println!("cargo:rerun-if-changed={}", qasan_dir.display()); @@ -232,4 +234,42 @@ pub fn build() { .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"); + } } diff --git a/libafl_qemu/libafl_qemu_sys/src/usermode.rs b/libafl_qemu/libafl_qemu_sys/src/usermode.rs index 81d065d6e4..73f1c47f75 100644 --- a/libafl_qemu/libafl_qemu_sys/src/usermode.rs +++ b/libafl_qemu/libafl_qemu_sys/src/usermode.rs @@ -1,6 +1,8 @@ #[cfg(feature = "python")] use core::convert::Infallible; #[cfg(target_os = "linux")] +use core::fmt::{self, Display, Formatter}; +#[cfg(target_os = "linux")] use core::{slice::from_raw_parts, str::from_utf8_unchecked}; #[cfg(target_os = "linux")] @@ -34,6 +36,46 @@ pub struct MapInfo { 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_attr(feature = "python", pymethods)] impl MapInfo { diff --git a/libafl_qemu/librasan/.cargo/config.toml b/libafl_qemu/librasan/.cargo/config.toml new file mode 100644 index 0000000000..b18c512b27 --- /dev/null +++ b/libafl_qemu/librasan/.cargo/config.toml @@ -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/" diff --git a/libafl_qemu/librasan/.gitignore b/libafl_qemu/librasan/.gitignore new file mode 100644 index 0000000000..0ce11a7a9d --- /dev/null +++ b/libafl_qemu/librasan/.gitignore @@ -0,0 +1,2 @@ +/target/ +core diff --git a/libafl_qemu/librasan/Cargo.toml b/libafl_qemu/librasan/Cargo.toml new file mode 100644 index 0000000000..c02557ae2c --- /dev/null +++ b/libafl_qemu/librasan/Cargo.toml @@ -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 diff --git a/libafl_qemu/librasan/Justfile b/libafl_qemu/librasan/Justfile new file mode 100644 index 0000000000..98f39acfe0 --- /dev/null +++ b/libafl_qemu/librasan/Justfile @@ -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 diff --git a/libafl_qemu/librasan/README.md b/libafl_qemu/librasan/README.md new file mode 100644 index 0000000000..3c8a07d86c --- /dev/null +++ b/libafl_qemu/librasan/README.md @@ -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. diff --git a/libafl_qemu/librasan/asan/Cargo.toml b/libafl_qemu/librasan/asan/Cargo.toml new file mode 100644 index 0000000000..5c664012c2 --- /dev/null +++ b/libafl_qemu/librasan/asan/Cargo.toml @@ -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" } diff --git a/libafl_qemu/librasan/asan/Justfile b/libafl_qemu/librasan/asan/Justfile new file mode 100644 index 0000000000..158af58cf3 --- /dev/null +++ b/libafl_qemu/librasan/asan/Justfile @@ -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 diff --git a/libafl_qemu/librasan/asan/build.rs b/libafl_qemu/librasan/asan/build.rs new file mode 100644 index 0000000000..589b77c5bc --- /dev/null +++ b/libafl_qemu/librasan/asan/build.rs @@ -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"); +} diff --git a/libafl_qemu/librasan/asan/cc/include/hooks.h b/libafl_qemu/librasan/asan/cc/include/hooks.h new file mode 100644 index 0000000000..ba2d77c579 --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/include/hooks.h @@ -0,0 +1,9 @@ +#include + +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(); diff --git a/libafl_qemu/librasan/asan/cc/include/printf.h b/libafl_qemu/librasan/asan/cc/include/printf.h new file mode 100644 index 0000000000..8dbba1dd61 --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/include/printf.h @@ -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 +#include + +#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_ diff --git a/libafl_qemu/librasan/asan/cc/include/trace.h b/libafl_qemu/librasan/asan/cc/include/trace.h new file mode 100644 index 0000000000..b4d2c4b31e --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/include/trace.h @@ -0,0 +1,6 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +void trace(const char *fmt, ...); + +#endif diff --git a/libafl_qemu/librasan/asan/cc/src/asprintf.c b/libafl_qemu/librasan/asan/cc/src/asprintf.c new file mode 100644 index 0000000000..cd5ac928bc --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/src/asprintf.c @@ -0,0 +1,26 @@ +#include +#include +#include +#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); +} diff --git a/libafl_qemu/librasan/asan/cc/src/log.c b/libafl_qemu/librasan/asan/cc/src/log.c new file mode 100644 index 0000000000..ceeb1bf58b --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/src/log.c @@ -0,0 +1,21 @@ +#include +#include +#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 diff --git a/libafl_qemu/librasan/asan/cc/src/printf.c b/libafl_qemu/librasan/asan/cc/src/printf.c new file mode 100644 index 0000000000..68ad5af690 --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/src/printf.c @@ -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 +#include + +#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 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 +#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 +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; +} diff --git a/libafl_qemu/librasan/asan/cc/src/vasprintf.c b/libafl_qemu/librasan/asan/cc/src/vasprintf.c new file mode 100644 index 0000000000..35b7846eda --- /dev/null +++ b/libafl_qemu/librasan/asan/cc/src/vasprintf.c @@ -0,0 +1,23 @@ +#include +#include +#include +#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); +} diff --git a/libafl_qemu/librasan/asan/src/allocator/backend/dlmalloc.rs b/libafl_qemu/librasan/asan/src/allocator/backend/dlmalloc.rs new file mode 100644 index 0000000000..33049d14f5 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/backend/dlmalloc.rs @@ -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 { + page_size: usize, + _phantom: PhantomData, +} + +unsafe impl Allocator for DlmallocBackendMap { + 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 DlmallocBackendMap { + pub const fn new(page_size: usize) -> DlmallocBackendMap { + DlmallocBackendMap { + page_size, + _phantom: PhantomData, + } + } +} + +pub struct DlmallocBackend { + dlmalloc: Mutex>>, +} + +impl DlmallocBackend { + pub const fn new(page_size: usize) -> DlmallocBackend { + let backend = DlmallocBackendMap::new(page_size); + let dlmalloc = Dlmalloc::>::new_with_allocator(backend); + Self { + dlmalloc: Mutex::new(dlmalloc), + } + } +} + +impl Debug for DlmallocBackend { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "DlmallocBackend") + } +} + +unsafe impl GlobalAlloc for DlmallocBackend { + 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()) + } + } +} diff --git a/libafl_qemu/librasan/asan/src/allocator/backend/mimalloc.rs b/libafl_qemu/librasan/asan/src/allocator/backend/mimalloc.rs new file mode 100644 index 0000000000..4831db826d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/backend/mimalloc.rs @@ -0,0 +1,26 @@ +use alloc::alloc::{GlobalAlloc, Layout}; + +use baby_mimalloc::Mimalloc; +use spin::Mutex; + +pub struct MimallocBackend { + mimalloc: Mutex>, +} + +impl MimallocBackend { + pub const fn new(global_allocator: G) -> Self { + MimallocBackend { + mimalloc: Mutex::new(Mimalloc::with_os_allocator(global_allocator)), + } + } +} + +unsafe impl GlobalAlloc for MimallocBackend { + 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) } + } +} diff --git a/libafl_qemu/librasan/asan/src/allocator/backend/mod.rs b/libafl_qemu/librasan/asan/src/allocator/backend/mod.rs new file mode 100644 index 0000000000..748557098d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/backend/mod.rs @@ -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; diff --git a/libafl_qemu/librasan/asan/src/allocator/frontend/default.rs b/libafl_qemu/librasan/asan/src/allocator/frontend/default.rs new file mode 100644 index 0000000000..c7ee04877d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/frontend/default.rs @@ -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 { + backend: B, + shadow: S, + tracking: T, + red_zone_size: usize, + allocations: BTreeMap, + quarantine: VecDeque, + quarantine_size: usize, + quaratine_used: usize, +} + +impl AllocatorFrontend for DefaultFrontend { + type Error = DefaultFrontendError; + + fn alloc(&mut self, len: usize, align: usize) -> Result { + debug!("alloc - len: 0x{:x}, align: 0x{:x}", len, align); + if align % size_of::() != 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 { + debug!("get_size - addr: 0x{:x}", addr); + let alloc = self + .allocations + .get(&addr) + .ok_or_else(|| DefaultFrontendError::InvalidAddress(addr))?; + Ok(alloc.frontend_len) + } +} + +impl DefaultFrontend { + #[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, DefaultFrontendError> { + if red_zone_size % Self::ALLOC_ALIGN_SIZE != 0 { + Err(DefaultFrontendError::InvalidRedZoneSize(red_zone_size))?; + } + Ok(DefaultFrontend:: { + backend, + shadow, + tracking, + red_zone_size, + allocations: BTreeMap::new(), + quarantine: VecDeque::new(), + quarantine_size, + quaratine_used: 0, + }) + } + + fn purge_quarantine(&mut self) -> Result<(), DefaultFrontendError> { + 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 { + #[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, +} diff --git a/libafl_qemu/librasan/asan/src/allocator/frontend/mod.rs b/libafl_qemu/librasan/asan/src/allocator/frontend/mod.rs new file mode 100644 index 0000000000..a12d95a852 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/frontend/mod.rs @@ -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; + fn dealloc(&mut self, addr: GuestAddr) -> Result<(), Self::Error>; + fn get_size(&self, addr: GuestAddr) -> Result; +} diff --git a/libafl_qemu/librasan/asan/src/allocator/mod.rs b/libafl_qemu/librasan/asan/src/allocator/mod.rs new file mode 100644 index 0000000000..ef96646de8 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/allocator/mod.rs @@ -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; diff --git a/libafl_qemu/librasan/asan/src/arch/aarch64.rs b/libafl_qemu/librasan/asan/src/arch/aarch64.rs new file mode 100644 index 0000000000..3366f7ad84 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/arch/aarch64.rs @@ -0,0 +1,4 @@ +#[unsafe(no_mangle)] +extern "C" fn getauxval(_type: u64) -> u64 { + 0 +} diff --git a/libafl_qemu/librasan/asan/src/arch/arm.rs b/libafl_qemu/librasan/asan/src/arch/arm.rs new file mode 100644 index 0000000000..ddf2faa159 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/arch/arm.rs @@ -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(); +} diff --git a/libafl_qemu/librasan/asan/src/arch/mod.rs b/libafl_qemu/librasan/asan/src/arch/mod.rs new file mode 100644 index 0000000000..79d1d01867 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/arch/mod.rs @@ -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(); +} diff --git a/libafl_qemu/librasan/asan/src/exit/libc.rs b/libafl_qemu/librasan/asan/src/exit/libc.rs new file mode 100644 index 0000000000..7d7c1696be --- /dev/null +++ b/libafl_qemu/librasan/asan/src/exit/libc.rs @@ -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) }; +} diff --git a/libafl_qemu/librasan/asan/src/exit/linux.rs b/libafl_qemu/librasan/asan/src/exit/linux.rs new file mode 100644 index 0000000000..603f186a75 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/exit/linux.rs @@ -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!(); +} diff --git a/libafl_qemu/librasan/asan/src/exit/mod.rs b/libafl_qemu/librasan/asan/src/exit/mod.rs new file mode 100644 index 0000000000..219ac48e10 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/exit/mod.rs @@ -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; diff --git a/libafl_qemu/librasan/asan/src/hooks/aligned_alloc.rs b/libafl_qemu/librasan/asan/src/hooks/aligned_alloc.rs new file mode 100644 index 0000000000..8559c06058 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/aligned_alloc.rs @@ -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::() != 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) + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/atoi.rs b/libafl_qemu/librasan/asan/src/hooks/atoi.rs new file mode 100644 index 0000000000..212971738c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/atoi.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/atol.rs b/libafl_qemu/librasan/asan/src/hooks/atol.rs new file mode 100644 index 0000000000..395ec34f30 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/atol.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/atoll.rs b/libafl_qemu/librasan/asan/src/hooks/atoll.rs new file mode 100644 index 0000000000..b17bc5f9f4 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/atoll.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/bcmp.rs b/libafl_qemu/librasan/asan/src/hooks/bcmp.rs new file mode 100644 index 0000000000..44bc1bcce4 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/bcmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/bzero.rs b/libafl_qemu/librasan/asan/src/hooks/bzero.rs new file mode 100644 index 0000000000..d9eeaba6ee --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/bzero.rs @@ -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); + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/calloc.rs b/libafl_qemu/librasan/asan/src/hooks/calloc.rs new file mode 100644 index 0000000000..213f4285f1 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/calloc.rs @@ -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); + } + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/explicit_bzero.rs b/libafl_qemu/librasan/asan/src/hooks/explicit_bzero.rs new file mode 100644 index 0000000000..1dc9ec0a14 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/explicit_bzero.rs @@ -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); + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/fgets.rs b/libafl_qemu/librasan/asan/src/hooks/fgets.rs new file mode 100644 index 0000000000..50a1e60345 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/fgets.rs @@ -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::()); + 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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/free.rs b/libafl_qemu/librasan/asan/src/hooks/free.rs new file mode 100644 index 0000000000..7ba9ef8339 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/free.rs @@ -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); + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/malloc.rs b/libafl_qemu/librasan/asan/src/hooks/malloc.rs new file mode 100644 index 0000000000..3d59661bb0 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/malloc.rs @@ -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) + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/malloc_usable_size.rs b/libafl_qemu/librasan/asan/src/hooks/malloc_usable_size.rs new file mode 100644 index 0000000000..02c2a31223 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/malloc_usable_size.rs @@ -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) } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memalign.rs b/libafl_qemu/librasan/asan/src/hooks/memalign.rs new file mode 100644 index 0000000000..e826070aa6 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memalign.rs @@ -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::() != 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) + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memchr.rs b/libafl_qemu/librasan/asan/src/hooks/memchr.rs new file mode 100644 index 0000000000..3503680dc0 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memchr.rs @@ -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(), + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memcmp.rs b/libafl_qemu/librasan/asan/src/hooks/memcmp.rs new file mode 100644 index 0000000000..2b67cabab5 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memcmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memcpy.rs b/libafl_qemu/librasan/asan/src/hooks/memcpy.rs new file mode 100644 index 0000000000..c9bebfdb50 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memcpy.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memmem.rs b/libafl_qemu/librasan/asan/src/hooks/memmem.rs new file mode 100644 index 0000000000..25323ca2b9 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memmem.rs @@ -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() + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memmove.rs b/libafl_qemu/librasan/asan/src/hooks/memmove.rs new file mode 100644 index 0000000000..f2207c814c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memmove.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/mempcpy.rs b/libafl_qemu/librasan/asan/src/hooks/mempcpy.rs new file mode 100644 index 0000000000..9822e35484 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/mempcpy.rs @@ -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) + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memrchr.rs b/libafl_qemu/librasan/asan/src/hooks/memrchr.rs new file mode 100644 index 0000000000..768cbb3ae7 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memrchr.rs @@ -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(), + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/memset.rs b/libafl_qemu/librasan/asan/src/hooks/memset.rs new file mode 100644 index 0000000000..3ee986606b --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/memset.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/mmap/libc.rs b/libafl_qemu/librasan/asan/src/hooks/mmap/libc.rs new file mode 100644 index 0000000000..5d055125fc --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/mmap/libc.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/mmap/linux.rs b/libafl_qemu/librasan/asan/src/hooks/mmap/linux.rs new file mode 100644 index 0000000000..ba4a03c7d2 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/mmap/linux.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/mmap/mod.rs b/libafl_qemu/librasan/asan/src/hooks/mmap/mod.rs new file mode 100644 index 0000000000..76d1138876 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/mmap/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +pub mod linux; diff --git a/libafl_qemu/librasan/asan/src/hooks/mod.rs b/libafl_qemu/librasan/asan/src/hooks/mod.rs new file mode 100644 index 0000000000..a1501266f2 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/mod.rs @@ -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(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 { + [ + PatchedHook::new:: *mut c_void>( + c"aligned_alloc", + hooks::aligned_alloc::aligned_alloc, + ), + PatchedHook::new:: 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::( + c"bzero", + hooks::bzero::bzero, + ), + PatchedHook::new::( + c"explicit_bzero", + hooks::explicit_bzero::explicit_bzero, + ), + PatchedHook::new:: *mut c_void>( + c"memchr", + hooks::memchr::memchr, + ), + PatchedHook::new:: 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:: *mut c_void>( + c"memrchr", + hooks::memrchr::memrchr, + ), + PatchedHook::new:: *mut c_char>( + c"stpcpy", + hooks::stpcpy::stpcpy, + ), + PatchedHook::new:: c_int>( + c"strcasecmp", + hooks::strcasecmp::strcasecmp, + ), + PatchedHook::new:: *mut c_char>( + c"strcasestr", + hooks::strcasestr::strcasestr, + ), + PatchedHook::new:: *mut c_char>( + c"strcat", + hooks::strcat::strcat, + ), + PatchedHook::new:: *mut c_char>( + c"strchr", + hooks::strchr::strchr, + ), + PatchedHook::new:: c_int>( + c"strcmp", + hooks::strcmp::strcmp, + ), + PatchedHook::new:: *mut c_char>( + c"strcpy", + hooks::strcpy::strcpy, + ), + PatchedHook::new:: *mut c_char>( + c"strdup", + hooks::strdup::strdup, + ), + PatchedHook::new:: size_t>( + c"strlen", + hooks::strlen::strlen, + ), + PatchedHook::new:: c_int>( + c"strncasecmp", + hooks::strncasecmp::strncasecmp, + ), + PatchedHook::new:: 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:: *mut c_char>( + c"strndup", + hooks::strndup::strndup, + ), + PatchedHook::new:: size_t>( + c"strnlen", + hooks::strnlen::strnlen, + ), + PatchedHook::new:: *mut c_char>( + c"strrchr", + hooks::strrchr::strrchr, + ), + PatchedHook::new:: *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:: c_int>( + c"wcscmp", + hooks::wcscmp::wcscmp, + ), + PatchedHook::new:: *mut wchar_t>( + c"wcscpy", + hooks::wcscpy::wcscpy, + ), + PatchedHook::new:: size_t>( + c"wcslen", + hooks::wcslen::wcslen, + ), + ] + .to_vec() + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/munmap/libc.rs b/libafl_qemu/librasan/asan/src/hooks/munmap/libc.rs new file mode 100644 index 0000000000..47360a0351 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/munmap/libc.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/munmap/linux.rs b/libafl_qemu/librasan/asan/src/hooks/munmap/linux.rs new file mode 100644 index 0000000000..34643ecf80 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/munmap/linux.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/munmap/mod.rs b/libafl_qemu/librasan/asan/src/hooks/munmap/mod.rs new file mode 100644 index 0000000000..76d1138876 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/munmap/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +pub mod linux; diff --git a/libafl_qemu/librasan/asan/src/hooks/posix_memalign.rs b/libafl_qemu/librasan/asan/src/hooks/posix_memalign.rs new file mode 100644 index 0000000000..6abb6b8731 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/posix_memalign.rs @@ -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::() != 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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/pvalloc.rs b/libafl_qemu/librasan/asan/src/hooks/pvalloc.rs new file mode 100644 index 0000000000..60b03ea1f3 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/pvalloc.rs @@ -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) + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/read/libc.rs b/libafl_qemu/librasan/asan/src/hooks/read/libc.rs new file mode 100644 index 0000000000..f3045a407b --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/read/libc.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/read/linux.rs b/libafl_qemu/librasan/asan/src/hooks/read/linux.rs new file mode 100644 index 0000000000..9a0da46828 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/read/linux.rs @@ -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; + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/read/mod.rs b/libafl_qemu/librasan/asan/src/hooks/read/mod.rs new file mode 100644 index 0000000000..76d1138876 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/read/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +pub mod linux; diff --git a/libafl_qemu/librasan/asan/src/hooks/realloc.rs b/libafl_qemu/librasan/asan/src/hooks/realloc.rs new file mode 100644 index 0000000000..635a088eef --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/realloc.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/reallocarray.rs b/libafl_qemu/librasan/asan/src/hooks/reallocarray.rs new file mode 100644 index 0000000000..defe3258c7 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/reallocarray.rs @@ -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), + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/stpcpy.rs b/libafl_qemu/librasan/asan/src/hooks/stpcpy.rs new file mode 100644 index 0000000000..c00201182b --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/stpcpy.rs @@ -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) + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strcasecmp.rs b/libafl_qemu/librasan/asan/src/hooks/strcasecmp.rs new file mode 100644 index 0000000000..6d93f759e3 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strcasecmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strcasestr.rs b/libafl_qemu/librasan/asan/src/hooks/strcasestr.rs new file mode 100644 index 0000000000..36795dd2c7 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strcasestr.rs @@ -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::>(); + let ct_slice = from_raw_parts(ct, ct_len) + .iter() + .cloned() + .map(to_upper) + .collect::>(); + 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() + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strcat.rs b/libafl_qemu/librasan/asan/src/hooks/strcat.rs new file mode 100644 index 0000000000..c67299f55c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strcat.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strchr.rs b/libafl_qemu/librasan/asan/src/hooks/strchr.rs new file mode 100644 index 0000000000..1cbf6acaa0 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strchr.rs @@ -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(), + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strcmp.rs b/libafl_qemu/librasan/asan/src/hooks/strcmp.rs new file mode 100644 index 0000000000..027b75529c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strcmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strcpy.rs b/libafl_qemu/librasan/asan/src/hooks/strcpy.rs new file mode 100644 index 0000000000..cc9d5a0dd2 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strcpy.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strdup.rs b/libafl_qemu/librasan/asan/src/hooks/strdup.rs new file mode 100644 index 0000000000..216e58b878 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strdup.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strlen.rs b/libafl_qemu/librasan/asan/src/hooks/strlen.rs new file mode 100644 index 0000000000..1d8921100f --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strlen.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strncasecmp.rs b/libafl_qemu/librasan/asan/src/hooks/strncasecmp.rs new file mode 100644 index 0000000000..3a890ad07d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strncasecmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strncmp.rs b/libafl_qemu/librasan/asan/src/hooks/strncmp.rs new file mode 100644 index 0000000000..b6031ada4d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strncmp.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strncpy.rs b/libafl_qemu/librasan/asan/src/hooks/strncpy.rs new file mode 100644 index 0000000000..0561c518e2 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strncpy.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strndup.rs b/libafl_qemu/librasan/asan/src/hooks/strndup.rs new file mode 100644 index 0000000000..0441cdc758 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strndup.rs @@ -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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strnlen.rs b/libafl_qemu/librasan/asan/src/hooks/strnlen.rs new file mode 100644 index 0000000000..81f6753136 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strnlen.rs @@ -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 + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strrchr.rs b/libafl_qemu/librasan/asan/src/hooks/strrchr.rs new file mode 100644 index 0000000000..83c5ec7616 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strrchr.rs @@ -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(), + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/strstr.rs b/libafl_qemu/librasan/asan/src/hooks/strstr.rs new file mode 100644 index 0000000000..a2aa1cfe95 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/strstr.rs @@ -0,0 +1,55 @@ +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_strstr")] +pub unsafe extern "C" fn strstr(cs: *const c_char, ct: *const c_char) -> *mut c_char { + unsafe { + trace!("strstr - cs: {:p}, ct: {:p}", cs, ct); + + if cs.is_null() { + asan_panic(c"strstr - cs is null".as_ptr() as *const c_char); + } + + if ct.is_null() { + asan_panic(c"strstr - 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 cs_slice = from_raw_parts(cs, cs_len); + let ct_slice = from_raw_parts(ct, ct_len); + 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() + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/valloc.rs b/libafl_qemu/librasan/asan/src/hooks/valloc.rs new file mode 100644 index 0000000000..2d283a4403 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/valloc.rs @@ -0,0 +1,20 @@ +use core::{ffi::c_void, ptr::null_mut}; + +use log::trace; + +use crate::{asan_alloc, asan_page_size, size_t}; + +/// # Safety +/// See man pages +#[unsafe(export_name = "patch_valloc")] +pub unsafe extern "C" fn valloc(size: size_t) -> *mut c_void { + unsafe { + trace!("valloc - size: {:#x}", size); + + if size == 0 { + null_mut() + } else { + asan_alloc(size, asan_page_size()) + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/wcscmp.rs b/libafl_qemu/librasan/asan/src/hooks/wcscmp.rs new file mode 100644 index 0000000000..38f24be58c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/wcscmp.rs @@ -0,0 +1,60 @@ +use core::{ + cmp::Ordering, + ffi::{c_char, c_int, c_void}, + slice::from_raw_parts, +}; + +use log::trace; + +use crate::{asan_load, asan_panic, wchar_t}; + +/// # Safety +/// See man pages +#[unsafe(export_name = "patch_wcscmp")] +pub unsafe extern "C" fn wcscmp(cs: *const wchar_t, ct: *const wchar_t) -> c_int { + unsafe { + trace!("wcscmp - cs: {:p}, ct: {:p}", cs, ct); + + if cs.is_null() { + asan_panic(c"wcscmp - cs is null".as_ptr() as *const c_char); + } + + if ct.is_null() { + asan_panic(c"wcscmp - 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, size_of::() * (cs_len + 1)); + asan_load(ct as *const c_void, size_of::() * (ct_len + 1)); + + let slice1 = from_raw_parts(cs, cs_len); + let slice2 = from_raw_parts(ct, 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 + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/wcscpy.rs b/libafl_qemu/librasan/asan/src/hooks/wcscpy.rs new file mode 100644 index 0000000000..4c154b131a --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/wcscpy.rs @@ -0,0 +1,34 @@ +use core::{ + ffi::{c_char, c_void}, + ptr::copy, +}; + +use log::trace; + +use crate::{asan_load, asan_panic, asan_store, wchar_t}; + +/// # Safety +/// See man pages +#[unsafe(export_name = "patch_wcscpy")] +pub unsafe extern "C" fn wcscpy(dst: *mut wchar_t, src: *const wchar_t) -> *mut wchar_t { + unsafe { + trace!("wcscpy - dst: {:p}, src: {:p}", dst, src); + + if dst.is_null() { + asan_panic(c"wcscpy - dst is null".as_ptr() as *const c_char); + } + + if src.is_null() { + asan_panic(c"wcscpy - 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, size_of::() * (len + 1)); + asan_store(dst as *const c_void, size_of::() * (len + 1)); + copy(src, dst, len + 1); + dst + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/wcslen.rs b/libafl_qemu/librasan/asan/src/hooks/wcslen.rs new file mode 100644 index 0000000000..38dfb4e8c1 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/wcslen.rs @@ -0,0 +1,25 @@ +use core::ffi::{c_char, c_void}; + +use log::trace; + +use crate::{asan_load, asan_panic, size_t, wchar_t}; + +/// # Safety +/// See man pages +#[unsafe(export_name = "patch_wcslen")] +pub unsafe extern "C" fn wcslen(buf: *const wchar_t) -> size_t { + unsafe { + trace!("wcslen - buf: {:p}", buf); + + if buf.is_null() { + asan_panic(c"wcslen - buf is null".as_ptr() as *const c_char); + } + + let mut len = 0; + while *buf.add(len) != 0 { + len += 1; + } + asan_load(buf as *const c_void, size_of::() * (len + 1)); + len + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/write/libc.rs b/libafl_qemu/librasan/asan/src/hooks/write/libc.rs new file mode 100644 index 0000000000..2e02f09796 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/write/libc.rs @@ -0,0 +1,41 @@ +use core::ffi::{CStr, c_char, c_long}; + +use libc::{SYS_write, c_int, c_void}; +use log::trace; + +use crate::{ + asan_load, asan_panic, 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_write")] +pub unsafe extern "C" fn write(fd: c_int, buf: *const c_void, count: size_t) -> ssize_t { + unsafe { + trace!("write - fd: {:#x}, buf: {:p}, count: {:#x}", fd, buf, count); + + if buf.is_null() && count != 0 { + asan_panic(c"msg is null".as_ptr() as *const c_char); + } + + asan_load(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_write, fd, buf, count); + asan_swap(true); + ret as ssize_t + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/write/linux.rs b/libafl_qemu/librasan/asan/src/hooks/write/linux.rs new file mode 100644 index 0000000000..6239797c14 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/write/linux.rs @@ -0,0 +1,31 @@ +use core::{ + ffi::{c_char, c_int, c_void}, + slice::from_raw_parts, +}; + +use log::trace; +use rustix::{fd::BorrowedFd, io}; + +use crate::{asan_load, asan_panic, size_t, ssize_t}; + +/// # Safety +/// See man pages +#[unsafe(export_name = "patch_write")] +pub unsafe extern "C" fn write(fd: c_int, buf: *const c_void, count: size_t) -> ssize_t { + unsafe { + trace!("write - fd: {:#x}, buf: {:p}, count: {:#x}", fd, buf, count); + + if buf.is_null() && count != 0 { + asan_panic(c"write - buf is null".as_ptr() as *const c_char); + } + + asan_load(buf, count); + let file = BorrowedFd::borrow_raw(fd); + let data = from_raw_parts(buf as *const u8, count as usize); + if let Ok(ret) = io::write(file, data) { + return ret as ssize_t; + } else { + return -1; + } + } +} diff --git a/libafl_qemu/librasan/asan/src/hooks/write/mod.rs b/libafl_qemu/librasan/asan/src/hooks/write/mod.rs new file mode 100644 index 0000000000..76d1138876 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/hooks/write/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +pub mod linux; diff --git a/libafl_qemu/librasan/asan/src/host/libc.rs b/libafl_qemu/librasan/asan/src/host/libc.rs new file mode 100644 index 0000000000..cd235659fa --- /dev/null +++ b/libafl_qemu/librasan/asan/src/host/libc.rs @@ -0,0 +1,157 @@ +//! # linux +//! The `LibcHost` supports the established means of interacting with the QEMU +//! emulator on Linux by means of issuing a bespoke syscall via the libc library +use core::{ + ffi::{CStr, c_long}, + marker::PhantomData, +}; + +use thiserror::Error; + +use crate::{ + GuestAddr, + host::{Host, HostAction}, + shadow::PoisonType, + symbols::{ + AtomicGuestAddr, Function, FunctionPointer, FunctionPointerError, Symbols, SymbolsLookupStr, + }, +}; + +#[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"; +} + +#[derive(Debug)] +pub struct LibcHost { + _phantom: PhantomData, +} + +impl Host for LibcHost { + type Error = LibcHostError; + + fn load(start: GuestAddr, len: usize) -> Result<(), LibcHostError> { + unsafe { + let syscall = Self::get_syscall()?; + let ret = syscall(Self::SYSCALL_NO, HostAction::CheckLoad as usize, start, len); + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + } + Ok(()) + } + + fn store(start: GuestAddr, len: usize) -> Result<(), LibcHostError> { + unsafe { + let syscall = Self::get_syscall()?; + let ret = syscall( + Self::SYSCALL_NO, + HostAction::CheckStore as usize, + start, + len, + ); + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + }; + Ok(()) + } + + fn poison(start: GuestAddr, len: usize, val: PoisonType) -> Result<(), LibcHostError> { + unsafe { + let syscall = Self::get_syscall()?; + let ret = syscall( + Self::SYSCALL_NO, + HostAction::Poison as usize, + start, + len, + val as usize, + ); + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + }; + Ok(()) + } + + fn unpoison(start: GuestAddr, len: usize) -> Result<(), LibcHostError> { + let syscall = Self::get_syscall()?; + let ret = unsafe { syscall(Self::SYSCALL_NO, HostAction::Unpoison as usize, start, len) }; + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + Ok(()) + } + + fn is_poison(start: GuestAddr, len: usize) -> Result> { + let syscall = Self::get_syscall()?; + let ret = unsafe { syscall(Self::SYSCALL_NO, HostAction::IsPoison as usize, start, len) }; + match ret { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(LibcHostError::SyscallError(ret)), + } + } + + fn swap(enabled: bool) -> Result<(), LibcHostError> { + unsafe { + let syscall = Self::get_syscall()?; + let ret = syscall( + Self::SYSCALL_NO, + HostAction::SwapState as usize, + enabled as usize, + ); + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + }; + Ok(()) + } + + fn alloc(start: GuestAddr, len: usize) -> Result<(), LibcHostError> { + unsafe { + let syscall = Self::get_syscall()?; + let ret = syscall(Self::SYSCALL_NO, HostAction::Alloc as usize, start, len); + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + }; + Ok(()) + } + + fn dealloc(start: GuestAddr) -> Result<(), LibcHostError> { + let syscall = Self::get_syscall()?; + let ret = unsafe { syscall(Self::SYSCALL_NO, HostAction::Dealloc as usize, start) }; + if ret != 0 { + return Err(LibcHostError::SyscallError(ret)); + } + Ok(()) + } +} + +static SYSCALL_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); + +impl LibcHost { + const SYSCALL_NO: c_long = 0xa2a4; + + fn get_syscall() -> Result<::Func, LibcHostError> { + let addr = SYSCALL_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionSyscall::NAME).map_err(|e| LibcHostError::FailedToFindSymbol(e)) + })?; + let f = FunctionSyscall::as_ptr(addr).map_err(|e| LibcHostError::InvalidPointerType(e))?; + Ok(f) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum LibcHostError { + #[error("Syscall error: {0:?}")] + SyscallError(c_long), + #[error("Failed to find mmap functions")] + FailedToFindSymbol(S::Error), + #[error("Invalid pointer type: {0:?}")] + InvalidPointerType(FunctionPointerError), +} diff --git a/libafl_qemu/librasan/asan/src/host/linux.rs b/libafl_qemu/librasan/asan/src/host/linux.rs new file mode 100644 index 0000000000..6b0321cd4a --- /dev/null +++ b/libafl_qemu/librasan/asan/src/host/linux.rs @@ -0,0 +1,88 @@ +//! # linux +//! The `LinuxHost` supports the established means of interacting with the QEMU +//! emulator on Linux by means of issuing a bespoke syscall. +use core::mem::transmute; + +use syscalls::{Errno, Sysno, syscall2, syscall3, syscall4}; + +use crate::{ + GuestAddr, + host::{Host, HostAction}, + shadow::PoisonType, +}; + +#[derive(Debug)] +pub struct LinuxHost; + +pub type LinuxHostResult = Result; + +impl Host for LinuxHost { + type Error = Errno; + + fn load(start: GuestAddr, len: usize) -> LinuxHostResult<()> { + unsafe { + syscall3(Self::sysno(), HostAction::CheckLoad as usize, start, len)?; + } + Ok(()) + } + + fn store(start: GuestAddr, len: usize) -> LinuxHostResult<()> { + unsafe { + syscall3(Self::sysno(), HostAction::CheckStore as usize, start, len)?; + }; + Ok(()) + } + + fn poison(start: GuestAddr, len: usize, val: PoisonType) -> LinuxHostResult<()> { + unsafe { + syscall4( + Self::sysno(), + HostAction::Poison as usize, + start, + len, + val as usize, + )?; + }; + Ok(()) + } + + fn unpoison(start: GuestAddr, len: usize) -> LinuxHostResult<()> { + unsafe { syscall3(Self::sysno(), HostAction::Unpoison as usize, start, len)? }; + Ok(()) + } + + fn is_poison(start: GuestAddr, len: usize) -> LinuxHostResult { + unsafe { Ok(syscall3(Self::sysno(), HostAction::IsPoison as usize, start, len)? != 0) } + } + + fn swap(enabled: bool) -> LinuxHostResult<()> { + unsafe { + syscall2( + Self::sysno(), + HostAction::SwapState as usize, + enabled as usize, + )?; + }; + Ok(()) + } + + fn alloc(start: GuestAddr, len: usize) -> LinuxHostResult<()> { + unsafe { + syscall3(Self::sysno(), HostAction::Alloc as usize, start, len)?; + }; + Ok(()) + } + + fn dealloc(start: GuestAddr) -> LinuxHostResult<()> { + unsafe { syscall2(Self::sysno(), HostAction::Dealloc as usize, start)? }; + Ok(()) + } +} + +impl LinuxHost { + const SYSCALL_NO: u32 = 0xa2a4; + + pub fn sysno() -> Sysno { + unsafe { transmute(Self::SYSCALL_NO) } + } +} diff --git a/libafl_qemu/librasan/asan/src/host/mod.rs b/libafl_qemu/librasan/asan/src/host/mod.rs new file mode 100644 index 0000000000..a223e652c4 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/host/mod.rs @@ -0,0 +1,44 @@ +//! # host +//! The host module is responsible for interacting with the emulator hosting the +//! target. It provides an abstraction to allow alternative implementations to +//! be used in the event a different emulator is used, or if the target +//! application is for a different operating system, then the interface for +//! interacting the host may be different. +use core::fmt::Debug; + +use crate::{GuestAddr, shadow::PoisonType}; + +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(feature = "linux")] +pub mod linux; + +#[repr(usize)] +#[derive(Copy, Clone, Debug)] +#[allow(dead_code)] +enum HostAction { + CheckLoad, + CheckStore, + Poison, + UserPoison, + Unpoison, + IsPoison, + Alloc, + Dealloc, + Enable, + Disable, + SwapState, +} + +pub trait Host: Debug + Send { + type Error: Debug; + fn load(start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn store(start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn poison(start: GuestAddr, len: usize, val: PoisonType) -> Result<(), Self::Error>; + fn unpoison(start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn is_poison(start: GuestAddr, len: usize) -> Result; + fn swap(enabled: bool) -> Result<(), Self::Error>; + fn alloc(start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn dealloc(start: GuestAddr) -> Result<(), Self::Error>; +} diff --git a/libafl_qemu/librasan/asan/src/lib.rs b/libafl_qemu/librasan/asan/src/lib.rs new file mode 100644 index 0000000000..09585c5143 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/lib.rs @@ -0,0 +1,141 @@ +//! # asan +//! +//! `asan` 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 proposed: +//! +//! - `qasan` - Intended as a drop in replacement for the original libqasan, +//! this will have dependency on `libc` and will interact with QEMU using the +//! bespoke syscall interface to perform memory tracking and shadowing. +//! - `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 in bare-metal targets or +//! targets which have statically linked `libc`. +//! +//! The following ancilliary crates are provided as follows: +//! - `dummy_libc` - A dummy libc library used during linking which provides +//! only the symbols `dlsym` and `dlerror`. This is intended to ensure that +//! `gasan` and `qasan` do not have any direct dependency on libc and hence +//! avoids the possibility of accidental re-entrancy. (e.g. in the case that +//! we have hooked a function such as `malloc` and in our handling of the call +//! inadvertently call `malloc`, or one of our other hooked functions +//! ourselves). +//! - `fuzz` - A few different fuzzing harnesses used to test `asan`. +//! +//! The componentized nature of the design is intended to permit the user to +//! adapt `asan` 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 +//! - `hooks` - Enable support for hooking functions in the guest +//! - `host` - Enable support for shadow memory and tracking in the host +//! - `libc` - Enable use of the `libc` library to support creation of mappings, +//! read/write, logging etc (more OS agnostic) +//! - `linux` - Enable the use of direct syscalls (supported by `rustix`) to +//! interact with the operating system (Linux specific). +//! - `test` - Disable the magic used to support `no_std` environments for +//! running unit and integration tests +//! - `tracking` - Enable support for memory tracking. +#![cfg_attr(not(feature = "test"), no_std)] +#![cfg_attr(target_arch = "powerpc", feature(asm_experimental_arch))] + +pub mod allocator; + +#[cfg(not(feature = "test"))] +pub mod arch; + +pub mod exit; + +#[cfg(feature = "hooks")] +pub mod hooks; + +#[cfg(feature = "host")] +pub mod host; + +pub mod logger; + +pub mod maps; + +#[cfg(not(feature = "test"))] +pub mod mem; + +pub mod mmap; + +#[cfg(not(feature = "test"))] +mod nostd; + +pub mod patch; + +pub mod shadow; + +pub mod symbols; + +#[cfg(feature = "test")] +pub mod test; + +#[cfg(feature = "test")] +pub use test::*; + +pub mod tracking; + +extern crate alloc; + +pub type GuestAddr = usize; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +#[allow(non_camel_case_types)] +pub type size_t = usize; + +#[cfg(feature = "libc")] +#[allow(non_camel_case_types)] +pub type size_t = libc::size_t; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +#[allow(non_camel_case_types)] +pub type ssize_t = isize; + +#[cfg(feature = "libc")] +#[allow(non_camel_case_types)] +pub type ssize_t = libc::ssize_t; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +#[allow(non_camel_case_types)] +pub type wchar_t = i32; + +#[cfg(feature = "libc")] +#[allow(non_camel_case_types)] +pub type wchar_t = libc::wchar_t; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +#[allow(non_camel_case_types)] +pub type off_t = isize; + +#[cfg(feature = "libc")] +#[allow(non_camel_case_types)] +pub type off_t = libc::off_t; + +#[cfg(not(feature = "test"))] +use ::core::ffi::{c_char, c_void}; + +#[cfg(not(feature = "test"))] +unsafe extern "C" { + pub fn asan_load(addr: *const c_void, size: usize); + pub fn asan_store(addr: *const c_void, size: usize); + pub fn asan_alloc(len: usize, align: usize) -> *mut c_void; + pub fn asan_dealloc(addr: *const c_void); + pub fn asan_get_size(addr: *const c_void) -> usize; + #[cfg(feature = "libc")] + pub fn asan_sym(name: *const c_char) -> GuestAddr; + pub fn asan_page_size() -> usize; + pub fn asan_unpoison(addr: *mut c_void, len: usize); + pub fn asan_track(addr: *mut c_void, len: usize); + pub fn asan_untrack(addr: *mut c_void); + pub fn asan_panic(msg: *const c_char) -> !; + pub fn asan_swap(enabled: bool); +} diff --git a/libafl_qemu/librasan/asan/src/logger/libc.rs b/libafl_qemu/librasan/asan/src/logger/libc.rs new file mode 100644 index 0000000000..0f2500b3da --- /dev/null +++ b/libafl_qemu/librasan/asan/src/logger/libc.rs @@ -0,0 +1,67 @@ +use alloc::{boxed::Box, format}; +use core::ffi::{CStr, c_int, c_void}; + +use libc::{STDERR_FILENO, size_t, ssize_t}; +use log::{Level, LevelFilter, Log, Metadata, Record}; +use spin::Once; + +use crate::{ + GuestAddr, asan_swap, + symbols::{Function, FunctionPointer, Symbols, SymbolsLookupStr}, +}; + +#[derive(Debug)] +struct FunctionWrite; + +impl Function for FunctionWrite { + type Func = unsafe extern "C" fn(c_int, *const c_void, size_t) -> ssize_t; + const NAME: &'static CStr = c"write"; +} + +static ONCE: Once<&'static LibcLogger> = Once::new(); +pub struct LibcLogger { + level: Level, + write: GuestAddr, +} + +impl LibcLogger { + pub fn initialize(level: Level) { + ONCE.call_once(|| { + let write = S::lookup_str(FunctionWrite::NAME).unwrap(); + let logger = Box::leak(Box::new(LibcLogger { level, write })); + log::set_logger(logger).unwrap(); + log::set_max_level(LevelFilter::Trace); + logger + }); + } +} + +impl Log for LibcLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.level >= metadata.level() + } + + fn flush(&self) {} + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let formatted = format!( + "{} [{}]: {}\n", + record.metadata().level(), + record.metadata().target(), + record.args() + ); + let buf = formatted.as_bytes(); + let fn_write = FunctionWrite::as_ptr(self.write).unwrap(); + unsafe { asan_swap(false) }; + unsafe { + fn_write( + STDERR_FILENO, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + ) + }; + unsafe { asan_swap(true) }; + } + } +} diff --git a/libafl_qemu/librasan/asan/src/logger/linux.rs b/libafl_qemu/librasan/asan/src/logger/linux.rs new file mode 100644 index 0000000000..b3e42ef030 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/logger/linux.rs @@ -0,0 +1,45 @@ +use alloc::{boxed::Box, format}; + +use log::{Level, LevelFilter, Log, Metadata, Record}; +use rustix::{io::write, stdio::stderr}; +use spin::Once; + +static ONCE: Once<&'static LinuxLogger> = Once::new(); + +pub struct LinuxLogger { + level: Level, +} + +impl LinuxLogger { + pub fn initialize(level: Level) { + ONCE.call_once(|| { + let logger = Box::leak(Box::new(LinuxLogger { level })); + log::set_logger(logger).unwrap(); + log::set_max_level(LevelFilter::Trace); + logger + }); + } +} + +impl Log for LinuxLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.level >= metadata.level() + } + + fn flush(&self) {} + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let formatted = format!( + "{} [{}]: {}\n", + record.metadata().level(), + record.metadata().target(), + record.args() + ); + let buf = formatted.as_bytes(); + #[allow(unused_unsafe)] + let fd = unsafe { stderr() }; + write(fd, buf).unwrap(); + } + } +} diff --git a/libafl_qemu/librasan/asan/src/logger/mod.rs b/libafl_qemu/librasan/asan/src/logger/mod.rs new file mode 100644 index 0000000000..735adbac1d --- /dev/null +++ b/libafl_qemu/librasan/asan/src/logger/mod.rs @@ -0,0 +1,26 @@ +//! # logger +//! This module provides an implementation of a logger which can be used to +//! provide logging information about the operation of the `asan` library +//! during execution. +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(feature = "linux")] +pub mod linux; + +use core::ffi::{CStr, c_char}; + +use log::trace; + +/// # Safety +/// `msg` must be a pointer to a zero-terminated string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log_trace(msg: *const c_char) { + if msg.is_null() { + return; + } + let c_str = unsafe { CStr::from_ptr(msg) }; + if let Ok(rust_str) = c_str.to_str() { + trace!("{}", rust_str); + } +} diff --git a/libafl_qemu/librasan/asan/src/maps/decode.rs b/libafl_qemu/librasan/asan/src/maps/decode.rs new file mode 100644 index 0000000000..fac6869558 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/decode.rs @@ -0,0 +1,34 @@ +use core::fmt::Debug; + +use thiserror::Error; + +pub trait MapDecode { + fn from_hex(c: u8) -> Result; + fn from_dec(c: u8) -> Result; +} + +impl> MapDecode for T { + fn from_hex(c: u8) -> Result { + let c = match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + b'A'..=b'F' => c - b'a' + 10, + _ => Err(MapDecodeError::InvalidCharacter(c))?, + }; + Ok(c.into()) + } + + fn from_dec(c: u8) -> Result { + let c = match c { + b'0'..=b'9' => c - b'0', + _ => Err(MapDecodeError::InvalidCharacter(c))?, + }; + Ok(c.into()) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum MapDecodeError { + #[error("Failed to convert: {0}")] + InvalidCharacter(u8), +} diff --git a/libafl_qemu/librasan/asan/src/maps/entry.rs b/libafl_qemu/librasan/asan/src/maps/entry.rs new file mode 100644 index 0000000000..0e4959ee8c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/entry.rs @@ -0,0 +1,137 @@ +use alloc::string::String; +use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, +}; + +use crate::{ + GuestAddr, + mmap::{Mmap, MmapProt}, +}; + +pub struct MapEntry { + base: GuestAddr, + limit: GuestAddr, + read: bool, + write: bool, + exec: bool, + private: bool, + offset: u64, + major: u32, + minor: u32, + inode: usize, + path: String, +} + +impl Debug for MapEntry { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!( + fmt, + "{:016x}-{:016x} {}{}{}{} {:08x} {:02x}:{:02x} {:10} {}", + self.base, + self.limit, + if self.read { 'r' } else { '-' }, + if self.write { 'w' } else { '-' }, + if self.exec { 'x' } else { '-' }, + match self.private { + true => 'p', + false => 's', + }, + self.offset, + self.major, + self.minor, + self.inode, + self.path, + )?; + Ok(()) + } +} + +impl MapEntry { + #[allow(clippy::too_many_arguments)] + pub fn new( + base: GuestAddr, + limit: GuestAddr, + read: bool, + write: bool, + exec: bool, + private: bool, + offset: u64, + major: u32, + minor: u32, + inode: usize, + path: String, + ) -> Self { + Self { + base, + limit, + read, + write, + exec, + private, + offset, + major, + minor, + inode, + path, + } + } + + pub fn contains(&self, addr: GuestAddr) -> bool { + addr >= self.base && addr < self.limit + } + + fn base(&self) -> GuestAddr { + self.base + } + + fn len(&self) -> usize { + self.limit - self.base + } + + pub fn prot(&self) -> MmapProt { + let mut prot = MmapProt::empty(); + if self.read { + prot |= MmapProt::READ; + } + if self.write { + prot |= MmapProt::WRITE; + } + if self.exec { + prot |= MmapProt::EXEC; + } + prot + } + + pub fn path(&self) -> &str { + &self.path + } + + pub fn writeable(&self) -> Result, M::Error> { + if !self.write { + M::protect(self.base(), self.len(), self.prot() | MmapProt::WRITE)?; + } + Ok(WriteableMapProtection { + map_entry: self, + _phantom: PhantomData, + }) + } +} + +pub struct WriteableMapProtection<'a, M: Mmap> { + map_entry: &'a MapEntry, + _phantom: PhantomData, +} + +impl Drop for WriteableMapProtection<'_, M> { + fn drop(&mut self) { + if !self.map_entry.write { + M::protect( + self.map_entry.base(), + self.map_entry.len(), + self.map_entry.prot(), + ) + .unwrap(); + } + } +} diff --git a/libafl_qemu/librasan/asan/src/maps/iterator.rs b/libafl_qemu/librasan/asan/src/maps/iterator.rs new file mode 100644 index 0000000000..e870bbb758 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/iterator.rs @@ -0,0 +1,145 @@ +use alloc::string::String; +use core::fmt::Debug; + +use crate::{ + GuestAddr, + maps::{MapReader, decode::MapDecode, entry::MapEntry}, +}; + +const BUFFER_SIZE: usize = 4096; + +#[derive(Debug)] +enum MapState { + Base, + Limit, + Properties, + Offset, + Major, + Minor, + Inode, + Path, +} + +#[derive(Debug)] +pub struct MapIterator { + source: R, + buffer: [u8; BUFFER_SIZE], + buff_len: usize, + state: MapState, +} + +impl MapIterator { + pub fn new(source: R) -> MapIterator { + MapIterator { + source, + buffer: [0; BUFFER_SIZE], + buff_len: 0, + state: MapState::Base, + } + } +} + +impl Iterator for MapIterator { + type Item = MapEntry; + + fn next(&mut self) -> Option { + let mut base: GuestAddr = 0; + let mut limit: GuestAddr = 0; + let mut read: bool = false; + let mut write: bool = false; + let mut exec: bool = false; + let mut private: bool = false; + let mut offset: u64 = 0; + let mut major: u32 = 0; + let mut minor: u32 = 0; + let mut inode: usize = 0; + let mut path: String = String::new(); + loop { + let slice = &mut self.buffer[self.buff_len..]; + + self.buff_len += self.source.read(slice).ok()?; + if self.buff_len == 0 { + break; + } + + for i in 0..self.buff_len { + let c = self.buffer[i]; + match self.state { + MapState::Base => match c { + b'-' => self.state = MapState::Limit, + _ => { + base *= 16; + base += GuestAddr::from_hex(c).ok()?; + } + }, + MapState::Limit => match c { + b' ' => self.state = MapState::Properties, + _ => { + limit *= 16; + limit += GuestAddr::from_hex(c).ok()?; + } + }, + MapState::Properties => match c { + b' ' => self.state = MapState::Offset, + b'-' => (), + b'r' => read = true, + b'w' => write = true, + b'x' => exec = true, + b'p' => private = true, + b's' => private = false, + _ => return None, + }, + MapState::Offset => match c { + b' ' => self.state = MapState::Major, + _ => { + offset *= 16; + offset += u64::from_hex(c).ok()?; + } + }, + MapState::Major => match c { + b':' => self.state = MapState::Minor, + _ => { + major *= 16; + major += u32::from_hex(c).ok()?; + } + }, + MapState::Minor => match c { + b' ' => self.state = MapState::Inode, + _ => { + minor *= 16; + minor += u32::from_hex(c).ok()?; + } + }, + MapState::Inode => match c { + b' ' => self.state = MapState::Path, + _ => { + inode *= 10; + inode += usize::from_dec(c).ok()?; + } + }, + MapState::Path => match c { + b' ' => { + if !path.is_empty() { + path.push(' '); + } + } + b'\n' => { + self.state = MapState::Base; + self.buff_len -= i + 1; + self.buffer.copy_within(i + 1.., 0); + let entry = MapEntry::new( + base, limit, read, write, exec, private, offset, major, minor, + inode, path, + ); + return Some(entry); + } + _ => { + path.push(c as char); + } + }, + } + } + } + None + } +} diff --git a/libafl_qemu/librasan/asan/src/maps/libc.rs b/libafl_qemu/librasan/asan/src/maps/libc.rs new file mode 100644 index 0000000000..ce5b6a9c5c --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/libc.rs @@ -0,0 +1,172 @@ +use core::{ + ffi::{CStr, c_char, c_int}, + marker::PhantomData, +}; + +use libc::{O_NONBLOCK, O_RDONLY}; +use log::trace; +use thiserror::Error; + +use crate::{ + asan_swap, + maps::MapReader, + size_t, ssize_t, + symbols::{ + AtomicGuestAddr, Function, FunctionPointer, FunctionPointerError, Symbols, SymbolsLookupStr, + }, +}; + +#[derive(Debug)] +struct FunctionOpen; + +impl Function for FunctionOpen { + type Func = unsafe extern "C" fn(*const c_char, c_int, c_int) -> c_int; + const NAME: &'static CStr = c"open"; +} + +#[derive(Debug)] +struct FunctionClose; + +impl Function for FunctionClose { + type Func = unsafe extern "C" fn(c_int) -> c_int; + const NAME: &'static CStr = c"close"; +} + +#[derive(Debug)] +struct FunctionRead; + +impl Function for FunctionRead { + type Func = unsafe extern "C" fn(c_int, *mut c_char, size_t) -> ssize_t; + const NAME: &'static CStr = c"read"; +} + +#[derive(Debug)] +struct FunctionErrnoLocation; + +impl Function for FunctionErrnoLocation { + type Func = unsafe extern "C" fn() -> *mut c_int; + const NAME: &'static CStr = c"errno_location"; +} + +static OPEN_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static CLOSE_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static READ_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static GET_ERRNO_LOCATION_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); + +#[derive(Debug)] +pub struct LibcMapReader { + fd: c_int, + _phantom: PhantomData, +} + +impl LibcMapReader { + fn get_open() -> Result<::Func, LibcMapReaderError> { + let addr = OPEN_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionOpen::NAME).map_err(|e| LibcMapReaderError::FailedToFindSymbol(e)) + })?; + let f = + FunctionOpen::as_ptr(addr).map_err(|e| LibcMapReaderError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_close() -> Result<::Func, LibcMapReaderError> { + let addr = CLOSE_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionClose::NAME) + .map_err(|e| LibcMapReaderError::FailedToFindSymbol(e)) + })?; + let f = + FunctionClose::as_ptr(addr).map_err(|e| LibcMapReaderError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_read() -> Result<::Func, LibcMapReaderError> { + let addr = READ_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionRead::NAME).map_err(|e| LibcMapReaderError::FailedToFindSymbol(e)) + })?; + let f = + FunctionRead::as_ptr(addr).map_err(|e| LibcMapReaderError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_errno_location() + -> Result<::Func, LibcMapReaderError> { + let addr = GET_ERRNO_LOCATION_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionErrnoLocation::NAME) + .map_err(|e| LibcMapReaderError::FailedToFindSymbol(e)) + })?; + let f = FunctionErrnoLocation::as_ptr(addr) + .map_err(|e| LibcMapReaderError::InvalidPointerType(e))?; + Ok(f) + } + + fn errno() -> Result> { + unsafe { asan_swap(false) }; + let errno_location = Self::get_errno_location()?; + unsafe { asan_swap(true) }; + let errno = unsafe { *errno_location() }; + Ok(errno) + } +} + +impl MapReader for LibcMapReader { + type Error = LibcMapReaderError; + + fn new() -> Result, LibcMapReaderError> { + let fn_open = Self::get_open()?; + unsafe { asan_swap(false) }; + let fd = unsafe { + fn_open( + c"/proc/self/maps".as_ptr() as *const c_char, + O_NONBLOCK | O_RDONLY, + 0, + ) + }; + unsafe { asan_swap(true) }; + if fd < 0 { + let errno = Self::errno().unwrap(); + return Err(LibcMapReaderError::FailedToOpen(errno)); + } + Ok(LibcMapReader { + fd, + _phantom: PhantomData, + }) + } + + fn read(&mut self, buf: &mut [u8]) -> Result { + let fn_read = Self::get_read()?; + unsafe { asan_swap(false) }; + let ret = unsafe { fn_read(self.fd, buf.as_mut_ptr() as *mut c_char, buf.len()) }; + unsafe { asan_swap(true) }; + if ret < 0 { + let errno = Self::errno().unwrap(); + return Err(LibcMapReaderError::FailedToRead(self.fd, errno)); + } + Ok(ret as usize) + } +} + +impl Drop for LibcMapReader { + fn drop(&mut self) { + let fn_close = Self::get_close().unwrap(); + unsafe { asan_swap(false) }; + let ret = unsafe { fn_close(self.fd) }; + unsafe { asan_swap(true) }; + if ret < 0 { + let errno = Self::errno().unwrap(); + panic!("Failed to close: {}, Errno: {}", self.fd, errno); + } + trace!("Closed fd: {}", self.fd); + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum LibcMapReaderError { + #[error("Failed to find mmap functions")] + FailedToFindSymbol(S::Error), + #[error("Invalid pointer type: {0:?}")] + InvalidPointerType(FunctionPointerError), + #[error("Failed to read - fd: {0}, errno: {1}")] + FailedToRead(c_int, c_int), + #[error("Failed to open - errno: {0}")] + FailedToOpen(c_int), +} diff --git a/libafl_qemu/librasan/asan/src/maps/linux.rs b/libafl_qemu/librasan/asan/src/maps/linux.rs new file mode 100644 index 0000000000..2a1fe1af99 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/linux.rs @@ -0,0 +1,39 @@ +use rustix::{ + fd::OwnedFd, + fs::{Mode, OFlags, open}, + io::{Errno, read}, +}; +use thiserror::Error; + +use crate::maps::MapReader; + +#[derive(Debug)] +pub struct LinuxMapReader { + fd: OwnedFd, +} + +impl MapReader for LinuxMapReader { + type Error = LinuxMapReaderError; + + fn new() -> Result { + let fd = open( + c"/proc/self/maps", + OFlags::RDONLY | OFlags::NONBLOCK, + Mode::empty(), + ) + .map_err(LinuxMapReaderError::FailedToOpen)?; + Ok(LinuxMapReader { fd }) + } + + fn read(&mut self, buf: &mut [u8]) -> Result { + read(&self.fd, buf).map_err(LinuxMapReaderError::FailedToRead) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum LinuxMapReaderError { + #[error("Failed to read - errno: {0}")] + FailedToRead(Errno), + #[error("Failed to open - errno: {0}")] + FailedToOpen(Errno), +} diff --git a/libafl_qemu/librasan/asan/src/maps/mod.rs b/libafl_qemu/librasan/asan/src/maps/mod.rs new file mode 100644 index 0000000000..fac62abde8 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/maps/mod.rs @@ -0,0 +1,18 @@ +use core::fmt::Debug; + +mod decode; +pub mod entry; + +pub mod iterator; + +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(feature = "linux")] +pub mod linux; + +pub trait MapReader: Sized { + type Error: Debug; + fn new() -> Result; + fn read(&mut self, buf: &mut [u8]) -> Result; +} diff --git a/libafl_qemu/librasan/asan/src/mem.rs b/libafl_qemu/librasan/asan/src/mem.rs new file mode 100644 index 0000000000..9fb053025f --- /dev/null +++ b/libafl_qemu/librasan/asan/src/mem.rs @@ -0,0 +1,109 @@ +use core::{ + cmp::Ordering, + slice::{from_raw_parts, from_raw_parts_mut}, +}; + +#[cfg(feature = "dlmalloc")] +use crate::allocator::backend::dlmalloc::DlmallocBackend; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +type Mmap = crate::mmap::linux::LinuxMmap; + +#[cfg(feature = "libc")] +type Mmap = crate::mmap::libc::LibcMmap< + crate::symbols::dlsym::DlSymSymbols, +>; + +const PAGE_SIZE: usize = 4096; + +#[global_allocator] +#[cfg(all(feature = "dlmalloc", not(feature = "mimalloc")))] +static GLOBAL_ALLOCATOR: DlmallocBackend = DlmallocBackend::new(PAGE_SIZE); + +#[global_allocator] +#[cfg(all(feature = "dlmalloc", feature = "mimalloc"))] +static GLOBAL_ALLOCATOR: baby_mimalloc::MimallocMutexWrapper> = + baby_mimalloc::MimallocMutexWrapper::with_os_allocator(DlmallocBackend::new(PAGE_SIZE)); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, count: usize) { + let src_slice = unsafe { from_raw_parts(src, count) }; + let dest_slice = unsafe { from_raw_parts_mut(dest, count) }; + + if src < dest { + #[allow(clippy::manual_memcpy)] + for i in 0..count { + let idx = count - 1 - i; + dest_slice[idx] = src_slice[idx]; + } + } else { + #[allow(clippy::manual_memcpy)] + for i in 0..count { + dest_slice[i] = src_slice[i]; + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, count: usize) { + let src_slice = unsafe { from_raw_parts(src, count) }; + let dest_slice = unsafe { from_raw_parts_mut(dest, count) }; + #[allow(clippy::manual_memcpy)] + for i in 0..count { + dest_slice[i] = src_slice[i]; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memset(dest: *mut u8, value: u8, count: usize) { + let dest_slice = unsafe { from_raw_parts_mut(dest, count) }; + #[allow(clippy::needless_range_loop)] + for i in 0..count { + dest_slice[i] = value; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memcmp(ptr1: *const u8, ptr2: *const u8, count: usize) -> i32 { + let slice1 = unsafe { from_raw_parts(ptr1, count) }; + let slice2 = unsafe { from_raw_parts(ptr2, count) }; + + for i in 0..count { + match slice1[i].cmp(&slice2[i]) { + Ordering::Equal => (), + Ordering::Less => return -1, + Ordering::Greater => return 1, + } + } + + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn bcmp(ptr1: *const u8, ptr2: *const u8, count: usize) -> i32 { + let slice1 = unsafe { from_raw_parts(ptr1, count) }; + let slice2 = unsafe { from_raw_parts(ptr2, count) }; + + for i in 0..count { + if slice1[i] != slice2[i] { + return 1; + } + } + + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strlen(s: *const u8) -> usize { + let mut i = 0; + let mut cursor = s; + + unsafe { + while *cursor != 0 { + cursor = cursor.offset(1); + i += 1; + } + } + + i +} diff --git a/libafl_qemu/librasan/asan/src/mmap/libc.rs b/libafl_qemu/librasan/asan/src/mmap/libc.rs new file mode 100644 index 0000000000..6b929bb985 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/mmap/libc.rs @@ -0,0 +1,313 @@ +//! # libc +//! This implementation of `Mmap` uses the `libc` crate and hence the standard +//! `libc` library for allocating pages. It should therefore support most +//! operating systems which provide a `libc` library. But is no suited to +//! applications where the library has been statically linked. +use core::{ + cmp::Ordering, + ffi::{CStr, c_int, c_void}, + marker::PhantomData, + ptr::null_mut, + slice::{from_raw_parts, from_raw_parts_mut}, +}; + +use libc::{ + MADV_DONTDUMP, MADV_HUGEPAGE, PROT_EXEC, PROT_NONE, PROT_READ, PROT_WRITE, off_t, size_t, +}; +use log::trace; +use thiserror::Error; + +use crate::{ + GuestAddr, asan_swap, + mmap::{Mmap, MmapProt}, + symbols::{ + AtomicGuestAddr, Function, FunctionPointer, FunctionPointerError, Symbols, SymbolsLookupStr, + }, +}; + +#[derive(Debug)] +struct FunctionMmap; + +impl Function for FunctionMmap { + type Func = + unsafe extern "C" fn(*mut c_void, size_t, c_int, c_int, c_int, off_t) -> *mut c_void; + const NAME: &'static CStr = c"mmap"; +} + +#[derive(Debug)] +struct FunctionMunmap; + +impl Function for FunctionMunmap { + type Func = unsafe extern "C" fn(*mut c_void, size_t) -> c_int; + const NAME: &'static CStr = c"munmap"; +} + +#[derive(Debug)] +struct FunctionMprotect; + +impl Function for FunctionMprotect { + type Func = unsafe extern "C" fn(*mut c_void, size_t, c_int) -> c_int; + const NAME: &'static CStr = c"mprotect"; +} + +#[derive(Debug)] +struct FunctionErrnoLocation; + +impl Function for FunctionErrnoLocation { + type Func = unsafe extern "C" fn() -> *mut c_int; + const NAME: &'static CStr = c"errno_location"; +} + +#[derive(Debug)] +struct FunctionMadvise; + +impl Function for FunctionMadvise { + type Func = unsafe extern "C" fn(*mut c_void, size_t, c_int) -> c_int; + const NAME: &'static CStr = c"madvise"; +} + +#[derive(Debug)] +pub struct LibcMmap { + addr: GuestAddr, + len: usize, + _phantom: PhantomData, +} + +impl Ord for LibcMmap { + fn cmp(&self, other: &Self) -> Ordering { + self.addr.cmp(&other.addr) + } +} + +impl PartialOrd for LibcMmap { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for LibcMmap { + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } +} + +impl Eq for LibcMmap {} + +static MMAP_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static MUNMAP_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static MPROTECT_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static GET_ERRNO_LOCATION_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); +static MADVISE_ADDR: AtomicGuestAddr = AtomicGuestAddr::new(); + +impl LibcMmap { + fn get_mmap() -> Result<::Func, LibcMapError> { + let addr = MMAP_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionMmap::NAME).map_err(|e| LibcMapError::FailedToFindSymbol(e)) + })?; + let f = FunctionMmap::as_ptr(addr).map_err(|e| LibcMapError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_munmap() -> Result<::Func, LibcMapError> { + let addr = MUNMAP_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionMunmap::NAME).map_err(|e| LibcMapError::FailedToFindSymbol(e)) + })?; + let f = FunctionMunmap::as_ptr(addr).map_err(|e| LibcMapError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_mprotect() -> Result<::Func, LibcMapError> { + let addr = MPROTECT_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionMprotect::NAME).map_err(|e| LibcMapError::FailedToFindSymbol(e)) + })?; + let f = FunctionMprotect::as_ptr(addr).map_err(|e| LibcMapError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_errno_location() -> Result<::Func, LibcMapError> { + let addr = GET_ERRNO_LOCATION_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionErrnoLocation::NAME) + .map_err(|e| LibcMapError::FailedToFindSymbol(e)) + })?; + let f = + FunctionErrnoLocation::as_ptr(addr).map_err(|e| LibcMapError::InvalidPointerType(e))?; + Ok(f) + } + + fn get_madvise() -> Result<::Func, LibcMapError> { + let addr = MADVISE_ADDR.try_get_or_insert_with(|| { + S::lookup_str(FunctionMadvise::NAME).map_err(|e| LibcMapError::FailedToFindSymbol(e)) + })?; + let f = FunctionMadvise::as_ptr(addr).map_err(|e| LibcMapError::InvalidPointerType(e))?; + Ok(f) + } + + fn errno() -> Result> { + unsafe { asan_swap(false) }; + let errno_location = Self::get_errno_location()?; + unsafe { asan_swap(true) }; + let errno = unsafe { *errno_location() }; + Ok(errno) + } +} + +impl Mmap for LibcMmap { + type Error = LibcMapError; + + fn map(len: usize) -> Result, LibcMapError> { + let fn_mmap = Self::get_mmap()?; + unsafe { asan_swap(false) }; + let map = unsafe { + fn_mmap( + null_mut(), + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE, + -1, + 0, + ) + }; + unsafe { asan_swap(true) }; + if map == libc::MAP_FAILED { + let errno = Self::errno()?; + Err(LibcMapError::FailedToMap(len, errno)) + } else { + let addr = map as GuestAddr; + Ok(LibcMmap { + addr, + len, + _phantom: PhantomData, + }) + } + } + + fn map_at(addr: GuestAddr, len: usize) -> Result, LibcMapError> { + let fn_mmap = Self::get_mmap()?; + unsafe { asan_swap(false) }; + let map = unsafe { + fn_mmap( + addr as *mut c_void, + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE + | libc::MAP_ANONYMOUS + | libc::MAP_NORESERVE + | libc::MAP_FIXED + | libc::MAP_FIXED_NOREPLACE, + -1, + 0, + ) + }; + unsafe { asan_swap(true) }; + trace!("Mapped: 0x{:x}-0x{:x}", addr, addr + len); + if map == libc::MAP_FAILED { + let errno = Self::errno()?; + Err(LibcMapError::FailedToMapAt(addr, len, errno)) + } else { + Ok(LibcMmap { + addr, + len, + _phantom: PhantomData, + }) + } + } + + fn protect(addr: GuestAddr, len: usize, prot: MmapProt) -> Result<(), Self::Error> { + trace!( + "protect - addr: {:#x}, len: {:#x}, prot: {:#x}", + addr, len, prot + ); + let fn_mprotect = Self::get_mprotect()?; + unsafe { asan_swap(false) }; + let ret = unsafe { fn_mprotect(addr as *mut c_void, len, c_int::from(&prot)) }; + unsafe { asan_swap(true) }; + if ret != 0 { + let errno = Self::errno()?; + Err(LibcMapError::FailedToMprotect(addr, len, prot, errno))?; + } + + Ok(()) + } + + fn as_slice(&self) -> &[u8] { + unsafe { from_raw_parts(self.addr as *const u8, self.len) } + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { from_raw_parts_mut(self.addr as *mut u8, self.len) } + } + + fn huge_pages(addr: GuestAddr, len: usize) -> Result<(), Self::Error> { + trace!("huge_pages - addr: {:#x}, len: {:#x}", addr, len); + let fn_madvise = Self::get_madvise()?; + unsafe { asan_swap(false) }; + let ret = unsafe { fn_madvise(addr as *mut c_void, len, MADV_HUGEPAGE) }; + unsafe { asan_swap(true) }; + if ret != 0 { + let errno = Self::errno()?; + Err(LibcMapError::FailedToMadviseHugePage(addr, len, errno))?; + } + Ok(()) + } + + fn dont_dump(addr: GuestAddr, len: usize) -> Result<(), Self::Error> { + trace!("dont_dump - addr: {:#x}, len: {:#x}", addr, len); + let fn_madvise = Self::get_madvise()?; + unsafe { asan_swap(false) }; + let ret = unsafe { fn_madvise(addr as *mut c_void, len, MADV_DONTDUMP) }; + unsafe { asan_swap(true) }; + if ret != 0 { + let errno = Self::errno()?; + Err(LibcMapError::FailedToMadviseDontDump(addr, len, errno))?; + } + Ok(()) + } +} + +impl From<&MmapProt> for c_int { + fn from(prot: &MmapProt) -> Self { + let mut ret = PROT_NONE; + if prot.contains(MmapProt::READ) { + ret |= PROT_READ; + } + if prot.contains(MmapProt::WRITE) { + ret |= PROT_WRITE; + } + if prot.contains(MmapProt::EXEC) { + ret |= PROT_EXEC; + } + ret + } +} + +impl Drop for LibcMmap { + fn drop(&mut self) { + let fn_munmap = Self::get_munmap().unwrap(); + unsafe { asan_swap(false) }; + let ret = unsafe { fn_munmap(self.addr as *mut c_void, self.len) }; + unsafe { asan_swap(true) }; + if ret < 0 { + let errno = Self::errno().unwrap(); + panic!("Errno: {}", errno); + } + trace!("Unmapped: 0x{:x}-0x{:x}", self.addr, self.addr + self.len); + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum LibcMapError { + #[error("Failed to map - len: {0}, errno: {1}")] + FailedToMap(usize, c_int), + #[error("Failed to map: {0}, len: {1}, errno: {2}")] + FailedToMapAt(GuestAddr, usize, c_int), + #[error("Failed to find mmap functions")] + FailedToFindSymbol(S::Error), + #[error("Failed to mprotect - addr: {0}, len: {1}, prot: {2:?}, errno: {3}")] + FailedToMprotect(GuestAddr, usize, MmapProt, c_int), + #[error("Invalid pointer type: {0:?}")] + InvalidPointerType(FunctionPointerError), + #[error("Failed to madvise HUGEPAGE - addr: {0}, len: {1}, errno: {2}")] + FailedToMadviseHugePage(GuestAddr, usize, c_int), + #[error("Failed to madvise DONTDUMP - addr: {0}, len: {1}, errno: {2}")] + FailedToMadviseDontDump(GuestAddr, usize, c_int), +} diff --git a/libafl_qemu/librasan/asan/src/mmap/linux.rs b/libafl_qemu/librasan/asan/src/mmap/linux.rs new file mode 100644 index 0000000000..60dd6e14e3 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/mmap/linux.rs @@ -0,0 +1,137 @@ +//! # linux +//! This implementation of `Mmap` uses the `rustix` crate to make direct +//! `syscalls` to allocate pages and therefore whilst Linux specific, does not +//! introduce a dependency on the `libc` library and is therefore suited for +//! targets where `libc` is statically linked. +use core::{ + ffi::c_void, + ptr::null_mut, + slice::{from_raw_parts, from_raw_parts_mut}, +}; + +use log::trace; +use rustix::{ + io::Errno, + mm::{Advice, MapFlags, MprotectFlags, ProtFlags, madvise, mmap_anonymous, mprotect, munmap}, +}; +use thiserror::Error; + +use crate::{ + GuestAddr, + mmap::{Mmap, MmapProt}, +}; + +#[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] +pub struct LinuxMmap { + addr: GuestAddr, + len: usize, +} + +impl Mmap for LinuxMmap { + type Error = LinuxMapError; + fn map(len: usize) -> Result { + unsafe { + let addr = mmap_anonymous( + null_mut(), + len, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::PRIVATE | MapFlags::NORESERVE, + ) + .map_err(|errno| LinuxMapError::FailedToMap(len, errno))? + as GuestAddr; + trace!("Mapped: 0x{:x}-0x{:x}", addr, addr + len); + Ok(Self { addr, len }) + } + } + + fn map_at(addr: GuestAddr, len: usize) -> Result { + unsafe { + mmap_anonymous( + addr as *mut c_void, + len, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::PRIVATE + | MapFlags::FIXED + | MapFlags::FIXED_NOREPLACE + | MapFlags::NORESERVE, + ) + .map_err(|errno| LinuxMapError::FailedToMapAt(addr, len, errno))?; + trace!("Mapped: 0x{:x}-0x{:x}", addr, addr + len); + }; + Ok(Self { addr, len }) + } + + fn as_slice(&self) -> &[u8] { + unsafe { from_raw_parts(self.addr as *const u8, self.len) } + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { from_raw_parts_mut(self.addr as *mut u8, self.len) } + } + + fn protect(addr: GuestAddr, len: usize, prot: MmapProt) -> Result<(), Self::Error> { + trace!( + "protect - addr: {:#x}, len: {:#x}, prot: {:#x}", + addr, len, prot + ); + unsafe { + mprotect(addr as *mut c_void, len, MprotectFlags::from(&prot)) + .map_err(|errno| LinuxMapError::FailedToMprotect(addr, len, prot, errno)) + } + } + + fn huge_pages(addr: GuestAddr, len: usize) -> Result<(), Self::Error> { + trace!("huge_pages - addr: {:#x}, len: {:#x}", addr, len); + unsafe { + madvise(addr as *mut c_void, len, Advice::LinuxHugepage) + .map_err(|errno| LinuxMapError::FailedToMadviseHugePage(addr, len, errno)) + } + } + + fn dont_dump(addr: GuestAddr, len: usize) -> Result<(), Self::Error> { + trace!("dont_dump - addr: {:#x}, len: {:#x}", addr, len); + unsafe { + madvise(addr as *mut c_void, len, Advice::LinuxDontDump) + .map_err(|errno| LinuxMapError::FailedToMadviseDontDump(addr, len, errno)) + } + } +} + +impl From<&MmapProt> for MprotectFlags { + fn from(prot: &MmapProt) -> Self { + let mut ret = MprotectFlags::empty(); + if prot.contains(MmapProt::READ) { + ret |= MprotectFlags::READ; + } + if prot.contains(MmapProt::WRITE) { + ret |= MprotectFlags::WRITE; + } + if prot.contains(MmapProt::EXEC) { + ret |= MprotectFlags::EXEC; + } + ret + } +} + +impl Drop for LinuxMmap { + fn drop(&mut self) { + unsafe { + munmap(self.addr as *mut c_void, self.len).unwrap(); + } + trace!("Unmapped: 0x{:x}-0x{:x}", self.addr, self.addr + self.len); + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum LinuxMapError { + #[error("Failed to map - len: {0}, errno: {1}")] + FailedToMap(usize, Errno), + #[error("Failed to map: {0}, len: {1}, errno: {2}")] + FailedToMapAt(GuestAddr, usize, Errno), + #[error("Failed to mprotect - addr: {0}, len: {1}, prot: {2:?}, errno: {3}")] + FailedToMprotect(GuestAddr, usize, MmapProt, Errno), + #[error("Failed to madvise HUGEPAGE - addr: {0}, len: {1}, errno: {2}")] + FailedToMadviseHugePage(GuestAddr, usize, Errno), + #[error("Failed to madvise DONTDUMP - addr: {0}, len: {1}, errno: {2}")] + FailedToMadviseDontDump(GuestAddr, usize, Errno), +} diff --git a/libafl_qemu/librasan/asan/src/mmap/mod.rs b/libafl_qemu/librasan/asan/src/mmap/mod.rs new file mode 100644 index 0000000000..3efbee1a5b --- /dev/null +++ b/libafl_qemu/librasan/asan/src/mmap/mod.rs @@ -0,0 +1,68 @@ +//! # mmap +//! This module provides implementations for creating memory mappings. This is +//! used by the guest shadow implementation and can also be used by allocator +//! backends. +use alloc::fmt::{self, Debug, Formatter}; + +use bitflags::bitflags; + +use crate::GuestAddr; + +#[cfg(feature = "libc")] +pub mod libc; + +#[cfg(feature = "linux")] +pub mod linux; + +bitflags! { + #[derive(PartialEq, Eq)] + pub struct MmapProt: u32 { + const READ = 0; + const WRITE = 1; + const EXEC = 2; + } +} + +impl Debug for MmapProt { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{}", + if self.contains(MmapProt::READ) { + "r" + } else { + "-" + } + )?; + write!( + f, + "{}", + if self.contains(MmapProt::WRITE) { + "w" + } else { + "-" + } + )?; + write!( + f, + "{}", + if self.contains(MmapProt::EXEC) { + "x" + } else { + "-" + } + )?; + Ok(()) + } +} + +pub trait Mmap: Sized + Ord + Debug + Send { + type Error: Debug; + fn map(len: usize) -> Result; + fn map_at(addr: GuestAddr, len: usize) -> Result; + fn protect(addr: GuestAddr, len: usize, prot: MmapProt) -> Result<(), Self::Error>; + fn huge_pages(addr: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn dont_dump(addr: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn as_slice(&self) -> &[u8]; + fn as_mut_slice(&mut self) -> &mut [u8]; +} diff --git a/libafl_qemu/librasan/asan/src/nostd/mod.rs b/libafl_qemu/librasan/asan/src/nostd/mod.rs new file mode 100644 index 0000000000..0049beef1e --- /dev/null +++ b/libafl_qemu/librasan/asan/src/nostd/mod.rs @@ -0,0 +1,20 @@ +//! # nostd +//! This module is used to support `no_std` environments. +use core::panic::PanicInfo; + +use log::error; + +use crate::exit::abort; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic!"); + error!("INFO: {}", info); + abort(); +} + +#[unsafe(no_mangle)] +extern "C" fn rust_eh_personality() { + error!("rust_eh_personality"); + abort(); +} diff --git a/libafl_qemu/librasan/asan/src/patch/hooks.rs b/libafl_qemu/librasan/asan/src/patch/hooks.rs new file mode 100644 index 0000000000..609f8bf935 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/patch/hooks.rs @@ -0,0 +1,85 @@ +use alloc::{collections::BTreeMap, fmt::Debug, vec::Vec}; +use core::ffi::{CStr, c_char}; + +use itertools::Itertools; +use log::{debug, trace}; +use spin::Mutex; +use thiserror::Error; + +use crate::{ + GuestAddr, + hooks::PatchedHook, + maps::{MapReader, entry::MapEntry, iterator::MapIterator}, + mmap::Mmap, + patch::Patch, + symbols::Symbols, +}; + +static PATCHED: Mutex>> = Mutex::new(None); + +pub struct PatchedHooks; + +impl PatchedHooks { + pub fn init() + -> Result<(), PatchesError> { + debug!("Installing patches"); + let reader = R::new().map_err(|e| PatchesError::MapReaderError(e))?; + let mappings = MapIterator::new(reader).collect::>(); + mappings.iter().for_each(|m| trace!("{m:?}")); + let patches = PatchedHook::all() + .into_iter() + .map(|p| Self::apply_patch(p, &mappings)) + .collect::, PatchesError>>()?; + PATCHED.lock().replace(patches); + debug!("Patching complete"); + Ok(()) + } + + fn apply_patch( + p: PatchedHook, + mappings: &[MapEntry], + ) -> Result<(GuestAddr, PatchedHook), PatchesError> { + trace!("patch: {:?}, destination: {:#x}", p.name, p.destination); + let target = S::lookup(p.name.as_ptr() as *const c_char) + .map_err(|e| PatchesError::SymbolsError(e))?; + trace!("patching: {:#x} -> {:#x}", target, p.destination); + let mapping = mappings + .iter() + .filter(|m| m.contains(target)) + .exactly_one() + .map_err(|_e| PatchesError::MapError(target))?; + let prot = mapping + .writeable::() + .map_err(|e| PatchesError::MmapError(e))?; + P::patch(target, p.destination).map_err(|e| PatchesError::PatchError(e))?; + drop(prot); + Ok((target, p)) + } + + pub fn check_patched(addr: GuestAddr) -> Result<(), PatchesCheckError> { + match PATCHED.lock().as_ref().and_then(|p| p.get(&addr)) { + Some(patch) => Err(PatchesCheckError::AddressPatchedError(addr, patch.name))?, + _ => Ok(()), + } + } +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum PatchesError { + #[error("Symbols error: {0:?}")] + SymbolsError(S::Error), + #[error("Patch error: {0:?}")] + PatchError(P::Error), + #[error("Map reader error: {0:?}")] + MapReaderError(R::Error), + #[error("Map error: {0:?}")] + MapError(GuestAddr), + #[error("Mmap error: {0:?}")] + MmapError(M::Error), +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum PatchesCheckError { + #[error("Address: {0} is patched for {1:?}")] + AddressPatchedError(GuestAddr, &'static CStr), +} diff --git a/libafl_qemu/librasan/asan/src/patch/mod.rs b/libafl_qemu/librasan/asan/src/patch/mod.rs new file mode 100644 index 0000000000..370d8c274f --- /dev/null +++ b/libafl_qemu/librasan/asan/src/patch/mod.rs @@ -0,0 +1,16 @@ +//! # patch +//! This module provides implementations patching function prologues in order +//! to re-direct execution to an alternative address. +use alloc::fmt::Debug; + +use crate::GuestAddr; + +#[cfg(feature = "hooks")] +pub mod hooks; + +pub mod raw; + +pub trait Patch: Debug { + type Error: Debug; + fn patch(target: GuestAddr, destination: GuestAddr) -> Result<(), Self::Error>; +} diff --git a/libafl_qemu/librasan/asan/src/patch/raw.rs b/libafl_qemu/librasan/asan/src/patch/raw.rs new file mode 100644 index 0000000000..8add18e2de --- /dev/null +++ b/libafl_qemu/librasan/asan/src/patch/raw.rs @@ -0,0 +1,126 @@ +//! # raw +//! This implementation of patching performs modification by means of writing +//! the bytes of raw instructions into the target address +use alloc::vec::Vec; +use core::slice::from_raw_parts_mut; + +use log::{debug, trace}; +use thiserror::Error; + +use crate::{GuestAddr, patch::Patch}; + +#[derive(Debug)] +pub struct RawPatch; + +impl Patch for RawPatch { + type Error = RawPatchError; + fn patch(target: GuestAddr, destination: GuestAddr) -> Result<(), Self::Error> { + debug!("patch - addr: {:#x}, target: {:#x}", target, destination); + if target == destination { + Err(RawPatchError::IdentityPatch(target))?; + } + let patch = Self::get_patch(destination)?; + trace!("patch: {:02x?}", patch); + let dest = unsafe { from_raw_parts_mut(target as *mut u8, patch.len()) }; + dest.copy_from_slice(&patch); + Ok(()) + } +} + +impl RawPatch { + #[cfg(target_arch = "x86_64")] + fn get_patch(destination: GuestAddr) -> Result, RawPatchError> { + // mov rax, 0xdeadfacef00dd00d + // jmp rax + let insns = [ + [0x48, 0xb8, 0x0d, 0xd0, 0x0d, 0xf0, 0xce, 0xfa, 0xad, 0xde].to_vec(), + [0xff, 0xe0].to_vec(), + ]; + let addr = destination.to_le_bytes(); + let insn0_mod = [ + insns[0][0], + insns[0][1], + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5], + addr[6], + addr[7], + ] + .to_vec(); + let insns_mod = [&insn0_mod, &insns[1]]; + Ok(insns_mod.into_iter().flatten().cloned().collect()) + } + + #[cfg(target_arch = "x86")] + fn get_patch(destination: GuestAddr) -> Result, RawPatchError> { + // mov eax, 0xdeadface + // jmp eax + let insns = [ + [0xb8, 0xce, 0xfa, 0xad, 0xde].to_vec(), + [0xff, 0xe0].to_vec(), + ]; + let addr = destination.to_le_bytes(); + let insn0_mod = [insns[0][0], addr[0], addr[1], addr[2], addr[3]].to_vec(); + let insns_mod = [&insn0_mod, &insns[1]]; + Ok(insns_mod.into_iter().flatten().cloned().collect()) + } + + #[cfg(target_arch = "arm")] + fn get_patch(destination: GuestAddr) -> Result, RawPatchError> { + // ldr ip, [pc] + // mov pc, ip + // .long 0xdeadface + let insns = [ + [0x00, 0xc0, 0x9f, 0xe5].to_vec(), + [0x0c, 0xf0, 0xa0, 0xe1].to_vec(), + [0xce, 0xfa, 0xad, 0xde].to_vec(), + ]; + let addr = destination.to_ne_bytes().to_vec(); + let insns_mod = [&insns[0], &insns[1], &addr]; + Ok(insns_mod.into_iter().flatten().cloned().collect()) + } + + #[cfg(target_arch = "aarch64")] + fn get_patch(destination: GuestAddr) -> Result, RawPatchError> { + // ldr x16, #8 + // br x16 + // .quad 0xdeadfacef00dd00d + let insns = [ + [0x50, 0x00, 0x00, 0x58].to_vec(), + [0x00, 0x02, 0x1f, 0xd6].to_vec(), + [0x0d, 0xd0, 0x0d, 0xf0].to_vec(), + [0xce, 0xfa, 0xad, 0xde].to_vec(), + ]; + let addr = destination.to_ne_bytes().to_vec(); + let insns_mod = [&insns[0], &insns[1], &addr]; + Ok(insns_mod.into_iter().flatten().cloned().collect()) + } + + #[cfg(target_arch = "powerpc")] + fn get_patch(destination: GuestAddr) -> Result, RawPatchError> { + // lis 12, 0xdead + // ori 12, 12, 0xface + // mtctr 12 + // bctr + let insns = [ + [0x3d, 0x80, 0xde, 0xad].to_vec(), + [0x61, 0x8c, 0xfa, 0xce].to_vec(), + [0x7d, 0x89, 0x03, 0xa6].to_vec(), + [0x4e, 0x80, 0x04, 0x20].to_vec(), + ]; + let addr = destination.to_be_bytes().to_vec(); + let insn0_mod = [insns[0][0], insns[0][1], addr[0], addr[1]].to_vec(); + let insn1_mod = [insns[1][0], insns[1][1], addr[2], addr[3]].to_vec(); + let insns_mod = [&insn0_mod, &insn1_mod, &insns[2], &insns[3]]; + Ok(insns_mod.into_iter().flatten().cloned().collect()) + } +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum RawPatchError { + #[error("Target and destination are the same: {0}")] + IdentityPatch(GuestAddr), +} diff --git a/libafl_qemu/librasan/asan/src/shadow/guest.rs b/libafl_qemu/librasan/asan/src/shadow/guest.rs new file mode 100644 index 0000000000..c28fe15c79 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/shadow/guest.rs @@ -0,0 +1,482 @@ +//! # guest +//! Performs memory tracking by allocating the high and low shadow regions and +//! mapping them into the guest memory space. All of the operations of this +//! module are performed by reading or writing these shadow regions. +use alloc::fmt::Debug; +use core::marker::PhantomData; + +use log::{debug, trace}; +use thiserror::Error; + +use crate::{ + GuestAddr, + mmap::Mmap, + shadow::{PoisonType, Shadow}, +}; + +#[allow(dead_code)] +#[derive(Debug)] +pub struct GuestShadow { + lo: M, + hi: M, + _phantom: PhantomData, +} + +impl Shadow for GuestShadow { + type Error = GuestShadowError; + + fn load(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("load - start: 0x{:x}, len: 0x{:x}", start, len); + if self.is_poison(start, len)? { + Err(GuestShadowError::Poisoned(start, len)) + } else { + Ok(()) + } + } + + fn store(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("store - start: 0x{:x}, len: 0x{:x}", start, len); + if self.is_poison(start, len)? { + Err(GuestShadowError::Poisoned(start, len)) + } else { + Ok(()) + } + } + + fn poison( + &mut self, + start: GuestAddr, + len: usize, + poison: PoisonType, + ) -> Result<(), Self::Error> { + debug!( + "poison - start: 0x{:x}, len: 0x{:x}, pioson: {:?}", + start, len, poison + ); + + if Self::is_out_of_bounds(start, len) { + Err(GuestShadowError::AddressRangeOverflow(start, len))?; + } + + if !Self::is_memory(start, len) { + Err(GuestShadowError::InvalidMemoryAddress(start))?; + } + + if !Self::is_end_aligned(start, len) { + Err(GuestShadowError::UnalignedEndAddress(start, len))?; + } + + if len == 0 { + return Ok(()); + } + + let mut remaining_len = len; + + /* First poison any odd bytes from the unaligned start of the region */ + if !Self::is_start_aligned(start) { + let first_unpoisoned = Self::remainder(start); + let poisoned = Self::ALLOC_ALIGN_SIZE - first_unpoisoned; + remaining_len -= poisoned; + + let start_aligned_down = Self::align_down(start); + let first_shadow = self.get_shadow_mut(start_aligned_down, Self::ALLOC_ALIGN_SIZE)?; + first_shadow[0] = first_unpoisoned as u8; + } + + /* If our range is expressed within the first byte, then we are done here */ + if remaining_len == 0 { + return Ok(()); + } + + /* Now poison the rest of the range, then end is aligned */ + let start_aligned_up = Self::align_up(start); + let shadow_map = self.get_shadow_mut(start_aligned_up, remaining_len)?; + shadow_map.iter_mut().for_each(|v| *v = poison as u8); + + Ok(()) + } + + fn unpoison(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("unpoison - start: 0x{:x}, len: 0x{:x}", start, len); + + if Self::is_out_of_bounds(start, len) { + Err(GuestShadowError::AddressRangeOverflow(start, len))?; + } + + if !Self::is_memory(start, len) { + Err(GuestShadowError::InvalidMemoryAddress(start))?; + } + + if !Self::is_start_aligned(start) { + Err(GuestShadowError::UnalignedStartAddress(start, len))?; + } + + if len == 0 { + return Ok(()); + } + + let mut remaining_len = len; + + let aligned_len = Self::align_down(len); + + /* Handle the unaligned end of the region */ + if !Self::is_end_aligned(start, len) { + let end_aligned = start + aligned_len; + let last_unpoisoned = Self::remainder(len); + remaining_len -= last_unpoisoned; + + let last_shadow = self.get_shadow_mut(end_aligned, Self::ALLOC_ALIGN_SIZE)?; + last_shadow[0] = last_unpoisoned as u8; + } + + /* If our region is just the unaligned end, then we are done here */ + if remaining_len == 0 { + return Ok(()); + } + + /* + * Next unpoison the aligned portion of our allocation (the start + * is aligned, but the end is not) + */ + let shadow_map = self.get_shadow_mut(start, aligned_len)?; + shadow_map + .iter_mut() + .for_each(|v| *v = PoisonType::AsanValid as u8); + + Ok(()) + } + + fn is_poison(&self, start: GuestAddr, len: usize) -> Result { + debug!("is_poison - start: 0x{:x}, len: 0x{:x}", start, len); + + if Self::is_out_of_bounds(start, len) { + Err(GuestShadowError::AddressRangeOverflow(start, len))?; + } + + if !Self::is_memory(start, len) { + Err(GuestShadowError::InvalidMemoryAddress(start))?; + } + + if len == 0 { + return Ok(false); + } + + let mut remaining_len = len; + + /* If our start is unaligned */ + if !Self::is_start_aligned(start) { + /* How many bytes represented by the first shadow byte should be ignored */ + let skipped = Self::remainder(start); + + /* How many bytes from our region are represented by the first shadow byte */ + let first_len = Self::ALLOC_ALIGN_SIZE - skipped; + + let k_start = self.get_shadow(Self::align_down(start), Self::ALLOC_ALIGN_SIZE)?; + let first_k = k_start[0] as i8; + + /* If our buffer ends within the first shadow byte */ + if len <= first_len { + /* + * The length we must test is the length we have skipped (due to the + * unaligned start) plus the length of our buffer + */ + let test_len = (len + skipped) as i8; + if first_k != 0 && test_len > first_k { + trace!( + "is_poison #1 - start: 0x{:x}, len: 0x{:x}, first_k: 0x{:x}, first_len: 0x{:x}", + start, len, first_k, test_len + ); + return Ok(true); + } else { + trace!( + "!is_poison #1 - start: 0x{:x}, len: 0x{:x}, first_k: 0x{:x}, first_len: 0x{:x}", + start, len, first_k, test_len + ); + return Ok(false); + } + } + + remaining_len -= first_len; + + /* + * If our buffer extends beyond the first shadow byte, then it must be + * zero + */ + if first_k != 0 { + trace!( + "is_poison #2 - start: 0x{:x}, len: 0x{:x}, first_k: 0x{:x}", + start, len, first_k + ); + return Ok(true); + } + } + + /* If our end is unaligned */ + if !Self::is_end_aligned(start, len) { + let last_len = Self::end_remainder(start, len); + remaining_len -= last_len; + + let k_end = self.get_shadow(Self::align_down(start + len), Self::ALLOC_ALIGN_SIZE)?; + + let last_k = k_end[0] as i8; + if last_k != 0 && last_len as i8 > last_k { + trace!( + "is_poison #3 - start: 0x{:x}, len: 0x{:x}, last_k: 0x{:x}, last_len: 0x{:x}", + start, len, last_k, last_len + ); + return Ok(true); + } + } + + /* + * If we have accounted for the length of our region only using the unaligned start and end + */ + if remaining_len == 0 { + return Ok(false); + } + + let start_aligned = Self::align_up(start); + + let shadow_map = self.get_shadow(start_aligned, remaining_len)?; + + let poisoned = shadow_map.iter().any(|v| *v != PoisonType::AsanValid as u8); + + if poisoned { + trace!( + "is_poison #4 - start_aligned: 0x{:x}, remaining_len: 0x{:x}", + start_aligned, remaining_len + ); + Ok(true) + } else { + trace!( + "!is_poison #4 - start_aligned: 0x{:x}, remaining_len: 0x{:x}", + start_aligned, remaining_len + ); + Ok(false) + } + } +} + +impl GuestShadow { + pub const SHADOW_OFFSET: usize = L::SHADOW_OFFSET; + pub const LOW_MEM_OFFSET: GuestAddr = L::LOW_MEM_OFFSET; + pub const LOW_MEM_SIZE: usize = L::LOW_MEM_SIZE; + pub const LOW_SHADOW_OFFSET: GuestAddr = L::LOW_SHADOW_OFFSET; + pub const LOW_SHADOW_SIZE: usize = L::LOW_SHADOW_SIZE; + pub const HIGH_SHADOW_OFFSET: GuestAddr = L::HIGH_SHADOW_OFFSET; + pub const HIGH_SHADOW_SIZE: usize = L::HIGH_SHADOW_SIZE; + pub const HIGH_MEM_OFFSET: GuestAddr = L::HIGH_MEM_OFFSET; + pub const HIGH_MEM_SIZE: usize = L::HIGH_MEM_SIZE; + + pub const ALLOC_ALIGN_POW: usize = L::ALLOC_ALIGN_POW; + pub const ALLOC_ALIGN_SIZE: usize = L::ALLOC_ALIGN_SIZE; + + pub const LOW_MEM_LIMIT: usize = L::LOW_MEM_OFFSET + (L::LOW_MEM_SIZE - 1); + pub const LOW_SHADOW_LIMIT: usize = L::LOW_SHADOW_OFFSET + (L::LOW_SHADOW_SIZE - 1); + pub const HIGH_SHADOW_LIMIT: usize = L::HIGH_SHADOW_OFFSET + (L::HIGH_SHADOW_SIZE - 1); + pub const HIGH_MEM_LIMIT: usize = L::HIGH_MEM_OFFSET + (L::HIGH_MEM_SIZE - 1); + + pub fn new() -> Result, GuestShadowError> { + trace!( + "Mapping low shadow: 0x{:x}-0x{:x}", + Self::LOW_SHADOW_OFFSET, + Self::LOW_SHADOW_OFFSET + Self::LOW_SHADOW_SIZE + ); + let lo = Self::map_shadow(Self::LOW_SHADOW_OFFSET, Self::LOW_SHADOW_SIZE) + .map_err(|e| GuestShadowError::MmapError(e))?; + trace!( + "Mapping high shadow: 0x{:x}-0x{:x}", + Self::HIGH_SHADOW_OFFSET, + Self::HIGH_SHADOW_OFFSET + Self::HIGH_SHADOW_SIZE + ); + let hi = Self::map_shadow(Self::HIGH_SHADOW_OFFSET, Self::HIGH_SHADOW_SIZE) + .map_err(|e| GuestShadowError::MmapError(e))?; + Ok(GuestShadow { + lo, + hi, + _phantom: PhantomData, + }) + } + + fn map_shadow(addr: GuestAddr, size: usize) -> Result { + let m = M::map_at(addr, size)?; + M::huge_pages(addr, size)?; + M::dont_dump(addr, size)?; + Ok(m) + } + + pub fn align_down(addr: GuestAddr) -> GuestAddr { + addr & !(Self::ALLOC_ALIGN_SIZE - 1) + } + + pub fn align_up(addr: GuestAddr) -> GuestAddr { + assert!(addr <= GuestAddr::MAX - (Self::ALLOC_ALIGN_SIZE - 1)); + let val = addr + (Self::ALLOC_ALIGN_SIZE - 1); + val & !(Self::ALLOC_ALIGN_SIZE - 1) + } + + pub fn remainder(addr: GuestAddr) -> usize { + addr & (Self::ALLOC_ALIGN_SIZE - 1) + } + + pub fn is_out_of_bounds(addr: GuestAddr, len: usize) -> bool { + if len == 0 { + false + } else { + GuestAddr::MAX - len + 1 < addr + } + } + + pub fn is_start_aligned(addr: GuestAddr) -> bool { + let remainder = Self::remainder(addr); + remainder == 0 + } + + pub fn is_end_aligned(addr: GuestAddr, len: usize) -> bool { + Self::end_remainder(addr, len) == 0 + } + + pub fn end_remainder(addr: GuestAddr, len: usize) -> usize { + let start_remainder = Self::remainder(addr); + let end_remainder = Self::remainder(len); + Self::remainder(start_remainder + end_remainder) + } + + pub fn is_memory(addr: GuestAddr, len: usize) -> bool { + Self::is_low_memory(addr, len) || Self::is_high_memory(addr, len) + } + + pub fn is_low_memory(addr: GuestAddr, len: usize) -> bool { + if !(Self::LOW_MEM_OFFSET..=Self::LOW_MEM_LIMIT).contains(&addr) { + false + } else { + len <= Self::LOW_MEM_LIMIT - addr + 1 + } + } + + pub fn is_high_memory(addr: GuestAddr, len: usize) -> bool { + if !(Self::HIGH_MEM_OFFSET..=Self::HIGH_MEM_LIMIT).contains(&addr) { + false + } else { + len <= Self::HIGH_MEM_LIMIT - addr + 1 + } + } + + pub fn get_shadow(&self, addr: GuestAddr, len: usize) -> Result<&[u8], GuestShadowError> { + trace!("get_shadow - addr: 0x{:x}, len: 0x{:x}", addr, len); + assert!(addr % Self::ALLOC_ALIGN_SIZE == 0); + assert!(len % Self::ALLOC_ALIGN_SIZE == 0); + let shadow_addr = (addr >> Self::ALLOC_ALIGN_POW) + Self::SHADOW_OFFSET; + let shadow_len = len >> Self::ALLOC_ALIGN_POW; + if Self::is_low_memory(addr, len) { + let offset = shadow_addr - Self::LOW_SHADOW_OFFSET; + Ok(&self.lo.as_slice()[offset..(offset + shadow_len)]) + } else if Self::is_high_memory(addr, len) { + let offset = shadow_addr - Self::HIGH_SHADOW_OFFSET; + Ok(&self.hi.as_slice()[offset..(offset + shadow_len)]) + } else { + Err(GuestShadowError::InvalidMemoryAddress(addr)) + } + } + + pub fn get_shadow_mut( + &mut self, + addr: GuestAddr, + len: usize, + ) -> Result<&mut [u8], GuestShadowError> { + trace!("get_shadow_mut - addr: 0x{:x}, len: 0x{:x}", addr, len); + assert!(addr % Self::ALLOC_ALIGN_SIZE == 0); + assert!(len % Self::ALLOC_ALIGN_SIZE == 0); + let shadow_addr = (addr >> Self::ALLOC_ALIGN_POW) + Self::SHADOW_OFFSET; + let aligned_len = Self::align_up(len); + let shadow_len = aligned_len >> Self::ALLOC_ALIGN_POW; + if Self::is_low_memory(addr, len) { + let offset = shadow_addr - Self::LOW_SHADOW_OFFSET; + Ok(&mut self.lo.as_mut_slice()[offset..(offset + shadow_len)]) + } else if Self::is_high_memory(addr, len) { + let offset = shadow_addr - Self::HIGH_SHADOW_OFFSET; + Ok(&mut self.hi.as_mut_slice()[offset..(offset + shadow_len)]) + } else { + Err(GuestShadowError::InvalidMemoryAddress(addr)) + } + } +} + +pub trait ShadowLayout: Debug + Send { + const LOW_MEM_OFFSET: usize; + const LOW_MEM_SIZE: usize; + + const LOW_SHADOW_OFFSET: usize; + const LOW_SHADOW_SIZE: usize; + + const HIGH_SHADOW_OFFSET: usize; + const HIGH_SHADOW_SIZE: usize; + + const HIGH_MEM_OFFSET: usize; + const HIGH_MEM_SIZE: usize; + + const SHADOW_OFFSET: usize; + const ALLOC_ALIGN_POW: usize; + const ALLOC_ALIGN_SIZE: usize; +} + +#[derive(Debug)] +pub struct DefaultShadowLayout; + +#[cfg(target_pointer_width = "32")] +impl ShadowLayout for DefaultShadowLayout { + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + const SHADOW_OFFSET: usize = 0x20000000; + const LOW_MEM_OFFSET: GuestAddr = 0x0; + const LOW_MEM_SIZE: usize = 0x20000000; + const LOW_SHADOW_OFFSET: GuestAddr = 0x20000000; + const LOW_SHADOW_SIZE: usize = 0x4000000; + const HIGH_SHADOW_OFFSET: GuestAddr = 0x28000000; + const HIGH_SHADOW_SIZE: usize = 0x18000000; + const HIGH_MEM_OFFSET: GuestAddr = 0x40000000; + const HIGH_MEM_SIZE: usize = 0xc0000000; + + const ALLOC_ALIGN_POW: usize = 3; + const ALLOC_ALIGN_SIZE: usize = 1 << Self::ALLOC_ALIGN_POW; +} + +#[cfg(target_pointer_width = "64")] +impl ShadowLayout for DefaultShadowLayout { + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + const SHADOW_OFFSET: usize = 0x7fff8000; + const LOW_MEM_OFFSET: GuestAddr = 0x0; + const LOW_MEM_SIZE: usize = 0x00007fff8000; + const LOW_SHADOW_OFFSET: GuestAddr = 0x00007fff8000; + const LOW_SHADOW_SIZE: usize = 0xffff000; + const HIGH_SHADOW_OFFSET: GuestAddr = 0x02008fff7000; + const HIGH_SHADOW_SIZE: usize = 0xdfff0001000; + const HIGH_MEM_OFFSET: GuestAddr = 0x10007fff8000; + const HIGH_MEM_SIZE: usize = 0x6fff80008000; + + const ALLOC_ALIGN_POW: usize = 3; + const ALLOC_ALIGN_SIZE: usize = 1 << Self::ALLOC_ALIGN_POW; +} + +#[derive(Error, Debug, PartialEq)] +pub enum GuestShadowError { + #[error("Invalid shadow address: {0:x}")] + InvalidMemoryAddress(GuestAddr), + #[error("End address not aligned: {0:x}-{1:x}")] + UnalignedEndAddress(GuestAddr, usize), + #[error("Start address not aligned: {0:x}-{1:x}")] + UnalignedStartAddress(GuestAddr, GuestAddr), + #[error("Address overflow: {0:x}, len: {1:x}")] + AddressRangeOverflow(GuestAddr, usize), + #[error("Poisoned: {0:x}, len: {1:x}")] + Poisoned(GuestAddr, usize), + #[error("Mmap error: {0:?}")] + MmapError(M::Error), +} diff --git a/libafl_qemu/librasan/asan/src/shadow/host.rs b/libafl_qemu/librasan/asan/src/shadow/host.rs new file mode 100644 index 0000000000..509595f586 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/shadow/host.rs @@ -0,0 +1,66 @@ +//! # host +//! This implementation of the shadow map makes use of a `Host` implementation +//! in order to relay the requested shadow map queries or updates to the host +//! emulator. In the case of QEMU on Linux, this will typically be by means of a +//! bespoke `syscall`. +use core::marker::PhantomData; + +use log::debug; +use thiserror::Error; + +use crate::{ + GuestAddr, + host::Host, + shadow::{PoisonType, Shadow}, +}; + +#[derive(Debug)] +pub struct HostShadow { + _phantom: PhantomData, +} + +impl Shadow for HostShadow { + type Error = HostShadowError; + + fn load(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("load - start: 0x{:x}, len: 0x{:x}", start, len); + H::load(start, len).map_err(|e| HostShadowError::HostError(e)) + } + + fn store(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("store - start: 0x{:x}, len: 0x{:x}", start, len); + H::store(start, len).map_err(|e| HostShadowError::HostError(e)) + } + + fn poison(&mut self, start: GuestAddr, len: usize, val: PoisonType) -> Result<(), Self::Error> { + debug!( + "poison - start: 0x{:x}, len: 0x{:x}, pioson: {:?}", + start, len, val + ); + H::poison(start, len, val).map_err(|e| HostShadowError::HostError(e)) + } + + fn unpoison(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("unpoison - start: 0x{:x}, len: 0x{:x}", start, len); + H::unpoison(start, len).map_err(|e| HostShadowError::HostError(e)) + } + + fn is_poison(&self, start: GuestAddr, len: usize) -> Result { + debug!("is_poison - start: 0x{:x}, len: 0x{:x}", start, len); + H::is_poison(start, len).map_err(|e| HostShadowError::HostError(e)) + } +} + +impl HostShadow { + pub fn new() -> Result, HostShadowError> { + Ok(HostShadow:: { + _phantom: PhantomData, + }) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum HostShadowError { + #[error("Host error: {0:?}")] + HostError(H::Error), +} diff --git a/libafl_qemu/librasan/asan/src/shadow/mod.rs b/libafl_qemu/librasan/asan/src/shadow/mod.rs new file mode 100644 index 0000000000..d2185078ed --- /dev/null +++ b/libafl_qemu/librasan/asan/src/shadow/mod.rs @@ -0,0 +1,57 @@ +//! # shadow +//! This module provides implementations for tracking memory by means of a +//! shadow map. QEMU currently supports two modes of operation for this: +//! - `guest` - This newer mode (made possible by more recent versions of QEMU +//! supporting the `MAP_NORESERVE` flag to mmap much more efficiently) creates +//! the shadow maps in the guest memory space and augments the TCG code emit +//! instructions to test these maps when performing load/store operations. +//! - `host` - This is the original mode whereby the shadow maps are created by +//! QEMU itself (and hence live in the host's memory space). In this mode, the +//! TCG code is augmented with calls back into the host in order to validate the +//! memory being accessed during load/store operations. Note that this requires +//! the guest memory addresses being used by the TCG code to be converted into +//! host addresses to be tested against the shadow maps (incurring a performance +//! overhead) as well as placing constraints on register usage. +use core::fmt::Debug; + +use crate::GuestAddr; + +#[cfg(feature = "guest")] +pub mod guest; +#[cfg(feature = "host")] +pub mod host; + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum PoisonType { + AsanValid = 0x00, + AsanPartial1 = 0x01, + AsanPartial2 = 0x02, + AsanPartial3 = 0x03, + AsanPartial4 = 0x04, + AsanPartial5 = 0x05, + AsanPartial6 = 0x06, + AsanPartial7 = 0x07, + AsanArrayCookie = 0xac, + AsanStackRz = 0xf0, + AsanStackLeftRz = 0xf1, + AsanStackMidRz = 0xf2, + AsanStackRightRz = 0xf3, + AsanStackFreed = 0xf5, + AsanStackOoscope = 0xf8, + AsanGlobalRz = 0xf9, + AsanHeapRz = 0xe9, + AsanUser = 0xf7, + AsanHeapLeftRz = 0xfa, + AsanHeapRightRz = 0xfb, + AsanHeapFreed = 0xfd, +} + +pub trait Shadow: Sized + Debug + Send { + type Error: Debug; + fn load(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn store(&self, start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn poison(&mut self, start: GuestAddr, len: usize, val: PoisonType) -> Result<(), Self::Error>; + fn unpoison(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn is_poison(&self, start: GuestAddr, len: usize) -> Result; +} diff --git a/libafl_qemu/librasan/asan/src/symbols/dlsym.rs b/libafl_qemu/librasan/asan/src/symbols/dlsym.rs new file mode 100644 index 0000000000..8103a6911b --- /dev/null +++ b/libafl_qemu/librasan/asan/src/symbols/dlsym.rs @@ -0,0 +1,97 @@ +//! # dlsym +//! This module performs symbol lookup using the `dlsym` function. It supports +//! two variants: +//! - LookupTypeDefault: This performs the lookup using +//! `dlsym(RTLD_DEFAULT, name)` +//! - LookupTypeNext: This performs the lookup using +//! `dlsym(RTLD_NEXT, name)` +use alloc::{ffi::NulError, fmt::Debug}; +use core::{ + ffi::{CStr, c_char, c_void}, + marker::PhantomData, +}; + +use libc::{RTLD_DEFAULT, RTLD_NEXT, dlerror, dlsym}; +use thiserror::Error; + +use crate::{GuestAddr, symbols::Symbols}; + +pub trait LookupType: Debug + Send { + const HANDLE: *mut c_void; +} + +#[derive(Debug)] +pub struct LookupTypeDefault; +impl LookupType for LookupTypeDefault { + const HANDLE: *mut c_void = RTLD_DEFAULT; +} + +#[derive(Debug, Eq, PartialEq)] +pub struct LookupTypeNext; +impl LookupType for LookupTypeNext { + const HANDLE: *mut c_void = RTLD_NEXT; +} + +#[derive(Debug, Eq, PartialEq)] +pub struct DlSymSymbols { + _phantom: PhantomData, +} + +impl Symbols for DlSymSymbols { + type Error = DlSymSymbolsError; + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn lookup(name: *const c_char) -> Result { + if name.is_null() { + Err(DlSymSymbolsError::NullName())?; + } + let p_sym = unsafe { dlsym(L::HANDLE, name) }; + if p_sym.is_null() { + Err(DlSymSymbolsError::FailedToFindFunction( + name, + Self::get_error(), + )) + } else { + Ok(p_sym as GuestAddr) + } + } +} + +impl Default for DlSymSymbols { + fn default() -> Self { + Self::new() + } +} + +impl DlSymSymbols { + const UNKNOWN_ERROR: &str = "Unknown error"; + + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } + + fn get_error() -> &'static str { + let error = unsafe { dlerror() }; + if error.is_null() { + Self::UNKNOWN_ERROR + } else { + unsafe { + CStr::from_ptr(error) + .to_str() + .unwrap_or(Self::UNKNOWN_ERROR) + } + } + } +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum DlSymSymbolsError { + #[error("Bad function name: {0:?}")] + BadFunctionName(#[from] NulError), + #[error("Failed to find function: {0:p}, error: {1}")] + FailedToFindFunction(*const c_char, &'static str), + #[error("Null name")] + NullName(), +} diff --git a/libafl_qemu/librasan/asan/src/symbols/mod.rs b/libafl_qemu/librasan/asan/src/symbols/mod.rs new file mode 100644 index 0000000000..5966b720de --- /dev/null +++ b/libafl_qemu/librasan/asan/src/symbols/mod.rs @@ -0,0 +1,128 @@ +//! # symbols +//! This module provides implementations symbol lookups. The ability to +//! substitute this functionality may be helpful for targets where +//! conventional symbol lookup is not possible, e.g. if libc is statically +//! linked +use alloc::fmt::Debug; +use core::{ + ffi::{CStr, c_char, c_void}, + ptr::null_mut, + sync::atomic::{AtomicPtr, Ordering}, +}; + +use thiserror::Error; + +use crate::GuestAddr; +#[cfg(feature = "hooks")] +use crate::patch::hooks::{PatchedHooks, PatchesCheckError}; + +#[cfg(feature = "libc")] +pub mod dlsym; + +pub mod nop; + +pub struct AtomicGuestAddr { + addr: AtomicPtr, +} + +impl AtomicGuestAddr { + pub const fn new() -> Self { + AtomicGuestAddr { + addr: AtomicPtr::new(null_mut()), + } + } + + pub fn load(&self) -> Option { + let addr = self.addr.load(Ordering::SeqCst) as GuestAddr; + match addr { + GuestAddr::MIN => None, + _ => Some(addr), + } + } + + pub fn store(&self, addr: GuestAddr) { + self.addr.store(addr as *mut c_void, Ordering::SeqCst); + } + + pub fn get_or_insert_with(&self, f: F) -> GuestAddr + where + F: FnOnce() -> GuestAddr, + { + if let Some(addr) = self.load() { + addr + } else { + let addr = f(); + self.store(addr); + addr + } + } + + pub fn try_get_or_insert_with(&self, f: F) -> Result + where + F: FnOnce() -> Result, + E: Debug, + { + if let Some(addr) = self.load() { + Ok(addr) + } else { + let addr = f()?; + self.store(addr); + Ok(addr) + } + } +} + +impl Default for AtomicGuestAddr { + fn default() -> Self { + Self::new() + } +} + +pub trait Symbols: Debug + Sized + Send { + type Error: Debug; + fn lookup(name: *const c_char) -> Result; +} + +pub trait Function { + const NAME: &'static CStr; + type Func: Copy; +} + +pub trait SymbolsLookupStr: Symbols { + fn lookup_str(name: &CStr) -> Result; +} + +impl SymbolsLookupStr for S { + fn lookup_str(name: &CStr) -> Result { + S::lookup(name.as_ptr() as *const c_char) + } +} + +pub trait FunctionPointer: Function { + fn as_ptr(addr: GuestAddr) -> Result; +} + +impl FunctionPointer for T { + fn as_ptr(addr: GuestAddr) -> Result { + if addr == GuestAddr::MIN || addr == GuestAddr::MAX { + Err(FunctionPointerError::BadAddress(addr))?; + } + + #[cfg(feature = "hooks")] + PatchedHooks::check_patched(addr).map_err(FunctionPointerError::PatchedAddress)?; + + let pp_sym = (&addr) as *const GuestAddr as *const *mut c_void; + let p_f = pp_sym as *const Self::Func; + let f = unsafe { *p_f }; + Ok(f) + } +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FunctionPointerError { + #[error("Bad address: {0}")] + BadAddress(GuestAddr), + #[cfg(feature = "hooks")] + #[error("Patched address: {0}")] + PatchedAddress(PatchesCheckError), +} diff --git a/libafl_qemu/librasan/asan/src/symbols/nop.rs b/libafl_qemu/librasan/asan/src/symbols/nop.rs new file mode 100644 index 0000000000..53162338b9 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/symbols/nop.rs @@ -0,0 +1,22 @@ +use core::ffi::c_char; + +use thiserror::Error; + +use crate::{GuestAddr, symbols::Symbols}; + +#[derive(Debug)] +pub struct NopSymbols; + +impl Symbols for NopSymbols { + type Error = NopSymbolsError; + + fn lookup(name: *const c_char) -> Result { + Err(NopSymbolsError::SymbolNotFound(name)) + } +} + +#[derive(Error, Debug, PartialEq, Clone)] +pub enum NopSymbolsError { + #[error("Symbol not found: {0:p}")] + SymbolNotFound(*const c_char), +} diff --git a/libafl_qemu/librasan/asan/src/test.rs b/libafl_qemu/librasan/asan/src/test.rs new file mode 100644 index 0000000000..582f8771ff --- /dev/null +++ b/libafl_qemu/librasan/asan/src/test.rs @@ -0,0 +1,210 @@ +use core::{ + ffi::{CStr, c_char, c_void}, + sync::atomic::{AtomicBool, Ordering}, +}; + +use log::{Level, error, trace}; +use spin::{Lazy, Mutex}; + +use crate::{ + GuestAddr, + allocator::{ + backend::dlmalloc::DlmallocBackend, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + exit::exit, + shadow::Shadow, + symbols::Symbols, + tracking::Tracking, +}; + +#[cfg(not(feature = "libc"))] +type TestSyms = crate::symbols::nop::NopSymbols; + +#[cfg(feature = "libc")] +type TestSyms = crate::symbols::dlsym::DlSymSymbols; + +#[cfg(all(feature = "linux", not(feature = "libc")))] +type TestMap = crate::mmap::linux::LinuxMmap; + +#[cfg(feature = "libc")] +type TestMap = crate::mmap::libc::LibcMmap; + +#[cfg(all(feature = "libc", not(feature = "guest"), feature = "host"))] +type TestHost = crate::host::libc::LibcHost; + +#[cfg(all( + feature = "linux", + not(feature = "libc"), + not(feature = "guest"), + feature = "host" +))] +type TestHost = crate::host::linux::LinuxHost; + +#[cfg(feature = "guest")] +type TestShadow = + crate::shadow::guest::GuestShadow; + +#[cfg(feature = "guest")] +type TestTracking = crate::tracking::guest::GuestTracking; + +#[cfg(all(not(feature = "guest"), feature = "host"))] +type TestTracking = crate::tracking::host::HostTracking; + +#[cfg(all(not(feature = "guest"), feature = "host"))] +type TestShadow = crate::shadow::host::HostShadow; + +#[cfg(feature = "libc")] +use crate::logger::libc::LibcLogger; +#[cfg(all(feature = "linux", not(feature = "libc")))] +use crate::logger::linux::LinuxLogger; + +pub type TestFrontend = DefaultFrontend, TestShadow, TestTracking>; + +const PAGE_SIZE: usize = 4096; + +static FRONTEND: Lazy> = Lazy::new(|| { + #[cfg(all(feature = "linux", not(feature = "libc")))] + LinuxLogger::initialize(Level::Info); + #[cfg(feature = "libc")] + LibcLogger::initialize::(Level::Info); + let backend = DlmallocBackend::::new(PAGE_SIZE); + let shadow = TestShadow::new().unwrap(); + let tracking = TestTracking::new().unwrap(); + let frontend = TestFrontend::new( + backend, + shadow, + tracking, + TestFrontend::DEFAULT_REDZONE_SIZE, + TestFrontend::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + Mutex::new(frontend) +}); + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_load(addr: *const c_void, size: usize) { + trace!("load - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_store(addr: *const c_void, size: usize) { + trace!("store - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_alloc(len: usize, align: usize) -> *mut c_void { + trace!("alloc - len: {:#x}, align: {:#x}", len, align); + let ptr = FRONTEND.lock().alloc(len, align).unwrap() as *mut c_void; + trace!( + "alloc - len: {:#x}, align: {:#x}, ptr: {:p}", + len, align, ptr + ); + ptr +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_dealloc(addr: *const c_void) { + trace!("free - addr: {:p}", addr); + FRONTEND.lock().dealloc(addr as GuestAddr).unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_get_size(addr: *const c_void) -> usize { + trace!("get_size - addr: {:p}", addr); + FRONTEND.lock().get_size(addr as GuestAddr).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_sym(name: *const c_char) -> GuestAddr { + TestSyms::lookup(name).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_page_size() -> usize { + PAGE_SIZE +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_unpoison(addr: *const c_void, len: usize) { + trace!("unpoison - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .shadow_mut() + .unpoison(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_track(addr: *const c_void, len: usize) { + trace!("track - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .tracking_mut() + .alloc(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_untrack(addr: *const c_void) { + trace!("untrack - addr: {:p}", addr); + FRONTEND + .lock() + .tracking_mut() + .dealloc(addr as GuestAddr) + .unwrap(); +} + +static EXPECT_PANIC: AtomicBool = AtomicBool::new(false); + +pub fn expect_panic() { + EXPECT_PANIC.store(true, Ordering::SeqCst); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_panic(msg: *const c_char) -> ! { + trace!("panic - msg: {:p}", msg); + let msg = unsafe { CStr::from_ptr(msg as *const c_char) }; + error!("{}", msg.to_str().unwrap()); + match EXPECT_PANIC.load(Ordering::SeqCst) { + true => { + exit(0); + } + false => { + panic!("unexpected panic"); + } + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_swap(enabled: bool) { + trace!("swap - enabled: {}", enabled); +} diff --git a/libafl_qemu/librasan/asan/src/tracking/guest.rs b/libafl_qemu/librasan/asan/src/tracking/guest.rs new file mode 100644 index 0000000000..7937a3b5c3 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/tracking/guest.rs @@ -0,0 +1,125 @@ +//! # guest +//! This implementation performs guest memory tracking by use of a simple sorted +//! list residing in the guest's user space. Hence no interaction with the host +//! is required. +use alloc::vec::Vec; +use core::cmp::Ordering; + +use log::debug; +use thiserror::Error; + +use crate::{GuestAddr, tracking::Tracking}; + +#[derive(Eq, Debug)] +struct Range { + start: GuestAddr, + len: usize, +} + +impl PartialEq for Range { + fn eq(&self, other: &Self) -> bool { + if self.start > other.start { + let delta = self.start - other.start; + delta < other.len + } else { + let delta = other.start - self.start; + delta < self.len + } + } +} + +impl PartialOrd for Range { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Range { + fn cmp(&self, other: &Self) -> Ordering { + if self.eq(other) { + Ordering::Equal + } else { + self.start.cmp(&other.start) + } + } +} + +type Ranges = Vec; + +#[derive(Debug)] +pub struct GuestTracking { + ranges: Ranges, +} + +impl Tracking for GuestTracking { + type Error = GuestTrackingError; + + fn alloc(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("alloc - start: 0x{:x}, len: 0x{:x}", start, len); + if Self::is_out_of_bounds(start, len) { + Err(GuestTrackingError::AddressRangeOverflow(start, len))?; + } + + if len == 0 { + Err(GuestTrackingError::ZeroLength(start))?; + } + + let item = Range { start, len }; + + let pos = self.ranges.binary_search(&item); + match pos { + Ok(pos) => { + let conflict = &self.ranges[pos]; + Err(GuestTrackingError::TrackingConflict( + conflict.start, + conflict.len, + item.start, + item.len, + ))?; + } + Err(pos) => { + self.ranges.insert(pos, item); + } + } + + Ok(()) + } + + fn dealloc(&mut self, start: GuestAddr) -> Result<(), Self::Error> { + debug!("dealloc - start: 0x{:x}", start); + let pos = self.ranges.binary_search_by(|item| item.start.cmp(&start)); + match pos { + Ok(pos) => { + self.ranges.remove(pos); + Ok(()) + } + Err(_pos) => Err(GuestTrackingError::AllocationNotFound(start)), + } + } +} + +impl GuestTracking { + pub fn new() -> Result { + Ok(GuestTracking { ranges: Vec::new() }) + } + + pub fn is_out_of_bounds(addr: GuestAddr, len: usize) -> bool { + if len == 0 { + false + } else { + GuestAddr::MAX - len + 1 < addr + } + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum GuestTrackingError { + #[error("Address overflow: {0:x}, len: {1:x}")] + AddressRangeOverflow(GuestAddr, usize), + #[error("Allocation not found: {0:x}")] + AllocationNotFound(GuestAddr), + #[error("Tracking conflict")] + TrackingConflict(GuestAddr, usize, GuestAddr, usize), + #[error("Zero Length")] + ZeroLength(GuestAddr), +} diff --git a/libafl_qemu/librasan/asan/src/tracking/host.rs b/libafl_qemu/librasan/asan/src/tracking/host.rs new file mode 100644 index 0000000000..ad78084e75 --- /dev/null +++ b/libafl_qemu/librasan/asan/src/tracking/host.rs @@ -0,0 +1,46 @@ +//! # host +//! Like `HostShadow` this implementation makes use of a `Host` implementation +//! in order to relay the requests for memory tracking to the host emulator. In +//! the case of QEMU on Linux, this will typically be by means of a bespoke +//! `syscall`. +use core::marker::PhantomData; + +use log::debug; +use syscalls::Errno; +use thiserror::Error; + +use crate::{GuestAddr, host::Host, tracking::Tracking}; + +#[derive(Debug)] +pub struct HostTracking { + _phantom: PhantomData, +} + +impl Tracking for HostTracking { + type Error = HostTrackingError; + + fn alloc(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error> { + debug!("alloc - start: 0x{:x}, len: 0x{:x}", start, len); + /* Here QEMU expects a start and end, rather than start and length */ + H::alloc(start, start + len).map_err(|e| HostTrackingError::HostError(e)) + } + + fn dealloc(&mut self, start: GuestAddr) -> Result<(), Self::Error> { + debug!("free - start: 0x{:x}", start); + H::dealloc(start).map_err(|e| HostTrackingError::HostError(e)) + } +} + +impl HostTracking { + pub fn new() -> Result { + Ok(HostTracking:: { + _phantom: PhantomData, + }) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum HostTrackingError { + #[error("Host error: {0:?}")] + HostError(H::Error), +} diff --git a/libafl_qemu/librasan/asan/src/tracking/mod.rs b/libafl_qemu/librasan/asan/src/tracking/mod.rs new file mode 100644 index 0000000000..9447b8be2a --- /dev/null +++ b/libafl_qemu/librasan/asan/src/tracking/mod.rs @@ -0,0 +1,18 @@ +//! # tracking +//! This module is responsible for supporting memory tracking. By logging the +//! ranges of memory being allocated and freed by the target application, we +//! can detect double-free defects. +use alloc::fmt::Debug; + +use crate::GuestAddr; + +#[cfg(feature = "guest")] +pub mod guest; +#[cfg(feature = "host")] +pub mod host; + +pub trait Tracking: Sized + Debug + Send { + type Error: Debug; + fn alloc(&mut self, start: GuestAddr, len: usize) -> Result<(), Self::Error>; + fn dealloc(&mut self, start: GuestAddr) -> Result<(), Self::Error>; +} diff --git a/libafl_qemu/librasan/asan/tests/default_frontend.rs b/libafl_qemu/librasan/asan/tests/default_frontend.rs new file mode 100644 index 0000000000..712b16e306 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/default_frontend.rs @@ -0,0 +1,67 @@ +#[cfg(test)] +#[cfg(all(feature = "linux", feature = "dlmalloc"))] +mod tests { + + use asan::{ + allocator::{ + backend::dlmalloc::DlmallocBackend, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + mmap::linux::LinuxMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + tracking::guest::GuestTracking, + }; + use spin::{Lazy, Mutex, MutexGuard}; + + const PAGE_SIZE: usize = 4096; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + Mutex::new({ + env_logger::init(); + let backend = DlmallocBackend::::new(PAGE_SIZE); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + DF::new( + backend, + shadow, + tracking, + DF::DEFAULT_REDZONE_SIZE, + DF::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap() + }) + }); + + type DF = DefaultFrontend< + DlmallocBackend, + GuestShadow, + GuestTracking, + >; + + fn frontend() -> MutexGuard<'static, DF> { + INIT_ONCE.lock() + } + + #[test] + fn test_allocate() { + let mut frontend = frontend(); + let buf = frontend.alloc(16, 8).unwrap(); + frontend.dealloc(buf).unwrap(); + } + + #[test] + fn test_allocate_is_poisoned() { + let mut frontend = frontend(); + let len = 16; + let buf = frontend.alloc(len, 8).unwrap(); + for i in buf - DF::DEFAULT_REDZONE_SIZE..buf + len + DF::DEFAULT_REDZONE_SIZE { + let expected = i < buf || i >= buf + len; + let poisoned = frontend.shadow().is_poison(i, 1).unwrap(); + assert_eq!(expected, poisoned); + } + frontend.dealloc(buf).unwrap(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/default_frontend_mock.rs b/libafl_qemu/librasan/asan/tests/default_frontend_mock.rs new file mode 100644 index 0000000000..b917749224 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/default_frontend_mock.rs @@ -0,0 +1,124 @@ +extern crate alloc; + +#[cfg(test)] +#[cfg(all(feature = "linux"))] +mod tests { + use alloc::alloc::{GlobalAlloc, Layout}; + + use asan::{ + GuestAddr, + allocator::frontend::{AllocatorFrontend, default::DefaultFrontend}, + mmap::{Mmap, linux::LinuxMmap}, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + tracking::guest::GuestTracking, + }; + use log::{debug, info}; + use mockall::mock; + use spin::{Lazy, Mutex, MutexGuard}; + use thiserror::Error; + + const MAX_ADDR: GuestAddr = 64 << 20; + + // We can't mock GlobalAlloc since `*mut u8` isn't Send and Sync, so we will + // create a trivial implementation of it which converts the types and calls this + // substititue mockable trait instead. + trait BackendTrait { + fn do_alloc(&self, layout: Layout) -> GuestAddr; + fn do_dealloc(&self, addr: GuestAddr, layout: Layout); + } + + mock! { + #[derive(Debug)] + pub Backend {} + + impl BackendTrait for Backend { + fn do_alloc(&self, layout: Layout) -> GuestAddr; + fn do_dealloc(&self, addr: GuestAddr, layout: Layout); + } + } + + unsafe impl GlobalAlloc for MockBackend { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.do_alloc(layout) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.do_dealloc(ptr as GuestAddr, layout) + } + } + + #[derive(Error, Debug, PartialEq)] + pub enum MockBackendError {} + + static INIT_ONCE: Lazy> = Lazy::new(|| { + Mutex::new({ + env_logger::init(); + let backend = MockBackend::new(); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + DF::new( + backend, + shadow, + tracking, + DF::DEFAULT_REDZONE_SIZE, + DF::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap() + }) + }); + + static MAP: Lazy = Lazy::new(|| LinuxMmap::map(MAX_ADDR).unwrap()); + + type DF = + DefaultFrontend, GuestTracking>; + + fn frontend() -> MutexGuard<'static, DF> { + INIT_ONCE.lock() + } + + #[test] + fn test_allocate_is_poisoned() { + let mut frontend = frontend(); + + let base = MAP.as_slice().as_ptr() as GuestAddr; + info!("base: 0x{:x}", base); + + let inputs = [[4, 8, 0], [0x3ff, 0, 0]]; + for [len, align, addr] in inputs { + frontend + .backend_mut() + .expect_do_alloc() + .returning(move |layout| { + debug!( + "mock - len: 0x{:x}, align: 0x{:x}", + layout.size(), + layout.align() + ); + base + addr + }); + frontend + .backend_mut() + .expect_do_dealloc() + .returning(|addr, layout| { + debug!( + "mock - addr: 0x{:x}, len: 0x{:x}, align: 0x{:x}", + addr, + layout.size(), + layout.align() + ); + }); + + let buf = frontend.alloc(len, align).unwrap(); + info!("buf: 0x{:x}", buf); + for i in buf - DF::DEFAULT_REDZONE_SIZE..buf + len + DF::DEFAULT_REDZONE_SIZE { + let expected = i < buf || i >= buf + len; + let poisoned = frontend.shadow().is_poison(i, 1).unwrap(); + assert_eq!(expected, poisoned); + } + frontend.dealloc(buf).unwrap(); + } + } +} diff --git a/libafl_qemu/librasan/asan/tests/dlmalloc_backend.rs b/libafl_qemu/librasan/asan/tests/dlmalloc_backend.rs new file mode 100644 index 0000000000..e26912f0e2 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/dlmalloc_backend.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(all(feature = "linux", feature = "dlmalloc"))] +mod tests { + + use std::{ + alloc::{GlobalAlloc, Layout}, + sync::Mutex, + }; + + use asan::{allocator::backend::dlmalloc::DlmallocBackend, mmap::linux::LinuxMmap}; + use spin::Lazy; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + const PAGE_SIZE: usize = 4096; + + fn allocator() -> DlmallocBackend { + drop(INIT_ONCE.lock().unwrap()); + DlmallocBackend::::new(PAGE_SIZE) + } + + #[test] + fn test_allocate() { + let allocator = allocator(); + let layout = Layout::from_size_align(16, 8).unwrap(); + let buf = unsafe { allocator.alloc(layout) }; + assert!(!buf.is_null()); + unsafe { allocator.dealloc(buf, layout) }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_align.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_align.rs new file mode 100644 index 0000000000..cec39f0fe3 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_align.rs @@ -0,0 +1,124 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use asan::{ + GuestAddr, + mmap::Mmap, + shadow::guest::{DefaultShadowLayout, GuestShadow}, + }; + + #[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] + struct DummyMmap; + + impl Mmap for DummyMmap { + type Error = DummyMmapError; + + fn map(_size: usize) -> Result { + unimplemented!() + } + + fn map_at(_base: asan::GuestAddr, _size: usize) -> Result { + unimplemented!() + } + + fn protect( + _addr: GuestAddr, + _len: usize, + _prot: asan::mmap::MmapProt, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn as_slice(&self) -> &[u8] { + unimplemented!() + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + unimplemented!() + } + + fn huge_pages(_addr: GuestAddr, _len: usize) -> Result<(), Self::Error> { + unimplemented!() + } + + fn dont_dump(_addr: GuestAddr, _len: usize) -> Result<(), Self::Error> { + unimplemented!() + } + } + + #[derive(Debug)] + struct DummyMmapError; + + type GS = GuestShadow; + + #[test] + fn test_align_up_zero() { + assert_eq!(GS::align_up(0), 0); + assert_eq!(GS::align_up(1), 8); + assert_eq!(GS::align_up(2), 8); + assert_eq!(GS::align_up(3), 8); + assert_eq!(GS::align_up(4), 8); + assert_eq!(GS::align_up(5), 8); + assert_eq!(GS::align_up(6), 8); + assert_eq!(GS::align_up(7), 8); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_one() { + GS::align_up(GuestAddr::MAX - 1); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_two() { + GS::align_up(GuestAddr::MAX - 2); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_three() { + GS::align_up(GuestAddr::MAX - 3); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_four() { + GS::align_up(GuestAddr::MAX - 4); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_five() { + GS::align_up(GuestAddr::MAX - 5); + } + + #[test] + #[should_panic] + fn test_align_up_max_minus_six() { + GS::align_up(GuestAddr::MAX - 6); + } + + #[test] + fn test_align_down_zero() { + assert_eq!(GS::align_down(0), 0); + assert_eq!(GS::align_down(1), 0); + assert_eq!(GS::align_down(2), 0); + assert_eq!(GS::align_down(3), 0); + assert_eq!(GS::align_down(4), 0); + assert_eq!(GS::align_down(5), 0); + assert_eq!(GS::align_down(6), 0); + assert_eq!(GS::align_down(7), 0); + } + + #[test] + fn test_align_down_max() { + assert_eq!(GS::align_down(GuestAddr::MAX), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 1), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 2), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 3), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 4), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 5), GuestAddr::MAX - 7); + assert_eq!(GS::align_down(GuestAddr::MAX - 6), GuestAddr::MAX - 7); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_example.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_example.rs new file mode 100644 index 0000000000..8ed0f9b6b9 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_example.rs @@ -0,0 +1,65 @@ +#[cfg(test)] +#[cfg(all(feature = "guest", target_pointer_width = "64"))] +mod tests { + use std::sync::Mutex; + + use asan::{ + mmap::libc::LibcMmap, + shadow::{ + PoisonType, Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + symbols::dlsym::{DlSymSymbols, LookupTypeNext}, + }; + use spin::Lazy; + + type GS = GuestShadow>, DefaultShadowLayout>; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GS { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_poison_example1() { + let mut shadow = get_shadow(); + // poison - start: 0x7fff2bffff00, len: 0x100, pioson: AsanUser + // is_poison - start: 0x7fff2bfffc01, len: 0x300 + assert_eq!( + shadow.poison(0x7fff2bffff00, 0x100, PoisonType::AsanUser), + Ok(()) + ); + assert_eq!(shadow.is_poison(0x7fff2bfffc01, 0x300), Ok(true)); + } + + #[test] + fn test_poison_example2() { + let mut shadow = get_shadow(); + // poison - start: 0x7dff13ffffff, len: 0x3b9, pioson: AsanUser + // is_poison - start: 0x7dff14000302, len: 0x2 + assert_eq!( + shadow.poison(0x7dff13ffffff, 0x3b9, PoisonType::AsanUser), + Ok(()) + ); + assert_eq!(shadow.is_poison(0x7dff14000302, 0x2), Ok(true)); + } + + #[test] + fn test_poison_example3() { + let mut shadow = get_shadow(); + // poison - start: 0x7fffffffff00, len: 0x100, pioson: AsanUser + // is_poison - start: 0x7fffffffff00, len: 0xff + assert_eq!( + shadow.poison(0x7fffffffff00, 0x100, PoisonType::AsanUser), + Ok(()) + ); + assert_eq!(shadow.is_poison(0x7fffffffff00, 0xff), Ok(true)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_is_memory.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_is_memory.rs new file mode 100644 index 0000000000..a24a654f38 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_is_memory.rs @@ -0,0 +1,139 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use asan::{ + mmap::Mmap, + shadow::guest::{DefaultShadowLayout, GuestShadow}, + }; + + #[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] + struct DummyMmap; + + impl Mmap for DummyMmap { + type Error = DummyMmapError; + + fn map(_size: usize) -> Result { + unimplemented!() + } + + fn map_at(_base: asan::GuestAddr, _size: usize) -> Result { + unimplemented!() + } + + fn protect( + _addr: asan::GuestAddr, + _len: usize, + _prot: asan::mmap::MmapProt, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn as_slice(&self) -> &[u8] { + unimplemented!() + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + unimplemented!() + } + + fn huge_pages(_addr: asan::GuestAddr, _len: usize) -> Result<(), Self::Error> { + unimplemented!() + } + + fn dont_dump(_addr: asan::GuestAddr, _len: usize) -> Result<(), Self::Error> { + unimplemented!() + } + } + + #[derive(Debug)] + struct DummyMmapError; + + type GS = GuestShadow; + + #[test] + fn test_low_mem() { + assert!(GS::is_low_memory(GS::LOW_MEM_OFFSET, 1)); + assert!(GS::is_low_memory(GS::LOW_MEM_OFFSET, GS::LOW_MEM_SIZE)); + assert!(!GS::is_low_memory(GS::LOW_MEM_OFFSET, GS::LOW_MEM_SIZE + 1)); + assert!(GS::is_low_memory(GS::LOW_MEM_LIMIT, 1)); + + assert!(!GS::is_high_memory(GS::LOW_MEM_OFFSET, 1)); + assert!(!GS::is_high_memory(GS::LOW_MEM_OFFSET, GS::LOW_MEM_SIZE)); + assert!(!GS::is_high_memory( + GS::LOW_MEM_OFFSET, + GS::LOW_MEM_SIZE + 1 + )); + assert!(!GS::is_high_memory(GS::LOW_MEM_LIMIT, 1)); + + assert!(GS::is_memory(GS::LOW_MEM_OFFSET, 1)); + assert!(GS::is_memory(GS::LOW_MEM_OFFSET, GS::LOW_MEM_SIZE)); + assert!(!GS::is_memory(GS::LOW_MEM_OFFSET, GS::LOW_MEM_SIZE + 1)); + assert!(GS::is_memory(GS::LOW_MEM_LIMIT, 1)); + } + + #[test] + fn test_low_shadow() { + assert!(!GS::is_low_memory(GS::LOW_SHADOW_OFFSET, 1)); + assert!(!GS::is_low_memory( + GS::LOW_SHADOW_OFFSET, + GS::LOW_SHADOW_SIZE + )); + assert!(!GS::is_low_memory(GS::LOW_SHADOW_LIMIT, 1)); + + assert!(!GS::is_high_memory(GS::LOW_SHADOW_OFFSET, 0)); + assert!(!GS::is_high_memory( + GS::LOW_SHADOW_OFFSET, + GS::LOW_SHADOW_SIZE + )); + assert!(!GS::is_high_memory(GS::LOW_SHADOW_LIMIT, 1)); + + assert!(!GS::is_memory(GS::LOW_SHADOW_OFFSET, 1)); + assert!(!GS::is_memory(GS::LOW_SHADOW_OFFSET, GS::LOW_SHADOW_SIZE)); + assert!(!GS::is_memory(GS::LOW_SHADOW_LIMIT, 1)); + } + + #[test] + fn test_high_shadow() { + assert!(!GS::is_low_memory(GS::HIGH_SHADOW_OFFSET, 1)); + assert!(!GS::is_low_memory( + GS::HIGH_SHADOW_OFFSET, + GS::HIGH_SHADOW_SIZE + )); + assert!(!GS::is_low_memory(GS::HIGH_SHADOW_LIMIT, 1)); + + assert!(!GS::is_high_memory(GS::HIGH_SHADOW_OFFSET, 0)); + assert!(!GS::is_high_memory( + GS::HIGH_SHADOW_OFFSET, + GS::HIGH_SHADOW_SIZE + )); + assert!(!GS::is_high_memory(GS::HIGH_SHADOW_LIMIT, 1)); + + assert!(!GS::is_memory(GS::HIGH_SHADOW_OFFSET, 1)); + assert!(!GS::is_memory(GS::HIGH_SHADOW_OFFSET, GS::HIGH_SHADOW_SIZE)); + assert!(!GS::is_memory(GS::HIGH_SHADOW_LIMIT, 1)); + } + + #[test] + fn test_high_mem() { + assert!(GS::is_high_memory(GS::HIGH_MEM_OFFSET, 1)); + assert!(GS::is_high_memory(GS::HIGH_MEM_OFFSET, GS::HIGH_MEM_SIZE)); + assert!(!GS::is_high_memory( + GS::HIGH_MEM_OFFSET, + GS::HIGH_MEM_SIZE + 1 + )); + assert!(GS::is_high_memory(GS::HIGH_MEM_LIMIT, 1)); + + assert!(!GS::is_low_memory(GS::HIGH_MEM_OFFSET, 1)); + assert!(!GS::is_low_memory(GS::HIGH_MEM_OFFSET, GS::HIGH_MEM_SIZE)); + assert!(!GS::is_low_memory( + GS::HIGH_MEM_OFFSET, + GS::HIGH_MEM_SIZE + 1 + )); + assert!(!GS::is_low_memory(GS::HIGH_MEM_LIMIT, 1)); + + assert!(GS::is_memory(GS::HIGH_MEM_OFFSET, 1)); + assert!(GS::is_memory(GS::HIGH_MEM_OFFSET, GS::HIGH_MEM_SIZE)); + assert!(!GS::is_memory(GS::HIGH_MEM_OFFSET, GS::HIGH_MEM_SIZE + 1)); + assert!(GS::is_memory(GS::HIGH_MEM_LIMIT, 1)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_libc_is_poison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_libc_is_poison.rs new file mode 100644 index 0000000000..59dd26c59f --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_libc_is_poison.rs @@ -0,0 +1,186 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::libc::LibcMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + symbols::dlsym::{DlSymSymbols, LookupTypeNext}, + }; + use spin::Lazy; + + type GS = GuestShadow>, DefaultShadowLayout>; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GS { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_is_posion_bottom_of_low_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_MEM_OFFSET, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_top_of_low_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_MEM_LIMIT - 0x7, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_bottom_of_low_shadow() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_is_posion_top_of_low_shadow() { + use asan::GuestAddr; + + let shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.is_poison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_is_posion_bottom_of_high_shadow() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_is_posion_top_of_high_shadow() { + let shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.is_poison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_is_posion_bottom_of_high_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_MEM_OFFSET, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_top_of_high_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_MEM_LIMIT - 0x7, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_unaligned_start() { + let shadow = get_shadow(); + let result = shadow.is_poison(7, 1); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_one() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 1); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_two() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 2); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_three() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 3); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_four() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 4); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_five() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 5); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_six() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 6); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_seven() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 7); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_overflow_address_range() { + let shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.is_poison(usize::MAX - (ALIGN - 1), ALIGN * 2); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_libc_poison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_libc_poison.rs new file mode 100644 index 0000000000..37db094080 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_libc_poison.rs @@ -0,0 +1,181 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::libc::LibcMmap, + shadow::{ + PoisonType, Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + symbols::dlsym::{DlSymSymbols, LookupTypeNext}, + }; + use spin::Lazy; + + type GS = GuestShadow>, DefaultShadowLayout>; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GS { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_posion_bottom_of_low_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::LOW_MEM_OFFSET, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_top_of_low_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::LOW_MEM_LIMIT - 0x7, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_bottom_of_low_shadow() { + let mut shadow = get_shadow(); + let result = shadow.poison(GS::LOW_SHADOW_OFFSET, 0x8, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_posion_top_of_low_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.poison(ADDR, 0x8, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_posion_bottom_of_high_shadow() { + let mut shadow = get_shadow(); + let result = shadow.poison(GS::HIGH_SHADOW_OFFSET, 0x8, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_posion_top_of_high_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.poison(ADDR, 0x8, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_posion_bottom_of_high_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::HIGH_MEM_OFFSET, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_top_of_high_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::HIGH_MEM_LIMIT - 0x7, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_unaligned_end() { + let mut shadow = get_shadow(); + let result = shadow.poison(0, 7, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::UnalignedEndAddress(0, 7))); + } + + #[test] + fn test_aligned_one() { + let mut shadow = get_shadow(); + shadow.poison(7, 1, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_two() { + let mut shadow = get_shadow(); + shadow.poison(6, 2, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_three() { + let mut shadow = get_shadow(); + shadow.poison(5, 3, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_four() { + let mut shadow = get_shadow(); + shadow.poison(4, 4, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_five() { + let mut shadow = get_shadow(); + shadow.poison(3, 5, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_six() { + let mut shadow = get_shadow(); + shadow.poison(2, 6, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_seven() { + let mut shadow = get_shadow(); + shadow.poison(1, 7, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_overflow_address_range() { + let mut shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.poison(usize::MAX - (ALIGN - 1), ALIGN * 2, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_linux_is_poison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_is_poison.rs new file mode 100644 index 0000000000..41d4b3479d --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_is_poison.rs @@ -0,0 +1,183 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::linux::LinuxMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + }; + use spin::Lazy; + + type GS = GuestShadow; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GuestShadow { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_is_posion_bottom_of_low_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_MEM_OFFSET, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_top_of_low_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_MEM_LIMIT - 0x7, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_bottom_of_low_shadow() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::LOW_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_is_posion_top_of_low_shadow() { + let shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.is_poison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_is_posion_bottom_of_high_shadow() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_is_posion_top_of_high_shadow() { + let shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.is_poison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_is_posion_bottom_of_high_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_MEM_OFFSET, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_is_posion_top_of_high_mem() { + let shadow = get_shadow(); + let result = shadow.is_poison(GS::HIGH_MEM_LIMIT - 7, 0x8); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_unaligned_start() { + let shadow = get_shadow(); + let result = shadow.is_poison(7, 1); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_one() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 1); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_two() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 2); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_three() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 3); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_four() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 4); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_five() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 5); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_six() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 6); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_aligned_seven() { + let shadow = get_shadow(); + let result = shadow.is_poison(0, 7); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_overflow_address_range() { + let shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.is_poison(usize::MAX - (ALIGN - 1), ALIGN * 2); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_linux_poison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_poison.rs new file mode 100644 index 0000000000..f82ca77dc1 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_poison.rs @@ -0,0 +1,180 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::linux::LinuxMmap, + shadow::{ + PoisonType, Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + }; + use spin::Lazy; + + type GS = GuestShadow; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GuestShadow { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_posion_bottom_of_low_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::LOW_MEM_OFFSET, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_top_of_low_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::LOW_MEM_LIMIT - 0x7, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_bottom_of_low_shadow() { + let mut shadow = get_shadow(); + let result = shadow.poison(GS::LOW_SHADOW_OFFSET, 0x8, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_posion_top_of_low_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.poison(ADDR, 0x8, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_posion_bottom_of_high_shadow() { + let mut shadow = get_shadow(); + let result = shadow.poison(GS::HIGH_SHADOW_OFFSET, 0x8, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_posion_top_of_high_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.poison(ADDR, 0x8, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_posion_bottom_of_high_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::HIGH_MEM_OFFSET, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_posion_top_of_high_mem() { + let mut shadow = get_shadow(); + shadow + .poison(GS::HIGH_MEM_LIMIT - 7, 0x8, PoisonType::AsanUser) + .unwrap(); + } + + #[test] + fn test_unaligned_end() { + let mut shadow = get_shadow(); + let result = shadow.poison(0, 7, PoisonType::AsanUser); + assert_eq!(result, Err(GuestShadowError::UnalignedEndAddress(0, 7))); + } + + #[test] + fn test_aligned_one() { + let mut shadow = get_shadow(); + shadow.poison(7, 1, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_two() { + let mut shadow = get_shadow(); + shadow.poison(6, 2, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_three() { + let mut shadow = get_shadow(); + shadow.poison(5, 3, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_four() { + let mut shadow = get_shadow(); + shadow.poison(4, 4, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_five() { + let mut shadow = get_shadow(); + shadow.poison(3, 5, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_six() { + let mut shadow = get_shadow(); + shadow.poison(2, 6, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_aligned_seven() { + let mut shadow = get_shadow(); + shadow.poison(1, 7, PoisonType::AsanUser).unwrap(); + } + + #[test] + fn test_overflow_address_range() { + let mut shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.poison(usize::MAX - (ALIGN - 1), ALIGN * 2, PoisonType::AsanUser); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_linux_unpoison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_unpoison.rs new file mode 100644 index 0000000000..13d9ec9c85 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_linux_unpoison.rs @@ -0,0 +1,172 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::linux::LinuxMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + }; + use spin::Lazy; + + type GS = GuestShadow; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GuestShadow { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_unposion_bottom_of_low_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::LOW_MEM_OFFSET, 0x8).unwrap(); + } + + #[test] + fn test_unposion_top_of_low_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::LOW_MEM_LIMIT - 0x7, 0x8).unwrap(); + } + + #[test] + fn test_unposion_bottom_of_low_shadow() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(GS::LOW_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_unposion_top_of_low_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.unpoison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_unposion_bottom_of_high_shadow() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(GS::HIGH_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_unposion_top_of_high_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.unpoison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_unposion_bottom_of_high_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::HIGH_MEM_OFFSET, 0x8).unwrap(); + } + + #[test] + fn test_unposion_top_of_high_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::HIGH_MEM_LIMIT - 7, 0x8).unwrap(); + } + + #[test] + fn test_unaligned_start() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(7, 1); + assert_eq!(result, Err(GuestShadowError::UnalignedStartAddress(7, 1))); + } + + #[test] + fn test_aligned_one() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 1).unwrap(); + } + + #[test] + fn test_aligned_two() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 2).unwrap(); + } + + #[test] + fn test_aligned_three() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 3).unwrap(); + } + + #[test] + fn test_aligned_four() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 4).unwrap(); + } + + #[test] + fn test_aligned_five() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 5).unwrap(); + } + + #[test] + fn test_aligned_six() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 6).unwrap(); + } + + #[test] + fn test_aligned_seven() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 7).unwrap(); + } + + #[test] + fn test_overflow_address_range() { + let mut shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.unpoison(usize::MAX - (ALIGN - 1), ALIGN * 2); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_shadow_unpoison.rs b/libafl_qemu/librasan/asan/tests/guest_shadow_unpoison.rs new file mode 100644 index 0000000000..483cf5e23f --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_shadow_unpoison.rs @@ -0,0 +1,173 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + use std::sync::Mutex; + + use asan::{ + GuestAddr, + mmap::libc::LibcMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + symbols::dlsym::{DlSymSymbols, LookupTypeNext}, + }; + use spin::Lazy; + + type GS = GuestShadow>, DefaultShadowLayout>; + + const ALIGN: usize = GS::ALLOC_ALIGN_SIZE; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_shadow() -> GS { + drop(INIT_ONCE.lock().unwrap()); + GS::new().unwrap() + } + + #[test] + fn test_init() { + get_shadow(); + } + + // [0x10007fff8000, 0x7fffffffffff] HighMem + // [0x02008fff7000, 0x10007fff7fff] HighShadow + // [0x00008fff7000, 0x02008fff6fff] ShadowGap + // [0x00007fff8000, 0x00008fff6fff] LowShadow + // [0x000000000000, 0x00007fff7fff] LowMem + + // [0x40000000, 0xffffffff] HighMem + // [0x28000000, 0x3fffffff] HighShadow + // [0x24000000, 0x27ffffff] ShadowGap + // [0x20000000, 0x23ffffff] LowShadow + // [0x00000000, 0x1fffffff] LowMem + #[test] + fn test_unposion_bottom_of_low_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::LOW_MEM_OFFSET, 0x8).unwrap(); + } + + #[test] + fn test_unposion_top_of_low_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::LOW_MEM_LIMIT - 0x7, 0x8).unwrap(); + } + + #[test] + fn test_unposion_bottom_of_low_shadow() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(GS::LOW_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::LOW_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_unposion_top_of_low_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::LOW_SHADOW_OFFSET + GS::LOW_SHADOW_SIZE - 8; + let result = shadow.unpoison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_unposion_bottom_of_high_shadow() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(GS::HIGH_SHADOW_OFFSET, 0x8); + assert_eq!( + result, + Err(GuestShadowError::InvalidMemoryAddress( + GS::HIGH_SHADOW_OFFSET + )) + ); + } + + #[test] + fn test_unposion_top_of_high_shadow() { + let mut shadow = get_shadow(); + const ADDR: GuestAddr = GS::HIGH_SHADOW_OFFSET + GS::HIGH_SHADOW_SIZE - 8; + let result = shadow.unpoison(ADDR, 0x8); + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(ADDR))); + } + + #[test] + fn test_unposion_bottom_of_high_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::HIGH_MEM_OFFSET, 0x8).unwrap(); + } + + #[test] + fn test_unposion_top_of_high_mem() { + let mut shadow = get_shadow(); + shadow.unpoison(GS::HIGH_MEM_LIMIT - 7, 0x8).unwrap(); + } + + #[test] + fn test_unaligned_start() { + let mut shadow = get_shadow(); + let result = shadow.unpoison(7, 1); + assert_eq!(result, Err(GuestShadowError::UnalignedStartAddress(7, 1))); + } + + #[test] + fn test_aligned_one() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 1).unwrap(); + } + + #[test] + fn test_aligned_two() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 2).unwrap(); + } + + #[test] + fn test_aligned_three() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 3).unwrap(); + } + + #[test] + fn test_aligned_four() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 4).unwrap(); + } + + #[test] + fn test_aligned_five() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 5).unwrap(); + } + + #[test] + fn test_aligned_six() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 6).unwrap(); + } + + #[test] + fn test_aligned_seven() { + let mut shadow = get_shadow(); + shadow.unpoison(0, 7).unwrap(); + } + + #[test] + fn test_overflow_address_range() { + let mut shadow = get_shadow(); + let start = usize::MAX - (ALIGN - 1); + let end = ALIGN * 2; + let result = shadow.unpoison(usize::MAX - (ALIGN - 1), ALIGN * 2); + assert_eq!( + result, + Err(GuestShadowError::AddressRangeOverflow(start, end)) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/guest_tracking.rs b/libafl_qemu/librasan/asan/tests/guest_tracking.rs new file mode 100644 index 0000000000..83b6fb1fcc --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/guest_tracking.rs @@ -0,0 +1,110 @@ +#[cfg(test)] +#[cfg(feature = "guest")] +mod tests { + + use std::sync::Mutex; + + use asan::{ + GuestAddr, + tracking::{ + Tracking, + guest::{GuestTracking, GuestTrackingError}, + }, + }; + use spin::Lazy; + + static INIT_ONCE: Lazy> = Lazy::new(|| { + { + env_logger::init(); + }; + Mutex::new(()) + }); + + fn get_tracking() -> GuestTracking { + drop(INIT_ONCE.lock().unwrap()); + GuestTracking::new().unwrap() + } + + #[test] + fn test_max() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(GuestAddr::MAX, 1), Ok(())); + } + + #[test] + fn test_out_of_bounds() { + let mut tracking = get_tracking(); + assert_eq!( + tracking.alloc(GuestAddr::MAX, 2), + Err(GuestTrackingError::AddressRangeOverflow(GuestAddr::MAX, 2)) + ); + } + + #[test] + fn test_track_identical() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(0x1000, 0x1000), Ok(())); + assert_eq!( + tracking.alloc(0x1000, 0x1000), + Err(GuestTrackingError::TrackingConflict( + 0x1000, 0x1000, 0x1000, 0x1000 + )) + ); + } + + #[test] + fn test_track_adjacent_after() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(0x1000, 0x1000), Ok(())); + assert_eq!(tracking.alloc(0x2000, 0x1000), Ok(())); + } + + #[test] + fn test_track_adjacent_before() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(0x1000, 0x1000), Ok(())); + assert_eq!(tracking.alloc(0x0000, 0x1000), Ok(())); + } + + #[test] + fn test_track_overlapping_start() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(0x1000, 0x1000), Ok(())); + assert_eq!( + tracking.alloc(0x0000, 0x1001), + Err(GuestTrackingError::TrackingConflict( + 0x1000, 0x1000, 0x0000, 0x1001 + )) + ); + } + + #[test] + fn test_track_overlapping_end() { + let mut tracking = get_tracking(); + assert_eq!(tracking.alloc(0x1000, 0x1000), Ok(())); + assert_eq!( + tracking.alloc(0x1fff, 0x1001), + Err(GuestTrackingError::TrackingConflict( + 0x1000, 0x1000, 0x1fff, 0x1001 + )) + ); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_example_1() { + let mut tracking = get_tracking(); + // alloc - start: 0xffffffffb5b5ff21, len: 0x3ff + // alloc - start: 0xffffffffb5b60107, len: 0xdb + assert_eq!(tracking.alloc(0xffffffffb5b5ff21, 0x3ff), Ok(())); + assert_eq!( + tracking.alloc(0xffffffffb5b60107, 0xdb), + Err(GuestTrackingError::TrackingConflict( + 0xffffffffb5b5ff21, + 0x3ff, + 0xffffffffb5b60107, + 0xdb + )) + ); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_aligned_alloc.rs b/libafl_qemu/librasan/asan/tests/hooks_aligned_alloc.rs new file mode 100644 index 0000000000..b6c6a26219 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_aligned_alloc.rs @@ -0,0 +1,46 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::{expect_panic, hooks::aligned_alloc::aligned_alloc}; + + #[test] + fn aligned_alloc_zero_size() { + let ret = unsafe { aligned_alloc(8, 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn aligned_alloc_size_not_multiple() { + expect_panic(); + unsafe { aligned_alloc(9, 8) }; + unreachable!(); + } + + #[test] + fn aligned_alloc_power_of_two() { + let addr = unsafe { aligned_alloc(8, 8) }; + assert_ne!(addr, null_mut()); + assert_eq!(addr as usize & 7, 0); + } + + #[test] + fn aligned_alloc_not_power_of_two() { + expect_panic(); + unsafe { aligned_alloc(7, 24) }; + unreachable!(); + } + + #[test] + fn aligned_alloc_buff() { + let ret = unsafe { aligned_alloc(32, 8) }; + assert_ne!(ret, null_mut()); + assert!(ret as usize & 0x1f == 0); + unsafe { + from_raw_parts_mut(ret as *mut u8, 8) + .iter_mut() + .for_each(|x| *x = 0) + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_atoi.rs b/libafl_qemu/librasan/asan/tests/hooks_atoi.rs new file mode 100644 index 0000000000..f1bee0f2c9 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_atoi.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null_mut}; + use std::ffi::c_int; + + use asan::{expect_panic, hooks::atoi::atoi}; + + #[test] + fn atoi_test_null() { + expect_panic(); + unsafe { atoi(null_mut()) }; + unreachable!(); + } + + #[test] + fn atoi_test_number() { + let ret = unsafe { atoi(c"123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_zero() { + let ret = unsafe { atoi(c"0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atoi_test_negative_zero() { + let ret = unsafe { atoi(c"-0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atoi_test_leading_whitespace_1() { + let ret = unsafe { atoi(c" 123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_leading_whitespace_2() { + let ret = unsafe { atoi(c"\n\n123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_leading_whitespace_3() { + let ret = unsafe { atoi(c"\r\r123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_leading_whitespace_4() { + let ret = unsafe { atoi(c"\t\t123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_leading_whitespace_negative() { + let ret = unsafe { atoi(c" -123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atoi_test_leading_zeroes() { + let ret = unsafe { atoi(c"000123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoi_test_negative() { + let ret = unsafe { atoi(c"-123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atoi_test_non_numeric() { + let ret = unsafe { atoi(c"12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, 12); + } + + #[test] + fn atoi_test_non_numeric_negative() { + let ret = unsafe { atoi(c"-12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, -12); + } + + #[test] + fn atoi_test_max() { + let ret = unsafe { atoi(c"2147483647".as_ptr() as *const c_char) }; + assert_eq!(ret, c_int::MAX); + } + + #[test] + fn atoi_test_min() { + let ret = unsafe { atoi(c"-2147483648".as_ptr() as *const c_char) }; + assert_eq!(ret, c_int::MIN); + } + + #[test] + fn atoi_test_overflow() { + expect_panic(); + unsafe { atoi(c"2147483648".as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn atoi_test_underflow() { + expect_panic(); + unsafe { atoi(c"-2147483649".as_ptr() as *const c_char) }; + unreachable!(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_atol.rs b/libafl_qemu/librasan/asan/tests/hooks_atol.rs new file mode 100644 index 0000000000..3c1449ddaa --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_atol.rs @@ -0,0 +1,147 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null_mut}; + use std::ffi::c_long; + + use asan::{expect_panic, hooks::atol::atol}; + + #[test] + fn atol_test_null() { + expect_panic(); + unsafe { atol(null_mut()) }; + unreachable!(); + } + + #[test] + fn atol_test_number() { + let ret = unsafe { atol(c"123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_zero() { + let ret = unsafe { atol(c"0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atol_test_negative_zero() { + let ret = unsafe { atol(c"-0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atol_test_leading_whitespace_1() { + let ret = unsafe { atol(c" 123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_leading_whitespace_2() { + let ret = unsafe { atol(c"\n\n123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_leading_whitespace_3() { + let ret = unsafe { atol(c"\r\r123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_leading_whitespace_4() { + let ret = unsafe { atol(c"\t\t123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_leading_whitespace_negative() { + let ret = unsafe { atol(c" -123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atol_test_leading_zeroes() { + let ret = unsafe { atol(c"000123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atol_test_negative() { + let ret = unsafe { atol(c"-123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atol_test_non_numeric() { + let ret = unsafe { atol(c"12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, 12); + } + + #[test] + fn atol_test_non_numeric_negative() { + let ret = unsafe { atol(c"-12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, -12); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn atol_test_max() { + let ret = unsafe { atol(c"2147483647".as_ptr() as *const c_char) }; + assert_eq!(ret, c_long::MAX); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn atol_test_max() { + let ret = unsafe { atol(c"9223372036854775807".as_ptr() as *const c_char) }; + assert_eq!(ret, c_long::MAX); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn atol_test_min() { + let ret = unsafe { atol(c"-2147483648".as_ptr() as *const c_char) }; + assert_eq!(ret, c_long::MIN); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn atol_test_min() { + let ret = unsafe { atol(c"-9223372036854775808".as_ptr() as *const c_char) }; + assert_eq!(ret, c_long::MIN); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn atol_test_overflow() { + expect_panic(); + unsafe { atol(c"2147483648".as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn atol_test_overflow() { + expect_panic(); + unsafe { atol(c"9223372036854775808".as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn atol_test_underflow() { + expect_panic(); + unsafe { atol(c"-2147483649".as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn atol_test_underflow() { + expect_panic(); + unsafe { atol(c"-9223372036854775809".as_ptr() as *const c_char) }; + unreachable!(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_atoll.rs b/libafl_qemu/librasan/asan/tests/hooks_atoll.rs new file mode 100644 index 0000000000..4085d564a9 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_atoll.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null_mut}; + use std::ffi::c_longlong; + + use asan::{expect_panic, hooks::atoll::atoll}; + + #[test] + fn atoll_test_null() { + expect_panic(); + unsafe { atoll(null_mut()) }; + unreachable!(); + } + + #[test] + fn atoll_test_number() { + let ret = unsafe { atoll(c"123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_zero() { + let ret = unsafe { atoll(c"0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atoll_test_negative_zero() { + let ret = unsafe { atoll(c"-0".as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn atoll_test_leading_whitespace_1() { + let ret = unsafe { atoll(c" 123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_leading_whitespace_2() { + let ret = unsafe { atoll(c"\n\n123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_leading_whitespace_3() { + let ret = unsafe { atoll(c"\r\r123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_leading_whitespace_4() { + let ret = unsafe { atoll(c"\t\t123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_leading_whitespace_negative() { + let ret = unsafe { atoll(c" -123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atoll_test_leading_zeroes() { + let ret = unsafe { atoll(c"000123".as_ptr() as *const c_char) }; + assert_eq!(ret, 123); + } + + #[test] + fn atoll_test_negative() { + let ret = unsafe { atoll(c"-123".as_ptr() as *const c_char) }; + assert_eq!(ret, -123); + } + + #[test] + fn atoll_test_non_numeric() { + let ret = unsafe { atoll(c"12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, 12); + } + + #[test] + fn atoll_test_non_numeric_negative() { + let ret = unsafe { atoll(c"-12a3".as_ptr() as *const c_char) }; + assert_eq!(ret, -12); + } + + #[test] + fn atoll_test_max() { + let ret = unsafe { atoll(c"9223372036854775807".as_ptr() as *const c_char) }; + assert_eq!(ret, c_longlong::MAX); + } + + #[test] + fn atoll_test_min() { + let ret = unsafe { atoll(c"-9223372036854775808".as_ptr() as *const c_char) }; + assert_eq!(ret, c_longlong::MIN); + } + + #[test] + fn atoll_test_overflow() { + expect_panic(); + unsafe { atoll(c"9223372036854775808".as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn atoll_test_underflow() { + expect_panic(); + unsafe { atoll(c"-9223372036854775809".as_ptr() as *const c_char) }; + unreachable!(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_bcmp.rs b/libafl_qemu/librasan/asan/tests/hooks_bcmp.rs new file mode 100644 index 0000000000..c50bc8f6b6 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_bcmp.rs @@ -0,0 +1,70 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_void, ptr::null}; + + use asan::{expect_panic, hooks::bcmp::bcmp}; + + #[test] + fn test_zero_length() { + let ret = unsafe { bcmp(null(), null(), 0) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_null_cx() { + expect_panic(); + let data = [0u8; 10]; + unsafe { bcmp(null(), data.as_ptr() as *const c_void, data.len()) }; + unreachable!(); + } + + #[test] + fn test_null_ct() { + expect_panic(); + let data = [0u8; 10]; + unsafe { bcmp(data.as_ptr() as *const c_void, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_eq() { + let data = [0u8; 10]; + let ret = unsafe { + bcmp( + data.as_ptr() as *const c_void, + data.as_ptr() as *const c_void, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_lt() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + bcmp( + data1.as_ptr() as *const c_void, + data2.as_ptr() as *const c_void, + data1.len(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_gt() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + bcmp( + data1.as_ptr() as *const c_void, + data2.as_ptr() as *const c_void, + data1.len(), + ) + }; + assert!(ret > 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_bzero.rs b/libafl_qemu/librasan/asan/tests/hooks_bzero.rs new file mode 100644 index 0000000000..bdb5167e71 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_bzero.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::{expect_panic, hooks::bzero::bzero}; + + #[test] + fn test_bzero_zero_length() { + unsafe { bzero(null_mut(), 0) }; + } + + #[test] + fn test_bzero_null_s() { + expect_panic(); + unsafe { bzero(null_mut(), 10) }; + unreachable!(); + } + + #[test] + fn test_bzero_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { bzero(data.as_ptr() as *mut c_void, data.len()) }; + data.iter().for_each(|x| assert_eq!(*x, 0)); + } + + #[test] + fn test_bzero_partial_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { bzero(data.as_ptr().add(2) as *mut c_void, data.len() - 4) }; + data.iter().skip(2).take(6).for_each(|x| assert_eq!(*x, 0)); + data.iter().take(2).for_each(|x| assert_eq!(*x, 0xff)); + data.iter().skip(8).for_each(|x| assert_eq!(*x, 0xff)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_calloc.rs b/libafl_qemu/librasan/asan/tests/hooks_calloc.rs new file mode 100644 index 0000000000..df92ac5dcf --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_calloc.rs @@ -0,0 +1,32 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::ptr::null_mut; + + use asan::{expect_panic, hooks::calloc::calloc, size_t}; + + #[test] + fn test_zero_length() { + let ret = unsafe { calloc(0, 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_big_nobj() { + let ret = unsafe { calloc(65536, 1) }; + assert_ne!(ret, null_mut()); + } + + #[test] + fn test_big_size() { + let ret = unsafe { calloc(1, 65536) }; + assert_ne!(ret, null_mut()); + } + + #[test] + fn test_size_overflow() { + expect_panic(); + unsafe { calloc(size_t::MAX, 10) }; + unreachable!(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_explicit_bzero.rs b/libafl_qemu/librasan/asan/tests/hooks_explicit_bzero.rs new file mode 100644 index 0000000000..a66875f208 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_explicit_bzero.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::{expect_panic, hooks::explicit_bzero::explicit_bzero}; + + #[test] + fn test_explicit_bzero_zero_length() { + unsafe { explicit_bzero(null_mut(), 0) }; + } + + #[test] + fn test_explicit_bzero_null_s() { + expect_panic(); + unsafe { explicit_bzero(null_mut(), 10) }; + unreachable!(); + } + + #[test] + fn test_explicit_bzero_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { explicit_bzero(data.as_ptr() as *mut c_void, data.len()) }; + data.iter().for_each(|x| assert_eq!(*x, 0)); + } + + #[test] + fn test_explicit_bzero_partial_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { explicit_bzero(data.as_ptr().add(2) as *mut c_void, data.len() - 4) }; + data.iter().skip(2).take(6).for_each(|x| assert_eq!(*x, 0)); + data.iter().take(2).for_each(|x| assert_eq!(*x, 0xff)); + data.iter().skip(8).for_each(|x| assert_eq!(*x, 0xff)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_fgets.rs b/libafl_qemu/librasan/asan/tests/hooks_fgets.rs new file mode 100644 index 0000000000..28db370800 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_fgets.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks", feature = "libc"))] +mod tests { + use core::{ + ffi::{c_char, c_int}, + ptr::null_mut, + }; + + use asan::{expect_panic, hooks::fgets::fgets}; + use libc::FILE; + + #[test] + fn test_read_null_stream() { + let mut buf = [0u8; 10]; + expect_panic(); + + unsafe { + fgets( + buf.as_mut_ptr() as *mut c_char, + buf.len() as c_int, + null_mut(), + ) + }; + unreachable!(); + } + + #[test] + fn test_read_null_buff() { + let stream = 0xdeadface as *mut FILE; + expect_panic(); + + unsafe { fgets(null_mut(), 10, stream) }; + unreachable!(); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_free.rs b/libafl_qemu/librasan/asan/tests/hooks_free.rs new file mode 100644 index 0000000000..02cc1fe0e0 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_free.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::ptr::null_mut; + + use asan::hooks::{free::free, malloc::malloc}; + + #[test] + fn test_free_null() { + unsafe { free(null_mut()) }; + } + + #[test] + fn test_free_buff() { + let p = unsafe { malloc(10) }; + unsafe { free(p) } + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_malloc.rs b/libafl_qemu/librasan/asan/tests/hooks_malloc.rs new file mode 100644 index 0000000000..f75dc35ecc --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_malloc.rs @@ -0,0 +1,24 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::hooks::malloc::malloc; + + #[test] + fn test_malloc_zero() { + let p = unsafe { malloc(0) }; + assert_eq!(p, null_mut()); + } + + #[test] + fn test_malloc_buff() { + let p = unsafe { malloc(10) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 10) + .iter_mut() + .for_each(|x| *x = 0) + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_malloc_usable_size.rs b/libafl_qemu/librasan/asan/tests/hooks_malloc_usable_size.rs new file mode 100644 index 0000000000..392cf661ac --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_malloc_usable_size.rs @@ -0,0 +1,20 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::ptr::null_mut; + + use asan::hooks::{malloc::malloc, malloc_usable_size::malloc_usable_size}; + + #[test] + fn test_malloc_usable_size_null() { + let ret = unsafe { malloc_usable_size(null_mut()) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_malloc_usable_size_buff() { + let p = unsafe { malloc(10) }; + let ret = unsafe { malloc_usable_size(p) }; + assert_eq!(ret, 10); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memalign.rs b/libafl_qemu/librasan/asan/tests/hooks_memalign.rs new file mode 100644 index 0000000000..7e0b8bd0ef --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memalign.rs @@ -0,0 +1,46 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::{expect_panic, hooks::memalign::memalign}; + + #[test] + fn memalign_zero_size() { + let ret = unsafe { memalign(8, 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn memalign_size_not_multiple() { + expect_panic(); + unsafe { memalign(9, 8) }; + unreachable!(); + } + + #[test] + fn memalign_power_of_two() { + let addr = unsafe { memalign(8, 8) }; + assert_ne!(addr, 0 as *mut _); + assert_eq!(addr as usize & 7, 0); + } + + #[test] + fn memalign_not_power_of_two() { + expect_panic(); + unsafe { memalign(7, 24) }; + unreachable!(); + } + + #[test] + fn memalign_buff() { + let ret = unsafe { memalign(32, 8) }; + assert_ne!(ret, null_mut()); + assert!(ret as usize & 0x1f == 0); + unsafe { + from_raw_parts_mut(ret as *mut u8, 8) + .iter_mut() + .for_each(|x| *x = 0) + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memchr.rs b/libafl_qemu/librasan/asan/tests/hooks_memchr.rs new file mode 100644 index 0000000000..ee735ad712 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memchr.rs @@ -0,0 +1,65 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::{c_int, c_void}, + ptr::null_mut, + }; + + use asan::{expect_panic, hooks::memchr::memchr}; + + #[test] + fn test_memchr_zero_length() { + let ret = unsafe { memchr(null_mut(), 0, 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_memchr_null_buffer() { + expect_panic(); + unsafe { memchr(null_mut(), 0, 10) }; + unreachable!() + } + + #[test] + fn test_memchr_find_first() { + let data = "abcdefghij".as_bytes(); + let c = 'a' as c_int; + let ret = unsafe { memchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, data.as_ptr() as *mut c_void); + } + + #[test] + fn test_memchr_find_last() { + let data = "abcdefghij".as_bytes(); + let c = 'j' as c_int; + let ret = unsafe { memchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.len() - 1) as *mut c_void + }); + } + + #[test] + fn test_memchr_find_mid() { + let data = "abcdefghij".as_bytes(); + let c = 'e' as c_int; + let ret = unsafe { memchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { data.as_ptr().add(4) as *mut c_void }); + } + + #[test] + fn test_memchr_find_repeated() { + let data = "ababababab".as_bytes(); + let c = 'b' as c_int; + let ret = unsafe { memchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { data.as_ptr().add(1) as *mut c_void }); + } + + #[test] + fn test_memchr_not_found() { + let data = "abcdefghij".as_bytes(); + let c = 'k' as c_int; + let ret = unsafe { memchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memcmp.rs b/libafl_qemu/librasan/asan/tests/hooks_memcmp.rs new file mode 100644 index 0000000000..b220872e38 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memcmp.rs @@ -0,0 +1,70 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_void, ptr::null}; + + use asan::{expect_panic, hooks::memcmp::memcmp}; + + #[test] + fn test_zero_length() { + let ret = unsafe { memcmp(null(), null(), 0) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_null_cx() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memcmp(null(), data.as_ptr() as *const c_void, data.len()) }; + unreachable!(); + } + + #[test] + fn test_null_ct() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memcmp(data.as_ptr() as *const c_void, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_eq() { + let data = [0u8; 10]; + let ret = unsafe { + memcmp( + data.as_ptr() as *const c_void, + data.as_ptr() as *const c_void, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_lt() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + memcmp( + data1.as_ptr() as *const c_void, + data2.as_ptr() as *const c_void, + data1.len(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_gt() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + memcmp( + data1.as_ptr() as *const c_void, + data2.as_ptr() as *const c_void, + data1.len(), + ) + }; + assert!(ret > 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memcpy.rs b/libafl_qemu/librasan/asan/tests/hooks_memcpy.rs new file mode 100644 index 0000000000..d4b8f4dc35 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memcpy.rs @@ -0,0 +1,105 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ + ffi::c_void, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::memcpy::memcpy}; + + #[test] + fn test_memcpy_zero_length() { + let ret = unsafe { memcpy(null_mut(), null_mut(), 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_memcpy_src_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memcpy(null_mut(), data.as_ptr() as *const c_void, data.len()) }; + unreachable!(); + } + + #[test] + fn test_memcpy_dst_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memcpy(data.as_ptr() as *mut c_void, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_adjacent_1() { + let data = [0u8; 20]; + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(10) as *const c_void }; + let ret = unsafe { memcpy(dest, src, 10) }; + assert_eq!(ret, dest); + } + + #[test] + fn test_adjacent_2() { + let data = [0u8; 20]; + let dest = unsafe { data.as_ptr().add(10) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + let ret = unsafe { memcpy(dest, src, 10) }; + assert_eq!(ret, dest); + } + + #[test] + fn test_overlap_1() { + expect_panic(); + let data = [0u8; 20]; + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(9) as *const c_void }; + unsafe { memcpy(dest, src, 10) }; + unreachable!(); + } + + #[test] + fn test_overlap_2() { + expect_panic(); + let data = [0u8; 20]; + let dest = unsafe { data.as_ptr().add(9) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + unsafe { memcpy(dest, src, 10) }; + unreachable!(); + } + + #[test] + fn test_memcpy_full() { + let src = [0xffu8; 10]; + let dest = [0u8; 10]; + let ret = unsafe { + memcpy( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + dest.len(), + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_void); + src.iter() + .zip(dest.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_memcpy_partial() { + let src = [0xffu8; 5]; + let dest = [0u8; 10]; + let ret = unsafe { + memcpy( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + src.len(), + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_void); + src.iter() + .zip(dest.iter().take(src.len())) + .for_each(|(x, y)| assert_eq!(*x, *y)); + dest.iter().skip(5).for_each(|x| assert_eq!(*x, 0)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memmem.rs b/libafl_qemu/librasan/asan/tests/hooks_memmem.rs new file mode 100644 index 0000000000..f373316a06 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memmem.rs @@ -0,0 +1,124 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ + ffi::c_void, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::memmem::memmem}; + + #[test] + fn test_memmem_needle_zero_length() { + let haystack = [0u8; 10]; + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + null(), + 0, + ) + }; + assert_eq!(ret, haystack.as_ptr() as *mut c_void); + } + + #[test] + fn test_memmem_needle_too_long() { + let haystack = [0u8; 10]; + let needle = [0u8; 11]; + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + needle.as_ptr() as *const c_void, + needle.len(), + ) + }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_memmem_haystack_null() { + expect_panic(); + let needle = [0u8; 10]; + unsafe { memmem(null(), 10, needle.as_ptr() as *const c_void, needle.len()) }; + unreachable!(); + } + + #[test] + fn test_memmem_needle_null() { + expect_panic(); + let haystack = [0u8; 10]; + unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + null(), + 10, + ) + }; + unreachable!(); + } + + #[test] + fn test_memmem_found_at_start() { + let haystack = "abcdefghij".as_bytes(); + let needle = "abc".as_bytes(); + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + needle.as_ptr() as *const c_void, + needle.len(), + ) + }; + assert_eq!(ret, haystack.as_ptr() as *mut c_void); + } + + #[test] + fn test_memmem_found_at_end() { + let haystack = "abcdefghij".as_bytes(); + let needle = "hij".as_bytes(); + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + needle.as_ptr() as *const c_void, + needle.len(), + ) + }; + assert_eq!(ret, unsafe { + haystack.as_ptr().add(haystack.len() - needle.len()) as *mut c_void + }); + } + + #[test] + fn test_memmem_found_in_middle() { + let haystack = "abcdefghij".as_bytes(); + let needle = "def".as_bytes(); + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + needle.as_ptr() as *const c_void, + needle.len(), + ) + }; + assert_eq!(ret, unsafe { haystack.as_ptr().add(3) as *mut c_void }); + } + + #[test] + fn test_memmem_not_found() { + let haystack = "abcdefghij".as_bytes(); + let needle = "xyz".as_bytes(); + let ret = unsafe { + memmem( + haystack.as_ptr() as *const c_void, + haystack.len(), + needle.as_ptr() as *const c_void, + needle.len(), + ) + }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memmove.rs b/libafl_qemu/librasan/asan/tests/hooks_memmove.rs new file mode 100644 index 0000000000..483a966f25 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memmove.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ + ffi::c_void, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::memmove::memmove}; + + #[test] + fn test_memmove_zero_length() { + let ret = unsafe { memmove(null_mut(), null_mut(), 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_memmove_src_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memmove(null_mut(), data.as_ptr() as *const c_void, data.len()) }; + unreachable!(); + } + + #[test] + fn test_memmove_dst_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { memmove(data.as_ptr() as *mut c_void, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_adjacent_1() { + let data = [0u8; 20]; + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(10) as *const c_void }; + let ret = unsafe { memmove(dest, src, 10) }; + assert_eq!(ret, dest); + } + + #[test] + fn test_adjacent_2() { + let data = [0u8; 20]; + let dest = unsafe { data.as_ptr().add(10) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + let ret = unsafe { memmove(dest, src, 10) }; + assert_eq!(ret, dest); + } + + #[test] + fn test_overlap_1() { + let mut vec = "abcdefghijklmnopqrst".as_bytes().to_vec(); + let data = vec.as_mut_slice(); + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(9) as *const c_void }; + let ret = unsafe { memmove(dest, src, 10) }; + assert_eq!(ret, dest); + let expected = "jklmnopqrsklmnopqrst".as_bytes(); + data.iter() + .zip(expected.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_overlap_2() { + let mut vec = "abcdefghijklmnopqrst".as_bytes().to_vec(); + let data = vec.as_mut_slice(); + let dest = unsafe { data.as_ptr().add(9) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + let ret = unsafe { memmove(dest, src, 10) }; + assert_eq!(ret, dest); + let expected = "abcdefghiabcdefghijt".as_bytes(); + data.iter() + .zip(expected.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_memmove_full() { + let src = [0xffu8; 10]; + let dest = [0u8; 10]; + let ret = unsafe { + memmove( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + dest.len(), + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_void); + src.iter() + .zip(dest.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_memmove_partial() { + let src = [0xffu8; 5]; + let dest = [0u8; 10]; + let ret = unsafe { + memmove( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + src.len(), + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_void); + src.iter() + .zip(dest.iter().take(src.len())) + .for_each(|(x, y)| assert_eq!(*x, *y)); + dest.iter().skip(5).for_each(|x| assert_eq!(*x, 0)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_mempcpy.rs b/libafl_qemu/librasan/asan/tests/hooks_mempcpy.rs new file mode 100644 index 0000000000..b403a04512 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_mempcpy.rs @@ -0,0 +1,105 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ + ffi::c_void, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::mempcpy::mempcpy}; + + #[test] + fn test_mempcpy_zero_length() { + let ret = unsafe { mempcpy(null_mut(), null_mut(), 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_mempcpy_src_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { mempcpy(null_mut(), data.as_ptr() as *const c_void, data.len()) }; + unreachable!(); + } + + #[test] + fn test_mempcpy_dst_null() { + expect_panic(); + let data = [0u8; 10]; + unsafe { mempcpy(data.as_ptr() as *mut c_void, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_adjacent_1() { + let data = [0u8; 20]; + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(10) as *const c_void }; + let ret = unsafe { mempcpy(dest, src, 10) }; + assert_eq!(ret, unsafe { dest.add(10) }); + } + + #[test] + fn test_adjacent_2() { + let data = [0u8; 20]; + let dest = unsafe { data.as_ptr().add(10) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + let ret = unsafe { mempcpy(dest, src, 10) }; + assert_eq!(ret, unsafe { dest.add(10) }); + } + + #[test] + fn test_overlap_1() { + expect_panic(); + let data = [0u8; 20]; + let dest = data.as_ptr() as *mut c_void; + let src = unsafe { data.as_ptr().add(9) as *const c_void }; + unsafe { mempcpy(dest, src, 10) }; + unreachable!(); + } + + #[test] + fn test_overlap_2() { + expect_panic(); + let data = [0u8; 20]; + let dest = unsafe { data.as_ptr().add(9) as *mut c_void }; + let src = data.as_ptr() as *const c_void; + unsafe { mempcpy(dest, src, 10) }; + unreachable!(); + } + + #[test] + fn test_mempcpy_full() { + let src = [0xffu8; 10]; + let dest = [0u8; 10]; + let ret = unsafe { + mempcpy( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + dest.len(), + ) + }; + assert_eq!(ret, unsafe { dest.as_ptr().add(dest.len()) as *mut c_void }); + src.iter() + .zip(dest.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_mempcpy_partial() { + let src = [0xffu8; 5]; + let dest = [0u8; 10]; + let ret = unsafe { + mempcpy( + dest.as_ptr() as *mut c_void, + src.as_ptr() as *const c_void, + src.len(), + ) + }; + assert_eq!(ret, unsafe { dest.as_ptr().add(src.len()) as *mut c_void }); + src.iter() + .zip(dest.iter().take(src.len())) + .for_each(|(x, y)| assert_eq!(*x, *y)); + dest.iter().skip(5).for_each(|x| assert_eq!(*x, 0)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memrchr.rs b/libafl_qemu/librasan/asan/tests/hooks_memrchr.rs new file mode 100644 index 0000000000..7fe40838f8 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memrchr.rs @@ -0,0 +1,67 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::{c_int, c_void}, + ptr::null_mut, + }; + + use asan::{expect_panic, hooks::memrchr::memrchr}; + + #[test] + fn test_memrchr_zero_length() { + let ret = unsafe { memrchr(null_mut(), 0, 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_memrchr_null_buffer() { + expect_panic(); + unsafe { memrchr(null_mut(), 0, 10) }; + unreachable!() + } + + #[test] + fn test_memrchr_find_first() { + let data = "abcdefghij".as_bytes(); + let c = 'a' as c_int; + let ret = unsafe { memrchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, data.as_ptr() as *mut c_void); + } + + #[test] + fn test_memrchr_find_last() { + let data = "abcdefghij".as_bytes(); + let c = 'j' as c_int; + let ret = unsafe { memrchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.len() - 1) as *mut c_void + }); + } + + #[test] + fn test_memrchr_find_mid() { + let data = "abcdefghij".as_bytes(); + let c = 'e' as c_int; + let ret = unsafe { memrchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { data.as_ptr().add(4) as *mut c_void }); + } + + #[test] + fn test_memrchr_find_repeated() { + let data = "ababababab".as_bytes(); + let c = 'b' as c_int; + let ret = unsafe { memrchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.len() - 1) as *mut c_void + }); + } + + #[test] + fn test_memrchr_not_found() { + let data = "abcdefghij".as_bytes(); + let c = 'k' as c_int; + let ret = unsafe { memrchr(data.as_ptr() as *const c_void, c, data.len()) }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_memset.rs b/libafl_qemu/librasan/asan/tests/hooks_memset.rs new file mode 100644 index 0000000000..a9d41c2be5 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_memset.rs @@ -0,0 +1,45 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::{expect_panic, hooks::memset::memset}; + + #[test] + fn test_memset_zero_length() { + unsafe { memset(null_mut(), 0, 0) }; + } + + #[test] + fn test_memset_null_s() { + expect_panic(); + unsafe { memset(null_mut(), 0, 10) }; + unreachable!(); + } + + #[test] + fn test_memset_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { memset(data.as_ptr() as *mut c_void, 0, data.len()) }; + data.iter().for_each(|x| assert_eq!(*x, 0)); + } + + #[test] + fn test_memset_nonzero_buffer() { + let data = [0u8; 10]; + unsafe { memset(data.as_ptr() as *mut c_void, 0xff, data.len()) }; + data.iter().for_each(|x| assert_eq!(*x, 0xff)); + } + + #[test] + fn test_memset_partial_zero_buffer() { + let data = [0xffu8; 10]; + unsafe { memset(data.as_ptr().add(2) as *mut c_void, 0x88, data.len() - 4) }; + data.iter() + .skip(2) + .take(6) + .for_each(|x| assert_eq!(*x, 0x88)); + data.iter().take(2).for_each(|x| assert_eq!(*x, 0xff)); + data.iter().skip(8).for_each(|x| assert_eq!(*x, 0xff)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_posix_memalign.rs b/libafl_qemu/librasan/asan/tests/hooks_posix_memalign.rs new file mode 100644 index 0000000000..45ed57bed9 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_posix_memalign.rs @@ -0,0 +1,55 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + use std::os::raw::c_void; + + use asan::{expect_panic, hooks::posix_memalign::posix_memalign}; + + #[test] + fn posix_memalign_zero_size() { + let mut memptr = null_mut(); + let ret = unsafe { posix_memalign(&mut memptr as *mut *mut c_void, 8, 0) }; + assert_eq!(ret, 0); + assert_eq!(memptr, null_mut()); + } + + #[test] + fn posix_memalign_size_not_multiple() { + expect_panic(); + let mut memptr = null_mut(); + unsafe { posix_memalign(&mut memptr as *mut *mut c_void, 9, 8) }; + unreachable!(); + } + + #[test] + fn posix_memalign_power_of_two() { + let mut memptr = null_mut(); + let ret = unsafe { posix_memalign(&mut memptr as *mut *mut c_void, 8, 8) }; + assert_eq!(ret, 0); + assert_ne!(memptr, null_mut()); + assert_eq!(memptr as usize & 7, 0); + } + + #[test] + fn posix_memalign_not_power_of_two() { + expect_panic(); + let mut memptr = null_mut(); + unsafe { posix_memalign(&mut memptr as *mut *mut c_void, 7, 24) }; + unreachable!(); + } + + #[test] + fn posix_memalign_buff() { + let mut memptr = null_mut(); + let ret = unsafe { posix_memalign(&mut memptr as *mut *mut c_void, 32, 8) }; + assert_eq!(ret, 0); + assert_ne!(memptr, null_mut()); + assert!(memptr as usize & 0x1f == 0); + unsafe { + from_raw_parts_mut(memptr as *mut u8, 8) + .iter_mut() + .for_each(|x| *x = 0) + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_pvalloc.rs b/libafl_qemu/librasan/asan/tests/hooks_pvalloc.rs new file mode 100644 index 0000000000..9cc5a440a8 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_pvalloc.rs @@ -0,0 +1,31 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::hooks::pvalloc::pvalloc; + + #[test] + fn test_pvalloc_zero() { + let p = unsafe { pvalloc(0) }; + assert_ne!(p, null_mut()); + assert!(p as usize & 0xfff == 0); + unsafe { + from_raw_parts_mut(p as *mut u8, 4096) + .iter_mut() + .for_each(|x| *x = 0) + }; + } + + #[test] + fn test_pvalloc_buff() { + let p = unsafe { pvalloc(4097) }; + assert_ne!(p, null_mut()); + assert!(p as usize & 0xfff == 0); + unsafe { + from_raw_parts_mut(p as *mut u8, 8192) + .iter_mut() + .for_each(|x| *x = 0) + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_read_libc.rs b/libafl_qemu/librasan/asan/tests/hooks_read_libc.rs new file mode 100644 index 0000000000..4e280c7848 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_read_libc.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks", feature = "libc"))] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::{expect_panic, hooks::read::libc::read, size_t}; + + #[test] + fn test_read_invalid_args() { + let fd = 0; + let buf = null_mut(); + let count = 10; + + expect_panic(); + + unsafe { read(fd, buf, count) }; + unreachable!(); + } + + #[test] + fn test_read_valid_args() { + let fd = -1; + let buf = null_mut(); + let count = 0; + + let ret = unsafe { read(fd, buf, count) }; + assert!(ret < 0); + } + + #[test] + fn test_read_valid_args_with_buffer() { + let fd = -1; + let mut buf = [0u8; 10]; + let count = buf.len() as size_t; + + let ret = unsafe { read(fd, buf.as_mut_ptr() as *mut c_void, count) }; + assert!(ret < 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_read_linux.rs b/libafl_qemu/librasan/asan/tests/hooks_read_linux.rs new file mode 100644 index 0000000000..87bae8b6d4 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_read_linux.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks", feature = "linux", not(feature = "libc")))] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::hooks::{expect_panic, read::linux::read, size_t}; + + #[test] + fn test_read_invalid_args() { + let fd = 0; + let buf = null_mut(); + let count = 10; + + expect_panic(); + + unsafe { read(fd, buf, count) }; + unreachable!(); + } + + #[test] + fn test_read_valid_args() { + let fd = -1; + let buf = null_mut(); + let count = 0; + + let ret = unsafe { read(fd, buf, count) }; + assert!(ret < 0); + } + + #[test] + fn test_read_valid_args_with_buffer() { + let fd = -1; + let mut buf = [0u8; 10]; + let count = buf.len() as size_t; + + let ret = unsafe { read(fd, buf.as_mut_ptr() as *mut c_void, count) }; + assert!(ret < 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_realloc.rs b/libafl_qemu/librasan/asan/tests/hooks_realloc.rs new file mode 100644 index 0000000000..0bf57350e1 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_realloc.rs @@ -0,0 +1,70 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::hooks::realloc::realloc; + + #[test] + fn test_realloc_p_null_size_zero() { + let p = unsafe { realloc(null_mut(), 0) }; + assert_eq!(p, null_mut()); + } + + #[test] + fn test_realloc_p_null() { + let p = unsafe { realloc(null_mut(), 10) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 10) + .iter_mut() + .for_each(|x| *x = 0) + }; + } + + #[test] + fn test_realloc_size_zero() { + let p = unsafe { realloc(null_mut(), 10) }; + assert_ne!(p, null_mut()); + let q = unsafe { realloc(p, 0) }; + assert_eq!(q, null_mut()); + } + + #[test] + fn test_realloc_enlarge() { + let p = unsafe { realloc(null_mut(), 10) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 10) + .iter_mut() + .for_each(|x| *x = 0x88) + }; + let q = unsafe { realloc(p, 20) }; + assert_ne!(q, null_mut()); + + unsafe { + from_raw_parts_mut(q as *mut u8, 10) + .iter() + .for_each(|x| assert_eq!(*x, 0x88)); + }; + } + + #[test] + fn test_realloc_shrink() { + let p = unsafe { realloc(null_mut(), 20) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 20) + .iter_mut() + .for_each(|x| *x = 0x88) + }; + let q = unsafe { realloc(p, 10) }; + assert_ne!(q, null_mut()); + + unsafe { + from_raw_parts_mut(q as *mut u8, 10) + .iter() + .for_each(|x| assert_eq!(*x, 0x88)); + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_reallocarray.rs b/libafl_qemu/librasan/asan/tests/hooks_reallocarray.rs new file mode 100644 index 0000000000..62b41d6897 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_reallocarray.rs @@ -0,0 +1,77 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ptr::null_mut, slice::from_raw_parts_mut}; + + use asan::{expect_panic, hooks::reallocarray::reallocarray, size_t}; + + #[test] + fn test_reallocarray_p_null_size_zero() { + let p = unsafe { reallocarray(null_mut(), 0, 0) }; + assert_eq!(p, null_mut()); + } + + #[test] + fn test_reallocarray_p_null() { + let p = unsafe { reallocarray(null_mut(), 10, 10) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 10) + .iter_mut() + .for_each(|x| *x = 0) + }; + } + + #[test] + fn test_reallocarray_size_zero() { + let p = unsafe { reallocarray(null_mut(), 10, 10) }; + assert_ne!(p, null_mut()); + let q = unsafe { reallocarray(p, 0, 0) }; + assert_eq!(q, null_mut()); + } + + #[test] + fn test_reallocarray_size_overflow() { + expect_panic(); + unsafe { reallocarray(null_mut(), size_t::MAX, size_t::MAX) }; + unreachable!(); + } + + #[test] + fn test_reallocarray_enlarge() { + let p = unsafe { reallocarray(null_mut(), 10, 1) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 10) + .iter_mut() + .for_each(|x| *x = 0x88) + }; + let q = unsafe { reallocarray(p, 20, 1) }; + assert_ne!(q, null_mut()); + + unsafe { + from_raw_parts_mut(q as *mut u8, 10) + .iter() + .for_each(|x| assert_eq!(*x, 0x88)); + }; + } + + #[test] + fn test_reallocarray_shrink() { + let p = unsafe { reallocarray(null_mut(), 20, 1) }; + assert_ne!(p, null_mut()); + unsafe { + from_raw_parts_mut(p as *mut u8, 20) + .iter_mut() + .for_each(|x| *x = 0x88) + }; + let q = unsafe { reallocarray(p, 10, 1) }; + assert_ne!(q, null_mut()); + + unsafe { + from_raw_parts_mut(q as *mut u8, 10) + .iter() + .for_each(|x| assert_eq!(*x, 0x88)); + }; + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_stpcpy.rs b/libafl_qemu/librasan/asan/tests/hooks_stpcpy.rs new file mode 100644 index 0000000000..3458ae183b --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_stpcpy.rs @@ -0,0 +1,57 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::{ffi::c_char, ptr::null_mut}; + + use asan::{expect_panic, hooks::stpcpy::stpcpy}; + + #[test] + fn test_stpcpy_dst_null() { + let src = [0u8; 10]; + expect_panic(); + unsafe { stpcpy(null_mut(), src.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_stpcpy_src_null() { + let dst = [0u8; 10]; + expect_panic(); + unsafe { stpcpy(dst.as_ptr() as *mut c_char, null_mut()) }; + unreachable!(); + } + + #[test] + fn test_stpcpy_full() { + let src = [0xffu8; 10]; + let dst = [0u8; 10]; + let ret = unsafe { stpcpy(dst.as_ptr() as *mut c_char, src.as_ptr() as *const c_char) }; + assert_eq!(ret, unsafe { dst.as_ptr().add(src.len()) as *mut c_char }); + } + + #[test] + fn test_stpcpy_partial() { + let mut vec = c"abcdefghijklmnopqrstuvwxyz".to_bytes().to_vec(); + let dst = vec.as_mut_slice(); + let src = c"uvwxyz".to_bytes(); + let ret = unsafe { + stpcpy( + dst.as_ptr().add(2) as *mut c_char, + src.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, unsafe { + dst.as_ptr().add(2).add(src.len()) as *mut c_char + }); + let expected = c"abuvwxyz".to_bytes(); + expected + .iter() + .zip(dst.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + let remaining = c"jklmnopqrstuvwxyz".to_bytes(); + remaining + .iter() + .zip(dst.iter().skip(2).skip(src.len()).skip(1)) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strcasecmp.rs b/libafl_qemu/librasan/asan/tests/hooks_strcasecmp.rs new file mode 100644 index 0000000000..bcd2b9b3a5 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strcasecmp.rs @@ -0,0 +1,151 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strcasecmp::strcasecmp}; + + #[test] + fn test_strcasecmp_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcasecmp(null(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strcasecmp_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcasecmp(data.as_ptr() as *const c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strcasecmp_eq() { + let data = [1u8; 10]; + let ret = unsafe { + strcasecmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcasecmp_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { + strcasecmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcasecmp_zero_length_s1() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcasecmp_zero_length_s2() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcasecmp_eq_string() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcasecmp_s1_shorter() { + let data1 = c"abcdefghi"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcasecmp_s1_longer() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghi"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcasecmp_s1_less_than() { + let data1 = c"abcdefghii"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcasecmp_s1_greater_than() { + let data1 = c"abcdefghik"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcasecmp_case_ignored() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strcasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strcasestr.rs b/libafl_qemu/librasan/asan/tests/hooks_strcasestr.rs new file mode 100644 index 0000000000..fa40d4101a --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strcasestr.rs @@ -0,0 +1,95 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strcasestr::strcasestr}; + + #[test] + fn test_strcasestr_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcasestr(null(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strcasestr_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcasestr(data.as_ptr() as *const c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strcasestr_ct_too_long() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghijk"; + let ret = unsafe { + strcasestr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_strcasestr_found_at_start() { + let data1 = c"abcdefghijk"; + let data2 = c"abc"; + let ret = unsafe { + strcasestr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, data1.as_ptr() as *mut c_char); + } + + #[test] + fn test_strcasestr_found_at_end() { + let data1 = c"abcdefghijk"; + let data2 = c"ijk"; + let ret = unsafe { + strcasestr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, unsafe { + data1 + .as_ptr() + .add(data1.count_bytes() - data2.count_bytes()) as *mut c_char + }); + } + + #[test] + fn test_strcasestr_found_in_middle() { + let data1 = c"abcdefghijk"; + let data2 = c"def"; + let ret = unsafe { + strcasestr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, unsafe { data1.as_ptr().add(3) as *mut c_char }); + } + + #[test] + fn test_strcasestr_ignores_case() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strcasestr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, data1.as_ptr() as *mut c_char); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strcat.rs b/libafl_qemu/librasan/asan/tests/hooks_strcat.rs new file mode 100644 index 0000000000..a2e109f851 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strcat.rs @@ -0,0 +1,48 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strcat::strcat}; + + #[test] + fn test_strcat_null_s() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcat(null_mut(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strcat_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcat(data.as_ptr() as *mut c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strcat_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { strcat(data.as_ptr() as *mut c_char, data.as_ptr() as *const c_char) }; + assert_eq!(ret, data.as_ptr() as *mut c_char); + } + + #[test] + fn test_strcat_appends() { + let mut vec = "abcde\0zzzzzzzzzzzzzzz".as_bytes().to_vec(); + let s = vec.as_mut_slice(); + let ct = c"fghij"; + let ret = unsafe { strcat(s.as_ptr() as *mut c_char, ct.as_ptr() as *const c_char) }; + assert_eq!(ret, s.as_ptr() as *mut c_char); + let expected = c"abcdefghij"; + expected + .to_bytes() + .iter() + .zip(s.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strchr.rs b/libafl_qemu/librasan/asan/tests/hooks_strchr.rs new file mode 100644 index 0000000000..6244fc60a5 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strchr.rs @@ -0,0 +1,66 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::{c_char, c_int}, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strchr::strchr}; + + #[test] + fn test_strchr_zero_length() { + let data = c""; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, 0x88) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_strchr_null_buffer() { + expect_panic(); + unsafe { strchr(null(), 0x88) }; + unreachable!() + } + + #[test] + fn test_strchr_find_first() { + let data = c"abcdefghij"; + let c = 'a' as c_int; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, data.as_ptr() as *mut c_char); + } + + #[test] + fn test_strchr_find_last() { + let data = c"abcdefghij"; + let c = 'j' as c_int; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.count_bytes() - 1) as *mut c_char + }); + } + + #[test] + fn test_strchr_find_mid() { + let data = c"abcdefghij"; + let c = 'e' as c_int; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { data.as_ptr().add(4) as *mut c_char }); + } + + #[test] + fn test_strchr_find_repeated() { + let data = c"ababababab"; + let c = 'b' as c_int; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { data.as_ptr().add(1) as *mut c_char }); + } + + #[test] + fn test_strchr_not_found() { + let data = c"abcdefghij"; + let c = 'k' as c_int; + let ret = unsafe { strchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strcmp.rs b/libafl_qemu/librasan/asan/tests/hooks_strcmp.rs new file mode 100644 index 0000000000..ffb0713695 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strcmp.rs @@ -0,0 +1,151 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strcmp::strcmp}; + + #[test] + fn test_strcmp_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcmp(null(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strcmp_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcmp(data.as_ptr() as *const c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strcmp_eq() { + let data = [1u8; 10]; + let ret = unsafe { + strcmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcmp_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { + strcmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcmp_zero_length_s1() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcmp_zero_length_s2() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcmp_eq_string() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strcmp_s1_shorter() { + let data1 = c"abcdefghi"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcmp_s1_longer() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghi"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcmp_s1_less_than() { + let data1 = c"abcdefghii"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strcmp_s1_greater_than() { + let data1 = c"abcdefghik"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strcmp_case_not_ignored() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strcmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert!(ret > 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strcpy.rs b/libafl_qemu/librasan/asan/tests/hooks_strcpy.rs new file mode 100644 index 0000000000..d53cb16caf --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strcpy.rs @@ -0,0 +1,46 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strcpy::strcpy}; + + #[test] + fn test_strcpy_null_s() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcpy(null_mut(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strcpy_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strcpy(data.as_ptr() as *mut c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strcpy_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { strcpy(data.as_ptr() as *mut c_char, data.as_ptr() as *const c_char) }; + assert_eq!(ret, data.as_ptr() as *mut c_char); + } + + #[test] + fn test_strcpy_copies() { + let mut vec = c"abcdefghij".to_bytes().to_vec(); + let s = vec.as_mut_slice(); + let ct = c"klmnop"; + let ret = unsafe { strcpy(s.as_ptr() as *mut c_char, ct.as_ptr() as *const c_char) }; + assert_eq!(ret, s.as_ptr() as *mut c_char); + ct.to_bytes() + .iter() + .zip(s.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strdup.rs b/libafl_qemu/librasan/asan/tests/hooks_strdup.rs new file mode 100644 index 0000000000..10e5d26de1 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strdup.rs @@ -0,0 +1,37 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + slice::from_raw_parts, + }; + + use asan::{expect_panic, hooks::strdup::strdup}; + + #[test] + fn test_strdup_cs_null() { + expect_panic(); + unsafe { strdup(null() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strdup_cs_empty() { + let data = c""; + let ret = unsafe { strdup(data.as_ptr() as *const c_char) }; + assert_ne!(ret, null_mut()); + assert_eq!(unsafe { *ret }, 0); + } + + #[test] + fn test_strdup_full() { + let data = c"abcdefghij"; + let ret = unsafe { strdup(data.as_ptr() as *const c_char) }; + assert_ne!(ret, null_mut()); + data.to_bytes() + .iter() + .zip(unsafe { from_raw_parts(ret as *const u8, data.count_bytes()) }) + .for_each(|(x, y)| assert_eq!(x, y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strlen.rs b/libafl_qemu/librasan/asan/tests/hooks_strlen.rs new file mode 100644 index 0000000000..9ac6d627cf --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strlen.rs @@ -0,0 +1,28 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strlen::strlen}; + + #[test] + fn test_strlen_cs_null() { + expect_panic(); + unsafe { strlen(null() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strlen_cs_empty() { + let data = c""; + let ret = unsafe { strlen(data.as_ptr() as *const c_char) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strlen_full() { + let data = c"abcdefghij"; + let ret = unsafe { strlen(data.as_ptr() as *const c_char) }; + assert_eq!(ret, 10); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strncasecmp.rs b/libafl_qemu/librasan/asan/tests/hooks_strncasecmp.rs new file mode 100644 index 0000000000..4cc79c6602 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strncasecmp.rs @@ -0,0 +1,182 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strncasecmp::strncasecmp}; + + #[test] + fn test_strncasecmp_zero_length() { + expect_panic(); + let ret = unsafe { strncasecmp(null(), null(), 0) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncasecmp_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncasecmp(null(), data.as_ptr() as *const c_char, data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncasecmp_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncasecmp(data.as_ptr() as *const c_char, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncasecmp_eq() { + let data = [1u8; 10]; + let ret = unsafe { + strncasecmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncasecmp_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { + strncasecmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncasecmp_zero_length_s1() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.len(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncasecmp_zero_length_s2() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.len(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncasecmp_eq_string() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncasecmp_s1_shorter() { + let data1 = c"abcdefghi"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data2.count_bytes(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncasecmp_s1_longer() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghi"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncasecmp_s1_less_than() { + let data1 = c"abcdefghii"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncasecmp_s1_greater_than() { + let data1 = c"abcdefghik"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncasecmp_case_ignored() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncasecmp_differ_after_length() { + let data1 = c"abcdefghijXYZ"; + let data2 = c"abcdefghijUVW"; + let ret = unsafe { + strncasecmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes() - 3, + ) + }; + assert_eq!(ret, 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strncmp.rs b/libafl_qemu/librasan/asan/tests/hooks_strncmp.rs new file mode 100644 index 0000000000..b595f41024 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strncmp.rs @@ -0,0 +1,182 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strncmp::strncmp}; + + #[test] + fn test_strncmp_zero_length() { + expect_panic(); + let ret = unsafe { strncmp(null(), null(), 0) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncmp_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncmp(null(), data.as_ptr() as *const c_char, data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncmp_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncmp(data.as_ptr() as *const c_char, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncmp_eq() { + let data = [1u8; 10]; + let ret = unsafe { + strncmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncmp_zero_length_both() { + let data = [0u8; 10]; + let ret = unsafe { + strncmp( + data.as_ptr() as *const c_char, + data.as_ptr() as *const c_char, + data.len(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncmp_zero_length_s1() { + let data1 = [0u8; 10]; + let data2 = [1u8; 10]; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.len(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncmp_zero_length_s2() { + let data1 = [1u8; 10]; + let data2 = [0u8; 10]; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.len(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncmp_eq_string() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strncmp_s1_shorter() { + let data1 = c"abcdefghi"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data2.count_bytes(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncmp_s1_longer() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghi"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncmp_s1_less_than() { + let data1 = c"abcdefghii"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_strncmp_s1_greater_than() { + let data1 = c"abcdefghik"; + let data2 = c"abcdefghij"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncmp_case_not_ignored() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes(), + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_strncmp_differ_after_length() { + let data1 = c"abcdefghijXYZ"; + let data2 = c"abcdefghijUVW"; + let ret = unsafe { + strncmp( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + data1.count_bytes() - 3, + ) + }; + assert_eq!(ret, 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strncpy.rs b/libafl_qemu/librasan/asan/tests/hooks_strncpy.rs new file mode 100644 index 0000000000..6ea0327ed1 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strncpy.rs @@ -0,0 +1,75 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + slice::from_raw_parts, + }; + + use asan::{expect_panic, hooks::strncpy::strncpy}; + + #[test] + fn test_strncpy_zero_length() { + let ret = unsafe { strncpy(null_mut(), null(), 0) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_strncpy_null_dest() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncpy(null_mut(), data.as_ptr() as *const c_char, data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncpy_null_src() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strncpy(data.as_ptr() as *mut c_char, null(), data.len()) }; + unreachable!(); + } + + #[test] + fn test_strncpy_full() { + let mut vec = "abcde\0zzzzzzzzzzzzzzz".as_bytes().to_vec(); + let dest = vec.as_mut_slice(); + let src = c"fghij"; + let ret = unsafe { + strncpy( + dest.as_ptr() as *mut c_char, + src.as_ptr() as *const c_char, + 5, + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_char); + let expected = "fghij\0zzzzzzzzzzzzzzz"; + expected + .as_bytes() + .iter() + .zip(unsafe { from_raw_parts(dest.as_ptr() as *const u8, dest.len()) }) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } + + #[test] + fn test_strncpy_partial() { + let mut vec = "abcde\0zzzzzzzzzzzzzzz".as_bytes().to_vec(); + let dest = vec.as_mut_slice(); + let src = c"fghij"; + let ret = unsafe { + strncpy( + dest.as_ptr() as *mut c_char, + src.as_ptr() as *const c_char, + 3, + ) + }; + assert_eq!(ret, dest.as_ptr() as *mut c_char); + let expected = "fghde\0zzzzzzzzzzzzzzz"; + expected + .as_bytes() + .iter() + .zip(unsafe { from_raw_parts(dest.as_ptr() as *const u8, dest.len()) }) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strndup.rs b/libafl_qemu/librasan/asan/tests/hooks_strndup.rs new file mode 100644 index 0000000000..a74f1d016f --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strndup.rs @@ -0,0 +1,57 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + slice::from_raw_parts, + }; + + use asan::{expect_panic, hooks::strndup::strndup}; + + #[test] + fn test_strndup_cs_null_zero_length() { + let ret = unsafe { strndup(null() as *const c_char, 0x0) }; + assert_ne!(ret, null_mut()); + assert_eq!(unsafe { *ret }, 0); + } + + #[test] + fn test_strndup_cs_null() { + expect_panic(); + unsafe { strndup(null() as *const c_char, 0x10) }; + unreachable!(); + } + + #[test] + fn test_strndup_cs_empty() { + let data = c""; + let ret = unsafe { strndup(data.as_ptr() as *const c_char, 0x0) }; + assert_ne!(ret, null_mut()); + assert_eq!(unsafe { *ret }, 0); + } + + #[test] + fn test_strndup_full() { + let data = c"abcdefghij"; + let ret = unsafe { strndup(data.as_ptr() as *const c_char, data.count_bytes()) }; + assert_ne!(ret, null_mut()); + data.to_bytes() + .iter() + .zip(unsafe { from_raw_parts(ret as *const u8, data.count_bytes()) }) + .for_each(|(x, y)| assert_eq!(x, y)); + } + + #[test] + fn test_strndup_partial() { + let data = c"abcdefghij"; + let ret = unsafe { strndup(data.as_ptr() as *const c_char, 4) }; + assert_ne!(ret, null_mut()); + let expected = c"abcd"; + expected + .to_bytes() + .iter() + .zip(unsafe { from_raw_parts(ret as *const u8, expected.count_bytes()) }) + .for_each(|(x, y)| assert_eq!(x, y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strnlen.rs b/libafl_qemu/librasan/asan/tests/hooks_strnlen.rs new file mode 100644 index 0000000000..637b4603ca --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strnlen.rs @@ -0,0 +1,41 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ffi::c_char, ptr::null}; + + use asan::{expect_panic, hooks::strnlen::strnlen}; + + #[test] + fn test_strnlen_zero_length() { + let ret = unsafe { strnlen(null() as *const c_char, 0) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strnlen_cs_null() { + expect_panic(); + unsafe { strnlen(null() as *const c_char, 10) }; + unreachable!(); + } + + #[test] + fn test_strnlen_cs_empty() { + let data = c""; + let ret = unsafe { strnlen(data.as_ptr() as *const c_char, 10) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_strnlen_full() { + let data = c"abcdefghij"; + let ret = unsafe { strnlen(data.as_ptr() as *const c_char, data.count_bytes()) }; + assert_eq!(ret, 10); + } + + #[test] + fn test_strnlen_partial() { + let data = c"abcdefghij"; + let ret = unsafe { strnlen(data.as_ptr() as *const c_char, 5) }; + assert_eq!(ret, 5); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strrchr.rs b/libafl_qemu/librasan/asan/tests/hooks_strrchr.rs new file mode 100644 index 0000000000..f09fa88d3f --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strrchr.rs @@ -0,0 +1,68 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::{c_char, c_int}, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strrchr::strrchr}; + + #[test] + fn test_strrchr_zero_length() { + let data = c""; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, 0x88) }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_strrchr_null_buffer() { + expect_panic(); + unsafe { strrchr(null(), 0x88) }; + unreachable!() + } + + #[test] + fn test_strrchr_find_first() { + let data = c"abcdefghij"; + let c = 'a' as c_int; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, data.as_ptr() as *mut c_char); + } + + #[test] + fn test_strrchr_find_last() { + let data = c"abcdefghij"; + let c = 'j' as c_int; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.count_bytes() - 1) as *mut c_char + }); + } + + #[test] + fn test_strrchr_find_mid() { + let data = c"abcdefghij"; + let c = 'e' as c_int; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { data.as_ptr().add(4) as *mut c_char }); + } + + #[test] + fn test_strrchr_find_repeated() { + let data = c"ababababab"; + let c = 'b' as c_int; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, unsafe { + data.as_ptr().add(data.count_bytes() - 1) as *mut c_char + }); + } + + #[test] + fn test_strrchr_not_found() { + let data = c"abcdefghij"; + let c = 'k' as c_int; + let ret = unsafe { strrchr(data.as_ptr() as *const c_char, c) }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_strstr.rs b/libafl_qemu/librasan/asan/tests/hooks_strstr.rs new file mode 100644 index 0000000000..5dfd1e44e1 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_strstr.rs @@ -0,0 +1,95 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::{ + ffi::c_char, + ptr::{null, null_mut}, + }; + + use asan::{expect_panic, hooks::strstr::strstr}; + + #[test] + fn test_strstr_null_s1() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strstr(null(), data.as_ptr() as *const c_char) }; + unreachable!(); + } + + #[test] + fn test_strstr_null_s2() { + expect_panic(); + let data = [0u8; 10]; + unsafe { strstr(data.as_ptr() as *const c_char, null()) }; + unreachable!(); + } + + #[test] + fn test_strstr_ct_too_long() { + let data1 = c"abcdefghij"; + let data2 = c"abcdefghijk"; + let ret = unsafe { + strstr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, null_mut()); + } + + #[test] + fn test_strstr_found_at_start() { + let data1 = c"abcdefghijk"; + let data2 = c"abc"; + let ret = unsafe { + strstr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, data1.as_ptr() as *mut c_char); + } + + #[test] + fn test_strstr_found_at_end() { + let data1 = c"abcdefghijk"; + let data2 = c"ijk"; + let ret = unsafe { + strstr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, unsafe { + data1 + .as_ptr() + .add(data1.count_bytes() - data2.count_bytes()) as *mut c_char + }); + } + + #[test] + fn test_strstr_found_in_middle() { + let data1 = c"abcdefghijk"; + let data2 = c"def"; + let ret = unsafe { + strstr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, unsafe { data1.as_ptr().add(3) as *mut c_char }); + } + + #[test] + fn test_strstr_case_not_ignored() { + let data1 = c"abcdefghijklmnopqrstuvwxyz"; + let data2 = c"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let ret = unsafe { + strstr( + data1.as_ptr() as *const c_char, + data2.as_ptr() as *const c_char, + ) + }; + assert_eq!(ret, null_mut()); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_valloc.rs b/libafl_qemu/librasan/asan/tests/hooks_valloc.rs new file mode 100644 index 0000000000..3c910ffdc9 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_valloc.rs @@ -0,0 +1,20 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks"))] +mod tests { + use core::ptr::null_mut; + + use asan::hooks::valloc::valloc; + + #[test] + fn test_valloc_zero() { + let p = unsafe { valloc(0) }; + assert_eq!(p, null_mut()); + } + + #[test] + fn test_valloc_buff() { + let p = unsafe { valloc(10) }; + assert_ne!(p, null_mut()); + assert!(p as usize & 0xfff == 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_wcscmp.rs b/libafl_qemu/librasan/asan/tests/hooks_wcscmp.rs new file mode 100644 index 0000000000..b9fffe3dda --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_wcscmp.rs @@ -0,0 +1,152 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::ptr::null; + + use asan::{expect_panic, hooks::wcscmp::wcscmp, wchar_t}; + use widestring::widecstr; + + #[test] + fn test_wcscmp_null_s1() { + expect_panic(); + let data = [0u16; 10]; + unsafe { wcscmp(null(), data.as_ptr() as *const wchar_t) }; + unreachable!(); + } + + #[test] + fn test_wcscmp_null_s2() { + expect_panic(); + let data = [0u16; 10]; + unsafe { wcscmp(data.as_ptr() as *const wchar_t, null()) }; + unreachable!(); + } + + #[test] + fn test_wcscmp_eq() { + let data = [1u16; 10]; + let ret = unsafe { + wcscmp( + data.as_ptr() as *const wchar_t, + data.as_ptr() as *const wchar_t, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_wcscmp_zero_length_both() { + let data = [0u16; 10]; + let ret = unsafe { + wcscmp( + data.as_ptr() as *const wchar_t, + data.as_ptr() as *const wchar_t, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_wcscmp_zero_length_s1() { + let data1 = [0u16; 10]; + let data2 = [1u16; 10]; + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_wcscmp_zero_length_s2() { + let data1 = [1u16; 10]; + let data2 = [0u16; 10]; + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_wcscmp_eq_string() { + let data1 = widecstr!("abcdefghij"); + let data2 = widecstr!("abcdefghij"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert_eq!(ret, 0); + } + + #[test] + fn test_wcscmp_s1_shorter() { + let data1 = widecstr!("abcdefghi"); + let data2 = widecstr!("abcdefghij"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_wcscmp_s1_longer() { + let data1 = widecstr!("abcdefghij"); + let data2 = widecstr!("abcdefghi"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_wcscmp_s1_less_than() { + let data1 = widecstr!("abcdefghii"); + let data2 = widecstr!("abcdefghij"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret < 0); + } + + #[test] + fn test_wcscmp_s1_greater_than() { + let data1 = widecstr!("abcdefghik"); + let data2 = widecstr!("abcdefghij"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret > 0); + } + + #[test] + fn test_wcscmp_case_not_ignored() { + let data1 = widecstr!("abcdefghijklmnopqrstuvwxyz"); + let data2 = widecstr!("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + let ret = unsafe { + wcscmp( + data1.as_ptr() as *const wchar_t, + data2.as_ptr() as *const wchar_t, + ) + }; + assert!(ret > 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_wcscpy.rs b/libafl_qemu/librasan/asan/tests/hooks_wcscpy.rs new file mode 100644 index 0000000000..289a368200 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_wcscpy.rs @@ -0,0 +1,49 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::ptr::{null, null_mut}; + + use asan::{expect_panic, hooks::wcscpy::wcscpy, wchar_t}; + use widestring::widecstr; + + #[test] + fn test_wcscpy_null_s() { + expect_panic(); + let data = [0u16; 10]; + unsafe { wcscpy(null_mut(), data.as_ptr() as *const wchar_t) }; + unreachable!(); + } + + #[test] + fn test_wcscpy_null_s2() { + expect_panic(); + let data = [0u16; 10]; + unsafe { wcscpy(data.as_ptr() as *mut wchar_t, null()) }; + unreachable!(); + } + + #[test] + fn test_wcscpy_zero_length_both() { + let data = [0u16; 10]; + let ret = unsafe { + wcscpy( + data.as_ptr() as *mut wchar_t, + data.as_ptr() as *const wchar_t, + ) + }; + assert_eq!(ret, data.as_ptr() as *mut wchar_t); + } + + #[test] + fn test_wcscpy_copies() { + let mut vec = widecstr!("abcdefghij").as_slice().to_vec(); + let s = vec.as_mut_slice(); + let ct = widecstr!("klmnop"); + let ret = unsafe { wcscpy(s.as_ptr() as *mut wchar_t, ct.as_ptr() as *const wchar_t) }; + assert_eq!(ret, s.as_ptr() as *mut wchar_t); + ct.as_slice() + .iter() + .zip(s.iter()) + .for_each(|(x, y)| assert_eq!(*x, *y)); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_wcslen.rs b/libafl_qemu/librasan/asan/tests/hooks_wcslen.rs new file mode 100644 index 0000000000..0aa8150788 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_wcslen.rs @@ -0,0 +1,29 @@ +#[cfg(test)] +#[cfg(feature = "hooks")] +mod tests { + use core::ptr::null; + + use asan::{expect_panic, hooks::wcslen::wcslen, wchar_t}; + use widestring::widecstr; + + #[test] + fn test_wcslen_cs_null() { + expect_panic(); + unsafe { wcslen(null() as *const wchar_t) }; + unreachable!(); + } + + #[test] + fn test_wcslen_cs_empty() { + let data = widecstr!(""); + let ret = unsafe { wcslen(data.as_ptr() as *const wchar_t) }; + assert_eq!(ret, 0); + } + + #[test] + fn test_wcslen_full() { + let data = widecstr!("abcdefghij"); + let ret = unsafe { wcslen(data.as_ptr() as *const wchar_t) }; + assert_eq!(ret, 10); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_write_libc.rs b/libafl_qemu/librasan/asan/tests/hooks_write_libc.rs new file mode 100644 index 0000000000..a77d53ae11 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_write_libc.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks", feature = "libc"))] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::{expect_panic, hooks::write::libc::write, size_t}; + + #[test] + fn test_read_invalid_args() { + let fd = 0; + let buf = null_mut(); + let count = 10; + + expect_panic(); + + unsafe { write(fd, buf, count) }; + unreachable!(); + } + + #[test] + fn test_read_valid_args() { + let fd = -1; + let buf = null_mut(); + let count = 0; + + let ret = unsafe { write(fd, buf, count) }; + assert!(ret < 0); + } + + #[test] + fn test_read_valid_args_with_buffer() { + let fd = -1; + let mut buf = [0u8; 10]; + let count = buf.len() as size_t; + + let ret = unsafe { write(fd, buf.as_mut_ptr() as *mut c_void, count) }; + assert!(ret < 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/hooks_write_linux.rs b/libafl_qemu/librasan/asan/tests/hooks_write_linux.rs new file mode 100644 index 0000000000..d9524767f0 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/hooks_write_linux.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +#[cfg(all(feature = "hooks", feature = "linux", not(feature = "libc")))] +mod tests { + use core::{ffi::c_void, ptr::null_mut}; + + use asan::hooks::{expect_panic, size_t, write::linux::write}; + + #[test] + fn test_read_invalid_args() { + let fd = 0; + let buf = null_mut(); + let count = 10; + + expect_panic(); + + unsafe { write(fd, buf, count) }; + unreachable!(); + } + + #[test] + fn test_read_valid_args() { + let fd = -1; + let buf = null_mut(); + let count = 0; + + let ret = unsafe { write(fd, buf, count) }; + assert!(ret < 0); + } + + #[test] + fn test_read_valid_args_with_buffer() { + let fd = -1; + let mut buf = [0u8; 10]; + let count = buf.len() as size_t; + + let ret = unsafe { write(fd, buf.as_mut_ptr() as *mut c_void, count) }; + assert!(ret < 0); + } +} diff --git a/libafl_qemu/librasan/asan/tests/host.rs b/libafl_qemu/librasan/asan/tests/host.rs new file mode 100644 index 0000000000..d6ca5440a2 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/host.rs @@ -0,0 +1,10 @@ +#[cfg(test)] +#[cfg(feature = "host")] +mod tests { + use asan::host::linux::LinuxHost; + + #[test] + fn test_sysno() { + assert_eq!(LinuxHost::sysno() as u32, 0xa2a4); + } +} diff --git a/libafl_qemu/librasan/asan/tests/libc_map_reader.rs b/libafl_qemu/librasan/asan/tests/libc_map_reader.rs new file mode 100644 index 0000000000..8a957a237d --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/libc_map_reader.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(feature = "libc")] +mod tests { + use asan::{ + maps::{MapReader, entry::MapEntry, iterator::MapIterator, libc::LibcMapReader}, + mmap::MmapProt, + symbols::{ + SymbolsLookupStr, + dlsym::{DlSymSymbols, LookupTypeNext}, + }, + }; + use itertools::Itertools; + + type Syms = DlSymSymbols; + + #[test] + fn test_libc_map_reader() { + let reader = LibcMapReader::::new().unwrap(); + let iterator = MapIterator::new(reader); + let maps = iterator.collect::>(); + for entry in &maps { + println!("{:?}", entry); + } + let memcpy_addr = Syms::lookup_str(c"memcpy").unwrap(); + assert_ne!(maps.len(), 0); + assert!(maps.iter().any(|e| e.contains(memcpy_addr))); + let entry = maps + .iter() + .filter(|e| e.contains(memcpy_addr)) + .exactly_one() + .unwrap(); + assert!(entry.path().ends_with("libc.so.6")); + assert_eq!(entry.prot() & MmapProt::EXEC, MmapProt::EXEC) + } +} diff --git a/libafl_qemu/librasan/asan/tests/linux_map_reader.rs b/libafl_qemu/librasan/asan/tests/linux_map_reader.rs new file mode 100644 index 0000000000..f3e6d3b420 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/linux_map_reader.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +#[cfg(feature = "libc")] +mod tests { + use asan::{ + maps::{MapReader, entry::MapEntry, iterator::MapIterator, linux::LinuxMapReader}, + mmap::MmapProt, + symbols::{ + SymbolsLookupStr, + dlsym::{DlSymSymbols, LookupTypeNext}, + }, + }; + use itertools::Itertools; + + type Syms = DlSymSymbols; + + #[test] + fn test_linux_map_reader() { + let reader = LinuxMapReader::new().unwrap(); + let iterator = MapIterator::new(reader); + let maps = iterator.collect::>(); + for entry in &maps { + println!("{:?}", entry); + } + let memcpy_addr = Syms::lookup_str(c"memcpy").unwrap(); + assert_ne!(maps.len(), 0); + assert!(maps.iter().any(|e| e.contains(memcpy_addr))); + let entry = maps + .iter() + .filter(|e| e.contains(memcpy_addr)) + .exactly_one() + .unwrap(); + assert!(entry.path().ends_with("libc.so.6")); + assert_eq!(entry.prot() & MmapProt::EXEC, MmapProt::EXEC) + } +} diff --git a/libafl_qemu/librasan/asan/tests/patch_raw.rs b/libafl_qemu/librasan/asan/tests/patch_raw.rs new file mode 100644 index 0000000000..18a2a51f6a --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/patch_raw.rs @@ -0,0 +1,57 @@ +#[cfg(test)] +#[cfg(feature = "libc")] +mod tests { + use asan::{ + GuestAddr, + mmap::{Mmap, MmapProt, linux::LinuxMmap}, + patch::{Patch, raw::RawPatch}, + }; + use log::info; + + #[unsafe(no_mangle)] + extern "C" fn test1(a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize) -> usize { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + return 0xdeadface; + } + + #[unsafe(no_mangle)] + extern "C" fn test2(a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize) -> usize { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + return 0xd00df00d; + } + + #[test] + fn test_patch() { + let ret1 = test1(1, 2, 3, 4, 5, 6); + assert_eq!(ret1, 0xdeadface); + + let ret2 = test2(1, 2, 3, 4, 5, 6); + assert_eq!(ret2, 0xd00df00d); + + let ptest1 = test1 as *const () as GuestAddr; + let ptest2 = test2 as *const () as GuestAddr; + info!("pfn: {:#x}", ptest1); + let aligned_pfn = ptest1 & !0xfff; + info!("aligned_pfn: {:#x}", aligned_pfn); + LinuxMmap::protect( + aligned_pfn, + 0x4096, + MmapProt::READ | MmapProt::WRITE | MmapProt::EXEC, + ) + .unwrap(); + + RawPatch::patch(ptest1, ptest2).unwrap(); + let ret = test1(1, 2, 3, 4, 5, 6); + assert_eq!(ret, 0xd00df00d); + } +} diff --git a/libafl_qemu/librasan/asan/tests/symbols_dlsym.rs b/libafl_qemu/librasan/asan/tests/symbols_dlsym.rs new file mode 100644 index 0000000000..0a7f631e25 --- /dev/null +++ b/libafl_qemu/librasan/asan/tests/symbols_dlsym.rs @@ -0,0 +1,61 @@ +#[cfg(test)] +#[cfg(feature = "libc")] +mod tests { + use core::ffi::{CStr, c_int, c_void}; + + use asan::{ + GuestAddr, + symbols::{ + Function, SymbolsLookupStr, + dlsym::{DlSymSymbols, LookupTypeDefault}, + }, + }; + use libc::{off_t, size_t}; + + #[derive(Debug)] + struct FunctionMmap; + + impl Function for FunctionMmap { + type Func = + unsafe extern "C" fn(*mut c_void, size_t, c_int, c_int, c_int, off_t) -> *mut c_void; + const NAME: &'static CStr = c"mmap"; + } + + #[derive(Debug)] + struct FunctionMunmap; + + impl Function for FunctionMunmap { + type Func = unsafe extern "C" fn(*mut c_void, size_t) -> c_int; + const NAME: &'static CStr = c"munmap"; + } + + type DLSYM = DlSymSymbols; + + #[test] + fn test_dlsym() { + use asan::symbols::FunctionPointer; + + let mmap = DLSYM::lookup_str(c"mmap").unwrap(); + let mmap2 = DLSYM::lookup_str(c"mmap").unwrap(); + assert_eq!(mmap, mmap2); + let fnmmap = FunctionMmap::as_ptr(mmap).unwrap(); + let mapping = unsafe { + fnmmap( + core::ptr::null_mut(), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE, + -1, + 0, + ) + }; + let addr = mapping as GuestAddr; + assert!(addr & 0xfff == 0); + let munmap = DLSYM::lookup_str(c"munmap").unwrap(); + let munmap2 = DLSYM::lookup_str(c"munmap").unwrap(); + assert_eq!(munmap, munmap2); + let fnmunmap = FunctionMunmap::as_ptr(munmap).unwrap(); + let ret = unsafe { fnmunmap(mapping, 4096) }; + assert!(ret == 0); + } +} diff --git a/libafl_qemu/librasan/dummy_libc/Cargo.toml b/libafl_qemu/librasan/dummy_libc/Cargo.toml new file mode 100644 index 0000000000..e19e788b28 --- /dev/null +++ b/libafl_qemu/librasan/dummy_libc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dummy_libc" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +crate-type = ["staticlib"] + +[features] +default = [] +test = [] + +[dependencies] diff --git a/libafl_qemu/librasan/dummy_libc/Justfile b/libafl_qemu/librasan/dummy_libc/Justfile new file mode 100644 index 0000000000..7cdb4379f6 --- /dev/null +++ b/libafl_qemu/librasan/dummy_libc/Justfile @@ -0,0 +1,62 @@ +import "../../../just/libafl-qemu.just" +import "../fuzzer_name.just" + +DUMMY_SOURCE_DIR := source_directory() + +[unix] +compile_dummy: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + build \ + --package dummy_libc \ + --target $CROSS_TARGET \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} + +[unix] +fix_dummy: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package dummy_libc \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features test \ + --allow-dirty + +[unix] +link_dummy: compile_dummy + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + ${LIBRASAN_CFLAGS} \ + -shared \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u dlsym \ + -u dlerror \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libdummy_libc.so \ + -Wl,-hlibc.so.6 \ + -Wl,--version-script={{ DUMMY_SOURCE_DIR }}/dummy_libc.map \ + -Wl,--gc-sections \ + -Wl,--no-undefined \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libdummy_libc.a \ + +[unix] +strip_dummy: link_dummy + #!/bin/bash + source {{ DOTENV }} + ${CROSS_STRIP} \ + --strip-unneeded \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libdummy_libc.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libdummy_libc.so + +[unix] +build_dummy: strip_dummy diff --git a/libafl_qemu/librasan/dummy_libc/dummy_libc.map b/libafl_qemu/librasan/dummy_libc/dummy_libc.map new file mode 100644 index 0000000000..9c6a3898d9 --- /dev/null +++ b/libafl_qemu/librasan/dummy_libc/dummy_libc.map @@ -0,0 +1,8 @@ +{ + global: + dlsym; + dlerror; + + local: + *; +}; diff --git a/libafl_qemu/librasan/dummy_libc/src/lib.rs b/libafl_qemu/librasan/dummy_libc/src/lib.rs new file mode 100644 index 0000000000..601d2a8b5d --- /dev/null +++ b/libafl_qemu/librasan/dummy_libc/src/lib.rs @@ -0,0 +1,48 @@ +#![cfg_attr(not(feature = "test"), no_std)] +use core::ffi::{c_char, c_void}; +#[cfg(not(feature = "test"))] +use core::panic::PanicInfo; + +/// # Safety +/// See man pages +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dlsym(_handle: *mut c_void, _symbol: *const c_char) -> *mut c_void { + todo!(); +} + +/// # Safety +/// See man pages +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dlerror() -> *mut c_char { + todo!(); +} + +#[panic_handler] +#[cfg(not(feature = "test"))] +fn panic(_info: &PanicInfo) -> ! { + unimplemented!() +} + +#[cfg(target_arch = "arm")] +#[unsafe(no_mangle)] +extern "C" fn __aeabi_unwind_cpp_pr0() { + unimplemented!() +} + +#[cfg(target_arch = "powerpc")] +#[unsafe(no_mangle)] +extern "C" fn rust_eh_personality() { + unimplemented!(); +} + +#[cfg(target_arch = "powerpc")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memcpy(_dest: *mut u8, _src: *const u8, _count: usize) { + unimplemented!(); +} + +#[cfg(target_arch = "powerpc")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memset(_dest: *mut u8, _value: u8, _count: usize) { + unimplemented!(); +} diff --git a/libafl_qemu/librasan/fuzz/.gitignore b/libafl_qemu/librasan/fuzz/.gitignore new file mode 100644 index 0000000000..401a0abbe0 --- /dev/null +++ b/libafl_qemu/librasan/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +fuzz-*.log diff --git a/libafl_qemu/librasan/fuzz/Cargo.toml b/libafl_qemu/librasan/fuzz/Cargo.toml new file mode 100644 index 0000000000..6e35d3a8ca --- /dev/null +++ b/libafl_qemu/librasan/fuzz/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "fuzz" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +publish = false + + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { version = "1.4.1" } +asan = { path = "../asan", default-features = false, features = [ + "dlmalloc", + "guest", + "host", + "libc", + "linux", + "test", + "tracking", +] } +env_logger = { version = "0.11.6" } +libfuzzer-sys = { version = "0.4" } +log = { version = "0.4.22", features = ["release_max_level_info"] } +mockall = { version = "0.13.1" } +thiserror = { version = "2.0.11" } + +[[bin]] +name = "guest_shadow" +path = "fuzz_targets/guest_shadow.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "guest_tracking" +path = "fuzz_targets/guest_tracking.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "default_frontend_mock" +path = "fuzz_targets/default_frontend_mock.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "default_frontend_dlmalloc" +path = "fuzz_targets/default_frontend_dlmalloc.rs" +test = false +doc = false +bench = false diff --git a/libafl_qemu/librasan/fuzz/Justfile b/libafl_qemu/librasan/fuzz/Justfile new file mode 100644 index 0000000000..ca5ab02fae --- /dev/null +++ b/libafl_qemu/librasan/fuzz/Justfile @@ -0,0 +1,71 @@ +import "../../../just/libafl-qemu.just" +import "../fuzzer_name.just" + +FUZZ_SOURCE_DIR := source_directory() +FUZZ_MODE_FLAG := if PROFILE == "dev" { "--dev" } else { "--release" } +FUZZ_JOBS := if PROFILE == "dev" { "" } else { "-jobs=8" } + +[unix] +build_fuzz: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + build \ + --package fuzz \ + --target x86_64-unknown-linux-gnu \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} + +[unix] +fix_fuzz: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package fuzz \ + --target x86_64-unknown-linux-gnu \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --allow-dirty + +[unix] +run_fuzz: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fuzz \ + run \ + --target x86_64-unknown-linux-gnu \ + --sanitizer none \ + {{ FUZZ_MODE_FLAG }} \ + ${HARNESS} \ + -- \ + -rss_limit_mb=8192 \ + {{ FUZZ_JOBS }} + +[unix] +fuzz_guest_shadow: + #!/bin/bash + source {{ DOTENV }} + HARNESS=guest_shadow just run_fuzz + +[unix] +fuzz_guest_tracking: + #!/bin/bash + source {{ DOTENV }} + HARNESS=guest_tracking just run_fuzz + +[unix] +fuzz_default_frontend_mock: + #!/bin/bash + source {{ DOTENV }} + HARNESS=default_frontend_mock just run_fuzz + +[unix] +fuzz_default_frontend_dlmalloc: + #!/bin/bash + source {{ DOTENV }} + HARNESS=default_frontend_dlmalloc just run_fuzz diff --git a/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_dlmalloc.rs b/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_dlmalloc.rs new file mode 100644 index 0000000000..62bf2734bd --- /dev/null +++ b/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_dlmalloc.rs @@ -0,0 +1,85 @@ +#![no_main] + +use std::sync::{LazyLock, Mutex, MutexGuard}; + +use asan::{ + GuestAddr, + allocator::{ + backend::dlmalloc::DlmallocBackend, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + mmap::linux::LinuxMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + tracking::guest::GuestTracking, +}; +use libfuzzer_sys::fuzz_target; +use log::info; + +type DF = DefaultFrontend< + DlmallocBackend, + GuestShadow, + GuestTracking, +>; + +const PAGE_SIZE: usize = 4096; + +static INIT_ONCE: LazyLock> = LazyLock::new(|| { + env_logger::init(); + let backend = DlmallocBackend::::new(PAGE_SIZE); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + let frontend = DF::new( + backend, + shadow, + tracking, + DF::DEFAULT_REDZONE_SIZE, + DF::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + Mutex::new(frontend) +}); + +fn get_frontend() -> MutexGuard<'static, DF> { + INIT_ONCE.lock().unwrap() +} + +const MAX_LENGTH: usize = 0x3ff; +/* + * Increase the changes of requesting unaligned or minimally aliugned allocations + * since these are likely to be most common + */ +const ALIGNMENTS: [usize; 16] = [0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 32, 64, 128, 256, 512, 1024]; +const ALIGNMENTS_MASK: usize = ALIGNMENTS.len() - 1; + +fuzz_target!(|data: Vec| { + if data.len() < 2 { + return; + } + let mut frontend = get_frontend(); + + let len = data[0] & MAX_LENGTH; + let align_idx = data[1] & ALIGNMENTS_MASK; + let align = ALIGNMENTS[align_idx]; + + info!( + "data: {:x?}, len: 0x{:x}, align: 0x{:x}", + &data[0..2], + len, + align + ); + + if len == 0 { + return; + } + + let buf = frontend.alloc(len, align).unwrap(); + for i in buf - DF::DEFAULT_REDZONE_SIZE..buf + len + DF::DEFAULT_REDZONE_SIZE { + let expected = i < buf || i >= buf + len; + let poisoned = frontend.shadow().is_poison(i, 1).unwrap(); + assert_eq!(expected, poisoned); + } + frontend.dealloc(buf).unwrap(); +}); diff --git a/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_mock.rs b/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_mock.rs new file mode 100644 index 0000000000..729c903e2d --- /dev/null +++ b/libafl_qemu/librasan/fuzz/fuzz_targets/default_frontend_mock.rs @@ -0,0 +1,144 @@ +#![no_main] + +use std::{ + alloc::{GlobalAlloc, Layout}, + fmt::Debug, + sync::{LazyLock, Mutex, MutexGuard}, +}; + +use asan::{ + GuestAddr, + allocator::frontend::{AllocatorFrontend, default::DefaultFrontend}, + mmap::{Mmap, linux::LinuxMmap}, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + tracking::guest::GuestTracking, +}; +use libfuzzer_sys::fuzz_target; +use log::{debug, info}; +use mockall::mock; +use thiserror::Error; + +// We can't mock GlobalAlloc since `*mut u8` isn't Send and Sync, so we will +// create a trivial implementation of it which converts the types and calls this +// substititue mockable trait instead. +trait BackendTrait { + fn do_alloc(&self, layout: Layout) -> GuestAddr; + fn do_dealloc(&self, addr: GuestAddr, layout: Layout); +} + +mock! { + #[derive(Debug)] + pub Backend {} + + impl BackendTrait for Backend { + fn do_alloc(&self, layout: Layout) -> GuestAddr; + fn do_dealloc(&self, addr: GuestAddr, layout: Layout); + } +} + +unsafe impl GlobalAlloc for MockBackend { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.do_alloc(layout) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.do_dealloc(ptr as GuestAddr, layout) + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum MockBackendError {} + +type DF = DefaultFrontend, GuestTracking>; + +static MAP: LazyLock = LazyLock::new(|| LinuxMmap::map(MAX_ADDR).unwrap()); + +static INIT_ONCE: LazyLock> = LazyLock::new(|| { + env_logger::init(); + let backend = MockBackend::new(); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + let frontend = DF::new( + backend, + shadow, + tracking, + DF::DEFAULT_REDZONE_SIZE, + DF::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + Mutex::new(frontend) +}); + +fn get_frontend() -> MutexGuard<'static, DF> { + INIT_ONCE.lock().unwrap() +} + +const MAX_ADDR: GuestAddr = 64 << 20; +const ADDR_MASK: GuestAddr = MAX_ADDR - 8; +const MAX_LENGTH: usize = 0x3ff; +/* + * Increase the changes of requesting unaligned or minimally aliugned allocations + * since these are likely to be most common + */ +const ALIGNMENTS: [usize; 16] = [0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 32, 64, 128, 256, 512, 1024]; +const ALIGNMENTS_MASK: usize = ALIGNMENTS.len() - 1; + +fuzz_target!(|data: Vec| { + if data.len() < 3 { + return; + } + let mut frontend = get_frontend(); + + let len = data[0] & MAX_LENGTH; + let align_idx = data[1] & ALIGNMENTS_MASK; + let align = ALIGNMENTS[align_idx]; + let addr = data[2] & ADDR_MASK; + + let base = MAP.as_slice().as_ptr() as GuestAddr; + + frontend + .backend_mut() + .expect_do_alloc() + .returning(move |layout| { + debug!( + "mock - len: 0x{:x}, align: 0x{:x}", + layout.size(), + layout.align() + ); + base + addr + }); + frontend + .backend_mut() + .expect_do_dealloc() + .returning(|addr, layout| { + debug!( + "mock - addr: 0x{:x}, len: 0x{:x}, align: 0x{:x}", + addr, + layout.size(), + layout.align() + ); + }); + + info!( + "data: {:p}, len: 0x{:x}, align: 0x{:x}, addr: 0x{:x}", + &data[0..2], + len, + align, + addr + ); + + if len == 0 { + return; + } + + let buf = frontend.alloc(len, align).unwrap(); + for i in buf - DF::DEFAULT_REDZONE_SIZE..buf + len + DF::DEFAULT_REDZONE_SIZE { + let expected = i < buf || i >= buf + len; + let poisoned = frontend.shadow().is_poison(i, 1).unwrap(); + assert_eq!(expected, poisoned); + } + frontend.dealloc(buf).unwrap(); +}); diff --git a/libafl_qemu/librasan/fuzz/fuzz_targets/guest_shadow.rs b/libafl_qemu/librasan/fuzz/fuzz_targets/guest_shadow.rs new file mode 100644 index 0000000000..258dcff7ba --- /dev/null +++ b/libafl_qemu/librasan/fuzz/fuzz_targets/guest_shadow.rs @@ -0,0 +1,94 @@ +#![no_main] + +use std::sync::{LazyLock, Mutex, MutexGuard}; + +use asan::{ + GuestAddr, + mmap::libc::LibcMmap, + shadow::{ + PoisonType, Shadow, + guest::{DefaultShadowLayout, GuestShadow, GuestShadowError}, + }, + symbols::dlsym::{DlSymSymbols, LookupTypeNext}, +}; +use libfuzzer_sys::fuzz_target; +use log::info; +type GS = GuestShadow>, DefaultShadowLayout>; + +static INIT_ONCE: LazyLock> = LazyLock::new(|| { + env_logger::init(); + Mutex::new( + GuestShadow::>, DefaultShadowLayout>::new().unwrap(), + ) +}); + +fn get_shadow() -> MutexGuard<'static, GS> { + INIT_ONCE.lock().unwrap() +} + +const MAX_LENGTH: usize = 0x3ff; +const MAX_ADDR: GuestAddr = GS::HIGH_MEM_LIMIT; +const MAX_OFFSET: usize = 0x7ff; + +fuzz_target!(|data: Vec| { + let mut shadow = get_shadow(); + if data.len() < 4 { + return; + } + info!("data: {:x?}", data); + let start = data[0] & MAX_ADDR; + let len = data[1] & MAX_LENGTH; + let test_offset = data[2] & MAX_OFFSET; + let test_len = data[3] & MAX_LENGTH; + + let result = shadow.poison(start, len, PoisonType::AsanUser); + + if !GS::is_memory(start, len) { + assert_eq!(result, Err(GuestShadowError::InvalidMemoryAddress(start))); + return; + } else if !GS::is_end_aligned(start, len) { + assert_eq!( + result, + Err(GuestShadowError::UnalignedEndAddress(start, len,)) + ); + return; + } else { + assert_eq!(result, Ok(())); + } + + let test_start = if test_offset > MAX_LENGTH { + start.saturating_add(test_offset - MAX_LENGTH) & MAX_ADDR + } else { + start.saturating_sub(test_offset) + }; + + let test_result = shadow.is_poison(test_start, test_len); + if !GS::is_memory(test_start, test_len) { + assert_eq!( + test_result, + Err(GuestShadowError::InvalidMemoryAddress(test_start)) + ); + } else if len == 0 || test_len == 0 { + assert_eq!(test_result, Ok(false)); + } else { + let end = start + len; + let test_end = test_start + test_len; + let overlaps = !(test_end <= start || test_start >= end); + assert_eq!(test_result, Ok(overlaps)); + } + + let start_aligned = GS::align_down(start); + shadow + .unpoison(start_aligned, len + start - start_aligned) + .unwrap(); + + let retest_result = shadow.is_poison(test_start, test_len); + if !GS::is_memory(test_start, test_len) { + assert_eq!( + retest_result, + Err(GuestShadowError::InvalidMemoryAddress(test_start)) + ); + } else { + assert_eq!(retest_result, Ok(false)); + } +}); diff --git a/libafl_qemu/librasan/fuzz/fuzz_targets/guest_tracking.rs b/libafl_qemu/librasan/fuzz/fuzz_targets/guest_tracking.rs new file mode 100644 index 0000000000..bb84ce5bfb --- /dev/null +++ b/libafl_qemu/librasan/fuzz/fuzz_targets/guest_tracking.rs @@ -0,0 +1,99 @@ +#![no_main] + +use std::sync::{LazyLock, Mutex, MutexGuard}; + +use asan::{ + GuestAddr, + tracking::{ + Tracking, + guest::{GuestTracking, GuestTrackingError}, + }, +}; +use libfuzzer_sys::fuzz_target; +use log::{debug, info}; + +static INIT_ONCE: LazyLock> = LazyLock::new(|| { + env_logger::init(); + Mutex::new(GuestTracking::new().unwrap()) +}); + +fn get_tracking() -> MutexGuard<'static, GuestTracking> { + INIT_ONCE.lock().unwrap() +} + +const MAX_LENGTH: usize = 0x3ff; +/* + * Deliberately ensure we are short of GuestAddr::MAX so we can use alternative logic for the implementation + * to check the overlap (which might otherwise overflow). The implementation is tested for overflow in the + * unit tests + */ +const MAX_ADDR: GuestAddr = 0x7fffffffffff; +const MAX_OFFSET: usize = 0x7ff; + +fuzz_target!(|data: Vec| { + let mut tracking = get_tracking(); + if data.len() < 4 { + return; + } + info!("data: {:x?}", data); + let start = data[0] & MAX_ADDR; + let len = data[1] & MAX_LENGTH; + let test_offset = data[2] & MAX_OFFSET; + let test_len = data[3] & MAX_LENGTH; + + let test_start = if test_offset > MAX_LENGTH { + start.saturating_add(test_offset - MAX_LENGTH) & MAX_ADDR + } else { + start.saturating_sub(test_offset) + }; + + let result = tracking.alloc(start, len); + if GuestTracking::is_out_of_bounds(start, len) { + assert_eq!( + result, + Err(GuestTrackingError::AddressRangeOverflow(start, len)) + ); + return; + } else if len == 0 { + assert_eq!(result, Err(GuestTrackingError::ZeroLength(start))); + return; + } else { + assert_eq!(result, Ok(())); + } + + let test_result = tracking.alloc(test_start, test_len); + if GuestTracking::is_out_of_bounds(test_start, test_len) { + assert_eq!( + test_result, + Err(GuestTrackingError::AddressRangeOverflow( + test_start, test_len + )) + ); + } else if test_len == 0 { + assert_eq!(test_result, Err(GuestTrackingError::ZeroLength(test_start))); + } else { + let end = start + len; + let test_end = test_start + test_len; + let a = test_end <= start; + let b = test_start >= end; + let overlaps = !(a || b); + let mut sorted = Vec::from([start, end, test_start, test_end]); + sorted.sort(); + debug!( + "start: {:x}, end: {:x}, test_start: {:x}, test_end: {:x}, sorted: {:x?}, a: {}, b: {}, overlaps: {}", + start, end, test_start, test_end, sorted, a, b, overlaps, + ); + if overlaps { + assert_eq!( + test_result, + Err(GuestTrackingError::TrackingConflict( + start, len, test_start, test_len + )) + ); + } else { + assert_eq!(test_result, Ok(())); + assert_eq!(tracking.dealloc(test_start), Ok(())); + } + } + assert_eq!(tracking.dealloc(start), Ok(())); +}); diff --git a/libafl_qemu/librasan/fuzzer_name.just b/libafl_qemu/librasan/fuzzer_name.just new file mode 100644 index 0000000000..79ddfe298f --- /dev/null +++ b/libafl_qemu/librasan/fuzzer_name.just @@ -0,0 +1 @@ +FUZZER_NAME := "" diff --git a/libafl_qemu/librasan/gasan/Cargo.toml b/libafl_qemu/librasan/gasan/Cargo.toml new file mode 100644 index 0000000000..ecbfb19756 --- /dev/null +++ b/libafl_qemu/librasan/gasan/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "gasan" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +crate-type = ["staticlib"] + +[features] +default = [] +test = ["asan/test", "dummy_libc/test"] + +[dependencies] +asan = { path = "../asan", default-features = false, features = [ + "dlmalloc", + "guest", + "hooks", + "libc", + "mimalloc", + "tracking", +] } +dummy_libc = { path = "../dummy_libc", default-features = false } +log = { version = "0.4.22", default-features = false, features = [ + "release_max_level_info", +] } +spin = { version = "0.9.8", default-features = false, features = [ + "lazy", + "mutex", + "spin_mutex", +] } diff --git a/libafl_qemu/librasan/gasan/Justfile b/libafl_qemu/librasan/gasan/Justfile new file mode 100644 index 0000000000..9eff698935 --- /dev/null +++ b/libafl_qemu/librasan/gasan/Justfile @@ -0,0 +1,122 @@ +import "../../../just/libafl-qemu.just" +import "../dummy_libc/Justfile" +import "../fuzzer_name.just" + +GASAN_SOURCE_DIR := source_directory() + +[unix] +compile_gasan: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + build \ + --package gasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} + +[unix] +fix_gasan: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package gasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features test \ + --allow-dirty + +[unix] +rel_gasan: compile_gasan build_dummy + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + ${LIBRASAN_CFLAGS} \ + -r \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u patch_aligned_alloc \ + -u patch_atoi \ + -u patch_atol \ + -u patch_atoll \ + -u patch_calloc \ + -u patch_free \ + -u patch_malloc \ + -u patch_malloc_usable_size \ + -u patch_memalign \ + -u patch_memset \ + -u patch_mmap \ + -u patch_munmap \ + -u patch_posix_memalign \ + -u patch_pvalloc \ + -u patch_read \ + -u patch_realloc \ + -u patch_reallocarray \ + -u patch_valloc \ + -u patch_write \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.a \ + +[unix] +rename_gasan: rel_gasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_OBJCOPY} \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_real.syms \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_patch.syms \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.renamed + +[unix] +link_gasan: rename_gasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + -shared \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u aligned_alloc \ + -u atoi \ + -u atol \ + -u atoll \ + -u calloc \ + -u free \ + -u malloc \ + -u malloc_usable_size \ + -u memalign \ + -u memset \ + -u mmap \ + -u munmap \ + -u posix_memalign \ + -u pvalloc \ + -u read \ + -u realloc \ + -u reallocarray \ + -u valloc \ + -u write \ + -Wl,--version-script={{ GASAN_SOURCE_DIR }}/gasan.map \ + -Wl,--gc-sections \ + -Wl,--no-undefined \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libgasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.renamed \ + -L {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }} \ + "-ldummy_libc" \ + +strip_gasan: link_gasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_STRIP} \ + --strip-unneeded \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libgasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libgasan.so + +[unix] +build_gasan: strip_gasan diff --git a/libafl_qemu/librasan/gasan/gasan.map b/libafl_qemu/librasan/gasan/gasan.map new file mode 100644 index 0000000000..3ca036d47b --- /dev/null +++ b/libafl_qemu/librasan/gasan/gasan.map @@ -0,0 +1,25 @@ +{ + global: + aligned_alloc; + atoi; + atol; + atoll; + calloc; + free; + malloc; + malloc_usable_size; + memalign; + memset; + mmap; + munmap; + posix_memalign; + pvalloc; + read; + realloc; + reallocarray; + valloc; + write; + + local: + *; +}; diff --git a/libafl_qemu/librasan/gasan/rename_patch.syms b/libafl_qemu/librasan/gasan/rename_patch.syms new file mode 100644 index 0000000000..1907e3e866 --- /dev/null +++ b/libafl_qemu/librasan/gasan/rename_patch.syms @@ -0,0 +1,19 @@ +patch_aligned_alloc aligned_alloc +patch_atoi atoi +patch_atol atol +patch_atoll atoll +patch_calloc calloc +patch_free free +patch_malloc malloc +patch_malloc_usable_size malloc_usable_size +patch_memalign memalign +patch_memset memset +patch_mmap mmap +patch_munmap munmap +patch_posix_memalign posix_memalign +patch_pvalloc pvalloc +patch_read read +patch_realloc realloc +patch_reallocarray reallocarray +patch_valloc valloc +patch_write write diff --git a/libafl_qemu/librasan/gasan/rename_real.syms b/libafl_qemu/librasan/gasan/rename_real.syms new file mode 100644 index 0000000000..80f90c6681 --- /dev/null +++ b/libafl_qemu/librasan/gasan/rename_real.syms @@ -0,0 +1,19 @@ +aligned_alloc real_aligned_alloc +atoi real_atoi +atol real_atol +atoll real_atoll +calloc real_calloc +free real_free +malloc real_malloc +malloc_usable_size real_malloc_usable_size +memalign real_memalign +memset real_memset +mmap real_mmap +munmap real_munmap +posix_memalign real_posix_memalign +pvalloc real_pvalloc +read real_read +realloc real_realloc +reallocarray real_reallocarray +valloc real_valloc +write real_write diff --git a/libafl_qemu/librasan/gasan/src/lib.rs b/libafl_qemu/librasan/gasan/src/lib.rs new file mode 100644 index 0000000000..4cfee87e06 --- /dev/null +++ b/libafl_qemu/librasan/gasan/src/lib.rs @@ -0,0 +1,180 @@ +#![cfg_attr(not(feature = "test"), no_std)] +extern crate alloc; + +use core::ffi::{CStr, c_char, c_void}; + +use asan::{ + GuestAddr, + allocator::{ + backend::{dlmalloc::DlmallocBackend, mimalloc::MimallocBackend}, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + logger::libc::LibcLogger, + maps::libc::LibcMapReader, + mmap::libc::LibcMmap, + patch::{hooks::PatchedHooks, raw::RawPatch}, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + symbols::{ + Symbols, + dlsym::{DlSymSymbols, LookupTypeNext}, + }, + tracking::{Tracking, guest::GuestTracking}, +}; +use log::{Level, debug, trace}; +use spin::{Lazy, mutex::Mutex}; + +type Syms = DlSymSymbols; + +type GasanMmap = LibcMmap; + +type GasanBackend = MimallocBackend>; + +pub type GasanFrontend = + DefaultFrontend, GuestTracking>; + +pub type GasanSyms = DlSymSymbols; + +const PAGE_SIZE: usize = 4096; + +static FRONTEND: Lazy> = Lazy::new(|| { + LibcLogger::initialize::(Level::Info); + debug!("init"); + let backend = GasanBackend::new(DlmallocBackend::new(PAGE_SIZE)); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + let frontend = GasanFrontend::new( + backend, + shadow, + tracking, + GasanFrontend::DEFAULT_REDZONE_SIZE, + GasanFrontend::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + PatchedHooks::init::, GasanMmap>().unwrap(); + Mutex::new(frontend) +}); + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_load(addr: *const c_void, size: usize) { + trace!("load - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_store(addr: *const c_void, size: usize) { + trace!("store - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_alloc(len: usize, align: usize) -> *mut c_void { + trace!("alloc - len: {:#x}, align: {:#x}", len, align); + let ptr = FRONTEND.lock().alloc(len, align).unwrap() as *mut c_void; + trace!( + "alloc - len: {:#x}, align: {:#x}, ptr: {:p}", + len, align, ptr + ); + ptr +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_dealloc(addr: *const c_void) { + trace!("free - addr: {:p}", addr); + FRONTEND.lock().dealloc(addr as GuestAddr).unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_get_size(addr: *const c_void) -> usize { + trace!("get_size - addr: {:p}", addr); + FRONTEND.lock().get_size(addr as GuestAddr).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_sym(name: *const c_char) -> GuestAddr { + GasanSyms::lookup(name).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_page_size() -> usize { + PAGE_SIZE +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_unpoison(addr: *const c_void, len: usize) { + trace!("unpoison - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .shadow_mut() + .unpoison(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_track(addr: *const c_void, len: usize) { + trace!("track - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .tracking_mut() + .alloc(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_untrack(addr: *const c_void) { + trace!("untrack - addr: {:p}", addr); + FRONTEND + .lock() + .tracking_mut() + .dealloc(addr as GuestAddr) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_panic(msg: *const c_char) -> ! { + trace!("panic - msg: {:p}", msg); + let msg = unsafe { CStr::from_ptr(msg as *const c_char) }; + panic!("{}", msg.to_str().unwrap()); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_swap(_enabled: bool) { + /* Don't log since this function is on the logging path */ +} + +#[used] +#[unsafe(link_section = ".init_array")] +static INIT: fn() = ctor; + +#[unsafe(no_mangle)] +fn ctor() { + drop(FRONTEND.lock()); +} diff --git a/libafl_qemu/librasan/qasan/Cargo.toml b/libafl_qemu/librasan/qasan/Cargo.toml new file mode 100644 index 0000000000..e7f1ed66d0 --- /dev/null +++ b/libafl_qemu/librasan/qasan/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "qasan" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +crate-type = ["staticlib"] + +[features] +default = [] +test = ["asan/test", "dummy_libc/test"] + +[dependencies] +asan = { path = "../asan", default-features = false, features = [ + "dlmalloc", + "hooks", + "host", + "libc", + "mimalloc", + "tracking", +] } +dummy_libc = { path = "../dummy_libc", default-features = false } +libc = { version = "0.2.169", default-features = false } +log = { version = "0.4.22", default-features = false, features = [ + "release_max_level_info", +] } +spin = { version = "0.9.8", default-features = false, features = [ + "lazy", + "mutex", + "spin_mutex", +] } diff --git a/libafl_qemu/librasan/qasan/Justfile b/libafl_qemu/librasan/qasan/Justfile new file mode 100644 index 0000000000..eaf72aacf4 --- /dev/null +++ b/libafl_qemu/librasan/qasan/Justfile @@ -0,0 +1,123 @@ +import "../../../just/libafl-qemu.just" +import "../dummy_libc/Justfile" +import "../fuzzer_name.just" + +QASAN_SOURCE_DIR := source_directory() + +[unix] +compile_qasan: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + build \ + --package qasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} + +[unix] +fix_qasan: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package qasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features test \ + --allow-dirty + +[unix] +rel_qasan: compile_qasan build_dummy + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + ${LIBRASAN_CFLAGS} \ + -r \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u patch_aligned_alloc \ + -u patch_atoi \ + -u patch_atol \ + -u patch_atoll \ + -u patch_calloc \ + -u patch_free \ + -u patch_malloc \ + -u patch_malloc_usable_size \ + -u patch_memalign \ + -u patch_memset \ + -u patch_mmap \ + -u patch_munmap \ + -u patch_posix_memalign \ + -u patch_pvalloc \ + -u patch_read \ + -u patch_realloc \ + -u patch_reallocarray \ + -u patch_valloc \ + -u patch_write \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.a + +[unix] +rename_qasan: rel_qasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_OBJCOPY} \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_real.syms \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_patch.syms \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.renamed + +[unix] +link_qasan: rename_qasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + -shared \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u aligned_alloc \ + -u atoi \ + -u atol \ + -u atoll \ + -u calloc \ + -u free \ + -u malloc \ + -u malloc_usable_size \ + -u memalign \ + -u memset \ + -u mmap \ + -u munmap \ + -u posix_memalign \ + -u pvalloc \ + -u read \ + -u realloc \ + -u reallocarray \ + -u valloc \ + -u write \ + -Wl,--version-script={{ QASAN_SOURCE_DIR }}/qasan.map \ + -Wl,--gc-sections \ + -Wl,--no-undefined \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libqasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.renamed \ + -L {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }} \ + "-ldummy_libc" \ + +[unix] +strip_qasan: link_qasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_STRIP} \ + --strip-unneeded \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libqasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libqasan.so + +[unix] +build_qasan: strip_qasan diff --git a/libafl_qemu/librasan/qasan/qasan.map b/libafl_qemu/librasan/qasan/qasan.map new file mode 100644 index 0000000000..3ca036d47b --- /dev/null +++ b/libafl_qemu/librasan/qasan/qasan.map @@ -0,0 +1,25 @@ +{ + global: + aligned_alloc; + atoi; + atol; + atoll; + calloc; + free; + malloc; + malloc_usable_size; + memalign; + memset; + mmap; + munmap; + posix_memalign; + pvalloc; + read; + realloc; + reallocarray; + valloc; + write; + + local: + *; +}; diff --git a/libafl_qemu/librasan/qasan/rename_patch.syms b/libafl_qemu/librasan/qasan/rename_patch.syms new file mode 100644 index 0000000000..1907e3e866 --- /dev/null +++ b/libafl_qemu/librasan/qasan/rename_patch.syms @@ -0,0 +1,19 @@ +patch_aligned_alloc aligned_alloc +patch_atoi atoi +patch_atol atol +patch_atoll atoll +patch_calloc calloc +patch_free free +patch_malloc malloc +patch_malloc_usable_size malloc_usable_size +patch_memalign memalign +patch_memset memset +patch_mmap mmap +patch_munmap munmap +patch_posix_memalign posix_memalign +patch_pvalloc pvalloc +patch_read read +patch_realloc realloc +patch_reallocarray reallocarray +patch_valloc valloc +patch_write write diff --git a/libafl_qemu/librasan/qasan/rename_real.syms b/libafl_qemu/librasan/qasan/rename_real.syms new file mode 100644 index 0000000000..80f90c6681 --- /dev/null +++ b/libafl_qemu/librasan/qasan/rename_real.syms @@ -0,0 +1,19 @@ +aligned_alloc real_aligned_alloc +atoi real_atoi +atol real_atol +atoll real_atoll +calloc real_calloc +free real_free +malloc real_malloc +malloc_usable_size real_malloc_usable_size +memalign real_memalign +memset real_memset +mmap real_mmap +munmap real_munmap +posix_memalign real_posix_memalign +pvalloc real_pvalloc +read real_read +realloc real_realloc +reallocarray real_reallocarray +valloc real_valloc +write real_write diff --git a/libafl_qemu/librasan/qasan/src/lib.rs b/libafl_qemu/librasan/qasan/src/lib.rs new file mode 100644 index 0000000000..6050eadd8f --- /dev/null +++ b/libafl_qemu/librasan/qasan/src/lib.rs @@ -0,0 +1,180 @@ +#![no_std] +extern crate alloc; + +use core::ffi::{CStr, c_char, c_void}; + +use asan::{ + GuestAddr, + allocator::{ + backend::{dlmalloc::DlmallocBackend, mimalloc::MimallocBackend}, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + host::{Host, libc::LibcHost}, + logger::libc::LibcLogger, + maps::libc::LibcMapReader, + mmap::libc::LibcMmap, + patch::{hooks::PatchedHooks, raw::RawPatch}, + shadow::{Shadow, host::HostShadow}, + symbols::{ + Symbols, + dlsym::{DlSymSymbols, LookupTypeNext}, + }, + tracking::{Tracking, host::HostTracking}, +}; +use log::{Level, trace}; +use spin::{Lazy, Mutex}; + +type Syms = DlSymSymbols; + +type QasanMmap = LibcMmap; + +type QasanBackend = MimallocBackend>; + +type QasanHost = LibcHost; + +pub type QasanFrontend = + DefaultFrontend, HostTracking>; + +pub type QasanSyms = DlSymSymbols; + +const PAGE_SIZE: usize = 4096; + +static FRONTEND: Lazy> = Lazy::new(|| { + LibcLogger::initialize::(Level::Info); + let backend = QasanBackend::new(DlmallocBackend::new(PAGE_SIZE)); + let shadow = HostShadow::::new().unwrap(); + let tracking = HostTracking::::new().unwrap(); + let frontend = QasanFrontend::new( + backend, + shadow, + tracking, + QasanFrontend::DEFAULT_REDZONE_SIZE, + QasanFrontend::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + PatchedHooks::init::, QasanMmap>().unwrap(); + Mutex::new(frontend) +}); + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_load(addr: *const c_void, size: usize) { + trace!("load - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_store(addr: *const c_void, size: usize) { + trace!("store - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_alloc(len: usize, align: usize) -> *mut c_void { + trace!("alloc - len: {:#x}, align: {:#x}", len, align); + let ptr = FRONTEND.lock().alloc(len, align).unwrap() as *mut c_void; + trace!( + "alloc - len: {:#x}, align: {:#x}, ptr: {:p}", + len, align, ptr + ); + ptr +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_dealloc(addr: *const c_void) { + trace!("free - addr: {:p}", addr); + FRONTEND.lock().dealloc(addr as GuestAddr).unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_get_size(addr: *const c_void) -> usize { + trace!("get_size - addr: {:p}", addr); + FRONTEND.lock().get_size(addr as GuestAddr).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_sym(name: *const c_char) -> GuestAddr { + QasanSyms::lookup(name).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_page_size() -> usize { + PAGE_SIZE +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_unpoison(addr: *const c_void, len: usize) { + trace!("unpoison - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .shadow_mut() + .unpoison(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_track(addr: *const c_void, len: usize) { + trace!("track - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .tracking_mut() + .alloc(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_untrack(addr: *const c_void) { + trace!("untrack - addr: {:p}", addr); + FRONTEND + .lock() + .tracking_mut() + .dealloc(addr as GuestAddr) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_panic(msg: *const c_char) -> ! { + trace!("panic - msg: {:p}", msg); + let msg = unsafe { CStr::from_ptr(msg as *const c_char) }; + panic!("{}", msg.to_str().unwrap()); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_swap(enabled: bool) { + /* Don't log since this function is on the logging path */ + QasanHost::swap(enabled).unwrap(); +} + +#[used] +#[unsafe(link_section = ".init_array")] +static INIT: fn() = ctor; + +#[unsafe(no_mangle)] +fn ctor() { + drop(FRONTEND.lock()); +} diff --git a/libafl_qemu/librasan/runner/Cargo.toml b/libafl_qemu/librasan/runner/Cargo.toml new file mode 100644 index 0000000000..259e60a78c --- /dev/null +++ b/libafl_qemu/librasan/runner/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "runner" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[[bin]] +name = "runner-arm" +path = "src/arm.rs" +required-features = ["arm"] + +[[bin]] +name = "runner-x86_64" +path = "src/x86_64.rs" +required-features = ["x86_64"] + +[[bin]] +name = "runner-i386" +path = "src/i386.rs" +required-features = ["i386"] + +[[bin]] +name = "runner-aarch64" +path = "src/aarch64.rs" +required-features = ["aarch64"] + +[[bin]] +name = "runner-ppc" +path = "src/ppc.rs" +required-features = ["ppc"] + +[features] +default = ["std"] +std = [] +clippy = [] +## Set emulator to big endian +be = ["libafl_qemu/be"] + +#! ## Mutually exclusive architectures +arm = ["libafl_qemu/arm"] +x86_64 = ["libafl_qemu/x86_64"] +i386 = ["libafl_qemu/i386"] +aarch64 = ["libafl_qemu/aarch64"] +mips = ["libafl_qemu/mips"] +ppc = ["libafl_qemu/ppc", "be"] +hexagon = ["libafl_qemu/hexagon"] + +[build-dependencies] +vergen = { version = "9.0.1", features = ["build", "cargo", "rustc", "si"] } +vergen-git2 = "1.0.1" + +[dependencies] +clap = { version = "4.5.18", features = ["derive", "string"] } +env_logger = { version = "0.11.5" } +log = { version = "0.4.22", features = ["release_max_level_info"] } +readonly = { version = "0.2.12" } +libafl_bolts = { path = "../../../libafl_bolts" } +libafl_qemu = { path = "../../" } +thiserror = { version = "2.0.11" } diff --git a/libafl_qemu/librasan/runner/Justfile b/libafl_qemu/librasan/runner/Justfile new file mode 100644 index 0000000000..31fbfa1a5a --- /dev/null +++ b/libafl_qemu/librasan/runner/Justfile @@ -0,0 +1,32 @@ +import "../../../just/libafl-qemu.just" +import "../fuzzer_name.just" + +[unix] +compile_runner: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + build \ + --package runner \ + --target x86_64-unknown-linux-gnu \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features {{ ARCH }} + +[unix] +fix_runner: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package runner \ + --target x86_64-unknown-linux-gnu \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features {{ ARCH }} \ + --allow-dirty + +[unix] +build_runner: compile_runner diff --git a/libafl_qemu/librasan/runner/build.rs b/libafl_qemu/librasan/runner/build.rs new file mode 100644 index 0000000000..0f20f30922 --- /dev/null +++ b/libafl_qemu/librasan/runner/build.rs @@ -0,0 +1,61 @@ +use vergen::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder, SysinfoBuilder}; +use vergen_git2::Git2Builder; + +#[macro_export] +macro_rules! assert_unique_feature { + () => {}; + ($first:tt $(,$rest:tt)*) => { + $( + #[cfg(all(not(any(doc, feature = "clippy")), feature = $first, feature = $rest))] + compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together")); + )* + assert_unique_feature!($($rest),*); + } +} + +fn main() { + let build = BuildBuilder::all_build().unwrap(); + let cargo = CargoBuilder::all_cargo().unwrap(); + let git = Git2Builder::all_git().unwrap(); + let rustc = RustcBuilder::all_rustc().unwrap(); + let sysinfo = SysinfoBuilder::all_sysinfo().unwrap(); + + Emitter::default() + .add_instructions(&build) + .unwrap() + .add_instructions(&cargo) + .unwrap() + .add_instructions(&git) + .unwrap() + .add_instructions(&rustc) + .unwrap() + .add_instructions(&sysinfo) + .unwrap() + .emit() + .unwrap(); + + assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon"); + + let cpu_target = if cfg!(feature = "x86_64") { + "x86_64".to_string() + } else if cfg!(feature = "arm") { + "arm".to_string() + } else if cfg!(feature = "aarch64") { + "aarch64".to_string() + } else if cfg!(feature = "i386") { + "i386".to_string() + } else if cfg!(feature = "mips") { + "mips".to_string() + } else if cfg!(feature = "ppc") { + "ppc".to_string() + } else if cfg!(feature = "hexagon") { + "hexagon".to_string() + } else { + println!("cargo:warning=No architecture specified defaulting to x86_64..."); + println!("cargo:rustc-cfg=feature=\"x86_64\""); + println!("cargo:rustc-cfg=feature=\"64bit\""); + "x86_64".to_string() + }; + + println!("cargo:rustc-env=CPU_TARGET={cpu_target}"); +} diff --git a/libafl_qemu/librasan/runner/src/aarch64.rs b/libafl_qemu/librasan/runner/src/aarch64.rs new file mode 100644 index 0000000000..18290ad641 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/aarch64.rs @@ -0,0 +1,5 @@ +pub mod fuzz; + +fn main() { + crate::fuzz::fuzz(); +} diff --git a/libafl_qemu/librasan/runner/src/arm.rs b/libafl_qemu/librasan/runner/src/arm.rs new file mode 100644 index 0000000000..18290ad641 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/arm.rs @@ -0,0 +1,5 @@ +pub mod fuzz; + +fn main() { + crate::fuzz::fuzz(); +} diff --git a/libafl_qemu/librasan/runner/src/fuzz.rs b/libafl_qemu/librasan/runner/src/fuzz.rs new file mode 100644 index 0000000000..a65a03d848 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/fuzz.rs @@ -0,0 +1,167 @@ +use std::{env, fmt::Write}; + +use clap::{Parser, builder::Str}; +use libafl_bolts::{Error, tuples::tuple_list}; +use libafl_qemu::{ + Emulator, NopEmulatorDriver, NopSnapshotManager, QemuExitError, QemuInitError, + command::NopCommandManager, + elf::EasyElf, + modules::{AsanGuestModule, AsanModule, EmulatorModuleTuple}, +}; +use log::{error, info}; +use thiserror::Error; + +#[derive(Default)] +pub struct Version; + +impl From for Str { + fn from(_: Version) -> Str { + let version = [ + ("Architecture:", env!("CPU_TARGET")), + ("Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP")), + ("Describe:", env!("VERGEN_GIT_DESCRIBE")), + ("Commit SHA:", env!("VERGEN_GIT_SHA")), + ("Commit Date:", env!("VERGEN_RUSTC_COMMIT_DATE")), + ("Commit Branch:", env!("VERGEN_GIT_BRANCH")), + ("Rustc Version:", env!("VERGEN_RUSTC_SEMVER")), + ("Rustc Channel:", env!("VERGEN_RUSTC_CHANNEL")), + ("Rustc Host Triple:", env!("VERGEN_RUSTC_HOST_TRIPLE")), + ("Rustc Commit SHA:", env!("VERGEN_RUSTC_COMMIT_HASH")), + ("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")), + ] + .iter() + .fold(String::new(), |mut output, (k, v)| { + // Note that write!-ing into a String can never fail, despite the return type of write! being std::fmt::Result, so it can be safely ignored or unwrapped. + // See https://rust-lang.github.io/rust-clippy/master/index.html#/format_collect + let _ = writeln!(output, "{k:25}: {v}"); + output + }); + + format!("\n{version:}").into() + } +} + +#[readonly::make] +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +#[command( + name = format!("runner-{}",env!("CPU_TARGET")), + version = Version::default(), + about, + long_about = "Binary fuzzer using QEMU binary instrumentation" +)] +pub struct FuzzerOptions { + #[clap(short, long, help = "Enable host asan")] + pub asan: bool, + + #[clap(short, long, help = "Enable guest asan", conflicts_with = "asan")] + pub gasan: bool, + + #[clap(short, long, help = "Enable output from the fuzzer clients")] + pub verbose: bool, + + #[arg(last = true, help = "Arguments passed to the target")] + pub args: Vec, +} + +pub fn fuzz() { + env_logger::init(); + let mut options = FuzzerOptions::parse(); + + let program = env::args().next().unwrap(); + info!("Program: {program:}"); + + options.args.insert(0, program); + info!("ARGS: {:#?}", options.args); + + let env = env::vars() + .filter(|(k, _v)| k != "LD_LIBRARY_PATH") + .collect::>(); + + let ret = if options.asan { + info!("Enabling ASAN"); + let modules = tuple_list!(AsanModule::builder().env(&env).build()); + info!("Modules: {:#?}", modules); + run(options, modules) + } else if options.gasan { + info!("Enabling Guest ASAN"); + let modules = tuple_list!(AsanGuestModule::default(&env)); + info!("Modules: {:#?}", modules); + run(options, modules) + } else { + info!("Running without ASAN"); + let modules = tuple_list!(); + info!("Modules: {:#?}", modules); + run(options, modules) + }; + match ret { + Ok(r) => { + info!("Exit: {r:?}"); + } + Err(e) => { + error!("Error: {e:?}"); + } + } +} + +fn run>( + options: FuzzerOptions, + modules: M, +) -> Result<(), LauncherError> { + info!("Building emulator"); + let mut emulator: Emulator< + (), + NopCommandManager, + NopEmulatorDriver, + M, + (), + (), + NopSnapshotManager, + > = Emulator::empty() + .qemu_parameters(options.args) + .modules(modules) + .build() + .map_err(LauncherError::Init)?; + info!("Build emultor"); + let qemu = emulator.qemu(); + + let mut elf_buffer = Vec::new(); + let elf = + EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).map_err(LauncherError::ElfError)?; + + let test_one_input = elf.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()); + log::info!( + "LLVMFuzzerTestOneInput @ {:#x}", + test_one_input.unwrap_or_default() + ); + let main = elf.resolve_symbol("main", qemu.load_addr()); + log::info!("main @ {:#x}", main.unwrap_or_default()); + + let entry = test_one_input.or(main); + log::info!("entry @ {:#x}", entry.unwrap_or_default()); + + match entry { + Some(e) => qemu.entry_break(e), + None => Err(LauncherError::FailedToFindEntry)?, + } + + let mut state = (); + emulator.modules_mut().first_exec_all(qemu, &mut state); + + info!("Running emulator"); + unsafe { qemu.run().map_err(LauncherError::Exit)? }; + info!("Emulator exited"); + Ok(()) +} + +#[derive(Error, Debug)] +pub enum LauncherError { + #[error("Qemu init error: {0:?}")] + Init(QemuInitError), + #[error("Qemu error: {0:?}")] + Exit(QemuExitError), + #[error("Elf error: {0:?}")] + ElfError(Error), + #[error("Failed to find entry point")] + FailedToFindEntry, +} diff --git a/libafl_qemu/librasan/runner/src/i386.rs b/libafl_qemu/librasan/runner/src/i386.rs new file mode 100644 index 0000000000..18290ad641 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/i386.rs @@ -0,0 +1,5 @@ +pub mod fuzz; + +fn main() { + crate::fuzz::fuzz(); +} diff --git a/libafl_qemu/librasan/runner/src/ppc.rs b/libafl_qemu/librasan/runner/src/ppc.rs new file mode 100644 index 0000000000..18290ad641 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/ppc.rs @@ -0,0 +1,5 @@ +pub mod fuzz; + +fn main() { + crate::fuzz::fuzz(); +} diff --git a/libafl_qemu/librasan/runner/src/x86_64.rs b/libafl_qemu/librasan/runner/src/x86_64.rs new file mode 100644 index 0000000000..18290ad641 --- /dev/null +++ b/libafl_qemu/librasan/runner/src/x86_64.rs @@ -0,0 +1,5 @@ +pub mod fuzz; + +fn main() { + crate::fuzz::fuzz(); +} diff --git a/libafl_qemu/librasan/zasan/Cargo.toml b/libafl_qemu/librasan/zasan/Cargo.toml new file mode 100644 index 0000000000..2821b6d662 --- /dev/null +++ b/libafl_qemu/librasan/zasan/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "zasan" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +crate-type = ["staticlib"] + +[features] +default = [] +test = ["asan/test"] + +[dependencies] +asan = { path = "../asan", default-features = false, features = [ + "dlmalloc", + "guest", + "hooks", + "host", + "linux", + "tracking", +] } +log = { version = "0.4.22", default-features = false, features = [ + "release_max_level_info", +] } +spin = { version = "0.9.8", default-features = false, features = [ + "lazy", + "mutex", + "spin_mutex", +] } diff --git a/libafl_qemu/librasan/zasan/Justfile b/libafl_qemu/librasan/zasan/Justfile new file mode 100644 index 0000000000..8edb6375b5 --- /dev/null +++ b/libafl_qemu/librasan/zasan/Justfile @@ -0,0 +1,121 @@ +import "../../../just/libafl-qemu.just" +import "../fuzzer_name.just" + +ZASAN_SOURCE_DIR := source_directory() + +[unix] +compile_zasan: + #!/bin/bash + source {{ DOTENV }} + RUSTFLAGS="--cfg rustix_use_experimental_asm" \ + cargo \ + +nightly \ + build \ + --package zasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} + +[unix] +fix_zasan: + #!/bin/bash + source {{ DOTENV }} + cargo \ + +nightly \ + fix \ + --package zasan \ + --target ${CROSS_TARGET} \ + --profile {{ PROFILE }} \ + --target-dir {{ TARGET_DIR }} \ + --features test \ + --allow-dirty + +[unix] +rel_zasan: compile_zasan build_dummy + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + ${LIBRASAN_CFLAGS} \ + -r \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u patch_aligned_alloc \ + -u patch_atoi \ + -u patch_atol \ + -u patch_atoll \ + -u patch_calloc \ + -u patch_free \ + -u patch_malloc \ + -u patch_malloc_usable_size \ + -u patch_memalign \ + -u patch_memset \ + -u patch_mmap \ + -u patch_munmap \ + -u patch_posix_memalign \ + -u patch_pvalloc \ + -u patch_read \ + -u patch_realloc \ + -u patch_reallocarray \ + -u patch_valloc \ + -u patch_write \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.a + +[unix] +rename_zasan: rel_zasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_OBJCOPY} \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_real.syms \ + --redefine-syms={{ GASAN_SOURCE_DIR }}/rename_patch.syms \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.rel \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.renamed + +[unix] +link_zasan: rename_zasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_CC} \ + -shared \ + -nodefaultlibs \ + -nostartfiles \ + -nostdlib \ + -g \ + -u aligned_alloc \ + -u atoi \ + -u atol \ + -u atoll \ + -u calloc \ + -u free \ + -u malloc \ + -u malloc_usable_size \ + -u memalign \ + -u memset \ + -u mmap \ + -u munmap \ + -u posix_memalign \ + -u pvalloc \ + -u read \ + -u realloc \ + -u reallocarray \ + -u valloc \ + -u write \ + -Wl,--version-script={{ ZASAN_SOURCE_DIR }}/zasan.map \ + -Wl,--gc-sections \ + -Wl,--no-undefined \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libzasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.renamed + +[unix] +strip_zasan: link_zasan + #!/bin/bash + source {{ DOTENV }} + ${CROSS_STRIP} \ + --strip-unneeded \ + -o {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/libzasan.so \ + {{ TARGET_DIR }}/$CROSS_TARGET/{{ PROFILE_DIR }}/_libzasan.so + +[unix] +build_zasan: strip_zasan diff --git a/libafl_qemu/librasan/zasan/rename_patch.syms b/libafl_qemu/librasan/zasan/rename_patch.syms new file mode 100644 index 0000000000..1907e3e866 --- /dev/null +++ b/libafl_qemu/librasan/zasan/rename_patch.syms @@ -0,0 +1,19 @@ +patch_aligned_alloc aligned_alloc +patch_atoi atoi +patch_atol atol +patch_atoll atoll +patch_calloc calloc +patch_free free +patch_malloc malloc +patch_malloc_usable_size malloc_usable_size +patch_memalign memalign +patch_memset memset +patch_mmap mmap +patch_munmap munmap +patch_posix_memalign posix_memalign +patch_pvalloc pvalloc +patch_read read +patch_realloc realloc +patch_reallocarray reallocarray +patch_valloc valloc +patch_write write diff --git a/libafl_qemu/librasan/zasan/rename_real.syms b/libafl_qemu/librasan/zasan/rename_real.syms new file mode 100644 index 0000000000..80f90c6681 --- /dev/null +++ b/libafl_qemu/librasan/zasan/rename_real.syms @@ -0,0 +1,19 @@ +aligned_alloc real_aligned_alloc +atoi real_atoi +atol real_atol +atoll real_atoll +calloc real_calloc +free real_free +malloc real_malloc +malloc_usable_size real_malloc_usable_size +memalign real_memalign +memset real_memset +mmap real_mmap +munmap real_munmap +posix_memalign real_posix_memalign +pvalloc real_pvalloc +read real_read +realloc real_realloc +reallocarray real_reallocarray +valloc real_valloc +write real_write diff --git a/libafl_qemu/librasan/zasan/src/lib.rs b/libafl_qemu/librasan/zasan/src/lib.rs new file mode 100644 index 0000000000..41006c2879 --- /dev/null +++ b/libafl_qemu/librasan/zasan/src/lib.rs @@ -0,0 +1,161 @@ +#![no_std] +extern crate alloc; + +use core::ffi::{CStr, c_char, c_void}; + +use asan::{ + GuestAddr, + allocator::{ + backend::dlmalloc::DlmallocBackend, + frontend::{AllocatorFrontend, default::DefaultFrontend}, + }, + logger::linux::LinuxLogger, + mmap::linux::LinuxMmap, + shadow::{ + Shadow, + guest::{DefaultShadowLayout, GuestShadow}, + }, + symbols::{Symbols, nop::NopSymbols}, + tracking::{Tracking, guest::GuestTracking}, +}; +use log::{Level, trace}; +use spin::{Lazy, Mutex}; + +pub type ZasanFrontend = DefaultFrontend< + DlmallocBackend, + GuestShadow, + GuestTracking, +>; + +pub type ZasanSyms = NopSymbols; + +const PAGE_SIZE: usize = 4096; + +static FRONTEND: Lazy> = Lazy::new(|| { + LinuxLogger::initialize(Level::Info); + let backend = DlmallocBackend::::new(PAGE_SIZE); + let shadow = GuestShadow::::new().unwrap(); + let tracking = GuestTracking::new().unwrap(); + let frontend = ZasanFrontend::new( + backend, + shadow, + tracking, + ZasanFrontend::DEFAULT_REDZONE_SIZE, + ZasanFrontend::DEFAULT_QUARANTINE_SIZE, + ) + .unwrap(); + Mutex::new(frontend) +}); + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_load(addr: *const c_void, size: usize) { + trace!("load - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_store(addr: *const c_void, size: usize) { + trace!("store - addr: 0x{:x}, size: {:#x}", addr as GuestAddr, size); + if FRONTEND + .lock() + .shadow() + .is_poison(addr as GuestAddr, size) + .unwrap() + { + panic!("Poisoned - addr: {:p}, size: 0x{:x}", addr, size); + } +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_alloc(len: usize, align: usize) -> *mut c_void { + trace!("alloc - len: {:#x}, align: {:#x}", len, align); + let ptr = FRONTEND.lock().alloc(len, align).unwrap() as *mut c_void; + trace!( + "alloc - len: {:#x}, align: {:#x}, ptr: {:p}", + len, align, ptr + ); + ptr +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_dealloc(addr: *const c_void) { + trace!("free - addr: {:p}", addr); + FRONTEND.lock().dealloc(addr as GuestAddr).unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_get_size(addr: *const c_void) -> usize { + trace!("get_size - addr: {:p}", addr); + FRONTEND.lock().get_size(addr as GuestAddr).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_sym(name: *const c_char) -> GuestAddr { + ZasanSyms::lookup(name).unwrap() +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_page_size() -> usize { + PAGE_SIZE +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_unpoison(addr: *const c_void, len: usize) { + trace!("unpoison - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .shadow_mut() + .unpoison(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_track(addr: *const c_void, len: usize) { + trace!("track - addr: {:p}, len: {:#x}", addr, len); + FRONTEND + .lock() + .tracking_mut() + .alloc(addr as GuestAddr, len) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_untrack(addr: *const c_void) { + trace!("untrack - addr: {:p}", addr); + FRONTEND + .lock() + .tracking_mut() + .dealloc(addr as GuestAddr) + .unwrap(); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_panic(msg: *const c_char) -> ! { + trace!("panic - msg: {:p}", msg); + let msg = unsafe { CStr::from_ptr(msg as *const c_char) }; + panic!("{}", msg.to_str().unwrap()); +} + +#[unsafe(no_mangle)] +/// # Safety +pub unsafe extern "C" fn asan_swap(_enabled: bool) { + /* Don't log since this function is on the logging path */ +} diff --git a/libafl_qemu/librasan/zasan/zasan.map b/libafl_qemu/librasan/zasan/zasan.map new file mode 100644 index 0000000000..3ca036d47b --- /dev/null +++ b/libafl_qemu/librasan/zasan/zasan.map @@ -0,0 +1,25 @@ +{ + global: + aligned_alloc; + atoi; + atol; + atoll; + calloc; + free; + malloc; + malloc_usable_size; + memalign; + memset; + mmap; + munmap; + posix_memalign; + pvalloc; + read; + realloc; + reallocarray; + valloc; + write; + + local: + *; +}; diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 13007e2870..9e2bbbd0a8 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -6,6 +6,7 @@ use std::{ env, fmt::{Debug, Display}, fs, + path::PathBuf, pin::Pin, sync::Mutex, }; @@ -978,45 +979,61 @@ where { let mut args: Vec = qemu_params.to_cli(); - let current = env::current_exe().unwrap(); - let asan_lib = fs::canonicalize(current) - .unwrap() - .parent() - .unwrap() - .join("libqasan.so"); - let asan_lib = asan_lib - .to_str() - .expect("The path to the asan lib is invalid") - .to_string(); - let add_asan = - |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; + // Let the use skip preloading the ASAN DSO. Maybe they want to use + // their own implementation. + if env::var_os("SKIP_ASAN_LD_PRELOAD").is_none() { + let current = env::current_exe().unwrap(); + let asan_lib = fs::canonicalize(current) + .unwrap() + .parent() + .unwrap() + .join("libqasan.so"); - // TODO: adapt since qemu does not take envp anymore as parameter - let mut added = false; - for (k, v) in &mut self.env { - if k == "QEMU_SET_ENV" { - let mut new_v = vec![]; - for e in v.split(',') { - if e.starts_with("LD_PRELOAD=") { - added = true; - new_v.push(add_asan(e)); - } else { - new_v.push(e.to_string()); + let asan_lib = env::var_os("CUSTOM_QASAN_PATH") + .map_or(asan_lib, |x| PathBuf::from(x.to_string_lossy().to_string())); + + assert!( + asan_lib.as_path().exists(), + "The ASAN library doesn't exist: {asan_lib:#?}" + ); + + let asan_lib = asan_lib + .to_str() + .expect("The path to the asan lib is invalid") + .to_string(); + + println!("Loading ASAN: {asan_lib:}"); + + let add_asan = + |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; + + // TODO: adapt since qemu does not take envp anymore as parameter + let mut added = false; + for (k, v) in &mut self.env { + if k == "QEMU_SET_ENV" { + let mut new_v = vec![]; + for e in v.split(',') { + if e.starts_with("LD_PRELOAD=") { + added = true; + new_v.push(add_asan(e)); + } else { + new_v.push(e.to_string()); + } } + *v = new_v.join(","); } - *v = new_v.join(","); } - } - for i in 0..args.len() { - if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { - added = true; - args[i + 1] = add_asan(&args[i + 1]); + for i in 0..args.len() { + if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { + added = true; + args[i + 1] = add_asan(&args[i + 1]); + } } - } - if !added { - args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); - args.insert(1, "-E".into()); + if !added { + args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); + args.insert(1, "-E".into()); + } } unsafe { diff --git a/libafl_qemu/src/modules/usermode/asan_guest.rs b/libafl_qemu/src/modules/usermode/asan_guest.rs index 3a45a86a59..712a8c511d 100644 --- a/libafl_qemu/src/modules/usermode/asan_guest.rs +++ b/libafl_qemu/src/modules/usermode/asan_guest.rs @@ -1,14 +1,10 @@ #![allow(clippy::cast_possible_wrap)] -use std::{ - env, - fmt::{self, Debug, Formatter}, - fs, - path::PathBuf, -}; +use std::{env, fmt::Debug, fs, ops::Range, path::PathBuf}; use libafl_qemu_sys::{GuestAddr, MapInfo}; +use super::IntervalSnapshotFilter; #[cfg(not(feature = "clippy"))] use crate::sys::libafl_tcg_gen_asan; use crate::{ @@ -16,40 +12,19 @@ use crate::{ emu::EmulatorModules, modules::{ AddressFilter, EmulatorModule, EmulatorModuleTuple, + snapshot::IntervalSnapshotFilters, utils::filters::{HasAddressFilter, StdAddressFilter}, }, qemu::{Hook, MemAccessInfo, Qemu}, sys::TCGTemp, }; -#[derive(Clone)] -struct QemuAsanGuestMapping { - start: GuestAddr, - end: GuestAddr, - path: String, -} - -impl Debug for QemuAsanGuestMapping { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "0x{:016x}-0x{:016x} {}", self.start, self.end, self.path) - } -} - -impl From<&MapInfo> for QemuAsanGuestMapping { - fn from(map: &MapInfo) -> QemuAsanGuestMapping { - let path = map.path().map(ToString::to_string).unwrap_or_default(); - let start = map.start(); - let end = map.end(); - QemuAsanGuestMapping { start, end, path } - } -} - #[derive(Debug)] pub struct AsanGuestModule { env: Vec<(String, String)>, filter: F, - mappings: Option>, asan_lib: Option, + asan_mappings: Option>, } #[cfg(any( @@ -84,6 +59,20 @@ impl AsanGuestModule { pub fn default(env: &[(String, String)]) -> Self { Self::new(env, StdAddressFilter::default()) } + + #[must_use] + pub fn snapshot_filters() -> IntervalSnapshotFilters { + IntervalSnapshotFilters::from(vec![IntervalSnapshotFilter::ZeroList(vec![ + Range { + start: Self::LOW_SHADOW_START, + end: Self::LOW_SHADOW_END + 1, + }, + Range { + start: Self::HIGH_SHADOW_START, + end: Self::HIGH_SHADOW_END + 1, + }, + ])]) + } } impl AsanGuestModule @@ -95,8 +84,8 @@ where Self { env: env.to_vec(), filter, - mappings: None, asan_lib: None, + asan_mappings: None, } } @@ -127,12 +116,10 @@ where } /* Don't sanitize the sanitizer! */ - unsafe { - if h.mappings - .as_mut() - .unwrap_unchecked() + if let Some(asan_mappings) = &h.asan_mappings { + if asan_mappings .iter() - .any(|m| m.start <= pc && pc < m.end) + .any(|m| m.start() <= pc && pc < m.end()) { return None; } @@ -140,11 +127,9 @@ where let size = info.size(); - /* TODO - If our size is > 8 then do things via a runtime callback */ - assert!(size <= 8, "I shouldn't be here!"); - + // TODO: Handle larger load/store operations unsafe { - libafl_tcg_gen_asan(addr, size); + libafl_tcg_gen_asan(addr, size.min(8)); } None @@ -202,57 +187,65 @@ where { let mut args = qemu_params.to_cli(); - let current = env::current_exe().unwrap(); - let asan_lib = fs::canonicalize(current) - .unwrap() - .parent() - .unwrap() - .join("libgasan.so"); + // Let the use skip preloading the ASAN DSO. Maybe they want to use + // their own implementation. + let asan_lib = if env::var_os("SKIP_ASAN_LD_PRELOAD").is_none() { + let current = env::current_exe().unwrap(); + let asan_lib = fs::canonicalize(current) + .unwrap() + .parent() + .unwrap() + .join("libgasan.so"); - let asan_lib = env::var_os("CUSTOM_ASAN_PATH") - .map_or(asan_lib, |x| PathBuf::from(x.to_string_lossy().to_string())); + let asan_lib = env::var_os("CUSTOM_GASAN_PATH").map_or(asan_lib, |x| { + fs::canonicalize(PathBuf::from(x.to_string_lossy().to_string())).unwrap() + }); - assert!( - asan_lib.as_path().exists(), - "The ASAN library doesn't exist: {asan_lib:#?}" - ); + assert!( + asan_lib.as_path().exists(), + "The ASAN library doesn't exist: {asan_lib:#?}" + ); - let asan_lib = asan_lib - .to_str() - .expect("The path to the asan lib is invalid") - .to_string(); + let asan_lib = asan_lib + .to_str() + .expect("The path to the asan lib is invalid") + .to_string(); - println!("Loading ASAN: {asan_lib:}"); + println!("Loading ASAN: {asan_lib:}"); - let add_asan = - |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; + let add_asan = + |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; - let mut added = false; - for (k, v) in &mut self.env { - if k == "QEMU_SET_ENV" { - let mut new_v = vec![]; - for e in v.split(',') { - if e.starts_with("LD_PRELOAD=") { - added = true; - new_v.push(add_asan(e)); - } else { - new_v.push(e.to_string()); + let mut added = false; + for (k, v) in &mut self.env { + if k == "QEMU_SET_ENV" { + let mut new_v = vec![]; + for e in v.split(',') { + if e.starts_with("LD_PRELOAD=") { + added = true; + new_v.push(add_asan(e)); + } else { + new_v.push(e.to_string()); + } } + *v = new_v.join(","); } - *v = new_v.join(","); } - } - for i in 0..args.len() { - if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { - added = true; - args[i + 1] = add_asan(&args[i + 1]); + for i in 0..args.len() { + if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { + added = true; + args[i + 1] = add_asan(&args[i + 1]); + } } - } - if !added { - args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); - args.insert(1, "-E".into()); - } + if !added { + args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); + args.insert(1, "-E".into()); + } + Some(asan_lib) + } else { + None + }; if env::var("QASAN_DEBUG").is_ok() { args.push("-E".into()); @@ -266,7 +259,7 @@ where *qemu_params = QemuParams::Cli(args); - self.asan_lib = Some(asan_lib); + self.asan_lib = asan_lib; } fn post_qemu_init(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) @@ -285,37 +278,35 @@ where I: Unpin, S: Unpin, { - for mapping in qemu.mappings() { - println!("mapping: {mapping:#?}"); - } - - let mappings = qemu - .mappings() - .map(|m| QemuAsanGuestMapping::from(&m)) - .collect::>(); - + let mappings = qemu.mappings().collect::>(); for mapping in &mappings { - println!("guest mapping: {mapping:#?}"); + log::info!("mapping: {mapping:}"); } - mappings + let high_shadow = mappings .iter() - .find(|m| m.start <= Self::HIGH_SHADOW_START && m.end > Self::HIGH_SHADOW_END) + .find(|m| m.start() <= Self::HIGH_SHADOW_START && m.end() > Self::HIGH_SHADOW_END) .expect("HighShadow not found, confirm ASAN DSO is loaded in the guest"); + log::info!("high_shadow: {high_shadow:}"); - mappings + let low_shadow = mappings .iter() - .find(|m| m.start <= Self::LOW_SHADOW_START && m.end > Self::LOW_SHADOW_END) + .find(|m| m.start() <= Self::LOW_SHADOW_START && m.end() > Self::LOW_SHADOW_END) .expect("LowShadow not found, confirm ASAN DSO is loaded in the guest"); + log::info!("low_shadow: {low_shadow:}"); - let mappings = mappings - .iter() - .filter(|m| &m.path == self.asan_lib.as_ref().unwrap()) - .cloned() - .collect::>(); - - for mapping in &mappings { - println!("asan mapping: {mapping:#?}"); + if let Some(asan_lib) = &self.asan_lib { + let asan_mappings = mappings + .into_iter() + .filter(|m| match m.path() { + Some(p) => p == asan_lib, + None => false, + }) + .collect::>(); + for m in &asan_mappings { + log::info!("asan mapping: {m:}"); + } + self.asan_mappings = Some(asan_mappings); } emulator_modules.reads( diff --git a/libafl_qemu/src/modules/usermode/snapshot.rs b/libafl_qemu/src/modules/usermode/snapshot.rs index cee748f1b0..93ef8f303c 100644 --- a/libafl_qemu/src/modules/usermode/snapshot.rs +++ b/libafl_qemu/src/modules/usermode/snapshot.rs @@ -35,6 +35,7 @@ use crate::{SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_statfs}; // TODO use the functions provided by Qemu pub const SNAPSHOT_PAGE_SIZE: usize = 4096; +pub const SNAPSHOT_PAGE_ZEROES: [u8; SNAPSHOT_PAGE_SIZE] = [0; SNAPSHOT_PAGE_SIZE]; pub const SNAPSHOT_PAGE_MASK: GuestAddr = !(SNAPSHOT_PAGE_SIZE as GuestAddr - 1); pub type StopExecutionCallback = Box; @@ -79,10 +80,67 @@ pub struct MappingInfo { /// It is supposed to be used primarily for debugging, its usage is discouraged. /// If you end up needing it, you most likely have an issue with the snapshot system. /// If this is the case, please [fill in an issue on the main repository](https://github.com/AFLplusplus/LibAFL/issues). +#[derive(Clone, Debug)] pub enum IntervalSnapshotFilter { All, AllowList(Vec>), DenyList(Vec>), + ZeroList(Vec>), +} + +#[derive(Clone, Default, Debug)] +pub struct IntervalSnapshotFilters { + filters: Vec, +} + +impl From> for IntervalSnapshotFilters { + fn from(filters: Vec) -> Self { + Self { filters } + } +} + +impl IntervalSnapshotFilters { + #[must_use] + pub fn new() -> Self { + Self { + filters: Vec::new(), + } + } + + #[must_use] + pub fn to_skip(&self, addr: GuestAddr) -> Option<&Range> { + for filter in &self.filters { + match filter { + IntervalSnapshotFilter::All => return None, + IntervalSnapshotFilter::AllowList(allow_list) => { + if allow_list.iter().any(|range| range.contains(&addr)) { + return None; + } + } + IntervalSnapshotFilter::DenyList(deny_list) => { + let deny = deny_list.iter().find(|range| range.contains(&addr)); + if deny.is_some() { + return deny; + } + } + IntervalSnapshotFilter::ZeroList(_) => {} + } + } + None + } + + #[must_use] + pub fn to_zero(&self, addr: GuestAddr) -> Option<&Range> { + for filter in &self.filters { + if let IntervalSnapshotFilter::ZeroList(zero_list) = filter { + let zero = zero_list.iter().find(|range| range.contains(&addr)); + if zero.is_some() { + return zero; + } + } + } + None + } } pub struct SnapshotModule { @@ -97,7 +155,7 @@ pub struct SnapshotModule { pub stop_execution: Option, pub empty: bool, pub accurate_unmap: bool, - pub interval_filter: Vec, + pub interval_filter: IntervalSnapshotFilters, auto_reset: bool, } @@ -130,13 +188,13 @@ impl SnapshotModule { stop_execution: None, empty: true, accurate_unmap: false, - interval_filter: Vec::::new(), + interval_filter: IntervalSnapshotFilters::new(), auto_reset: true, } } #[must_use] - pub fn with_filters(interval_filter: Vec) -> Self { + pub fn with_filters(interval_filter: IntervalSnapshotFilters) -> Self { Self { accesses: ThreadLocal::new(), maps: MappingInfo::default(), @@ -168,7 +226,7 @@ impl SnapshotModule { stop_execution: Some(stop_execution), empty: true, accurate_unmap: false, - interval_filter: Vec::::new(), + interval_filter: IntervalSnapshotFilters::new(), auto_reset: true, } } @@ -181,25 +239,6 @@ impl SnapshotModule { self.auto_reset = false; } - pub fn to_skip(&self, addr: GuestAddr) -> bool { - for filter in &self.interval_filter { - match filter { - IntervalSnapshotFilter::All => return false, - IntervalSnapshotFilter::AllowList(allow_list) => { - if allow_list.iter().any(|range| range.contains(&addr)) { - return false; - } - } - IntervalSnapshotFilter::DenyList(deny_list) => { - if deny_list.iter().any(|range| range.contains(&addr)) { - return true; - } - } - } - } - false - } - pub fn snapshot(&mut self, qemu: Qemu) { log::info!("Start snapshot"); self.brk = qemu.get_brk(); @@ -209,8 +248,10 @@ impl SnapshotModule { for map in qemu.mappings() { let mut addr = map.start(); while addr < map.end() { - if self.to_skip(addr) { - addr += SNAPSHOT_PAGE_SIZE as GuestAddr; + let zero = self.interval_filter.to_zero(addr); + let skip = self.interval_filter.to_skip(addr); + if let Some(range) = zero.or(skip) { + addr = range.end; continue; } let mut info = SnapshotPageInfo { @@ -291,8 +332,10 @@ impl SnapshotModule { let mut addr = map.start(); // assert_eq!(addr & SNAPSHOT_PAGE_MASK, 0); while addr < map.end() { - if self.to_skip(addr) { - addr += SNAPSHOT_PAGE_SIZE as GuestAddr; + let zero = self.interval_filter.to_zero(addr); + let skip = self.interval_filter.to_skip(addr); + if let Some(range) = zero.or(skip) { + addr = range.end; continue; } if let Some(saved_page) = saved_pages_list.remove(&addr) { @@ -440,28 +483,15 @@ impl SnapshotModule { for acc in &mut self.accesses { unsafe { &mut (*acc.get()) }.dirty.retain(|page| { if let Some(info) = self.pages.get_mut(page) { - // TODO avoid duplicated memcpy - if let Some(data) = info.data.as_ref() { - // Change segment perms to RW if not writeable in current mapping - let mut found = false; - for entry in new_maps - .tree - .query_mut(*page..(page + SNAPSHOT_PAGE_SIZE as GuestAddr)) - { - if !entry.value.perms.unwrap_or(MmapPerms::None).writable() { - qemu.mprotect( - entry.interval.start, - (entry.interval.end - entry.interval.start) as usize, - MmapPerms::ReadWrite, - ) - .unwrap(); - entry.value.changed = true; - entry.value.perms = Some(MmapPerms::ReadWrite); - } - found = true; + if self.interval_filter.to_skip(*page).is_some() { + if !Self::modify_mapping(qemu, new_maps, *page) { + return true; // Restore later } - - if !found { + unsafe { qemu.write_mem_unchecked(*page, &SNAPSHOT_PAGE_ZEROES) }; + } else if let Some(data) = info.data.as_ref() { + // TODO avoid duplicated memcpy + // Change segment perms to RW if not writeable in current mapping + if !Self::modify_mapping(qemu, new_maps, *page) { return true; // Restore later } @@ -498,7 +528,9 @@ impl SnapshotModule { } } - if let Some(info) = self.pages.get_mut(page) { + if self.interval_filter.to_skip(*page).is_some() { + unsafe { qemu.write_mem_unchecked(*page, &SNAPSHOT_PAGE_ZEROES) }; + } else if let Some(info) = self.pages.get_mut(page) { // TODO avoid duplicated memcpy if let Some(data) = info.data.as_ref() { unsafe { qemu.write_mem_unchecked(*page, &data[..]) }; @@ -531,6 +563,26 @@ impl SnapshotModule { log::debug!("End restore"); } + fn modify_mapping(qemu: Qemu, maps: &mut MappingInfo, page: GuestAddr) -> bool { + let mut found = false; + for entry in maps + .tree + .query_mut(page..(page + SNAPSHOT_PAGE_SIZE as GuestAddr)) + { + if !entry.value.perms.unwrap_or(MmapPerms::None).writable() { + drop(qemu.mprotect( + entry.interval.start, + (entry.interval.end - entry.interval.start) as usize, + MmapPerms::ReadWrite, + )); + entry.value.changed = true; + entry.value.perms = Some(MmapPerms::ReadWrite); + } + found = true; + } + found + } + pub fn is_unmap_allowed(&mut self, start: GuestAddr, mut size: usize) -> bool { if size % SNAPSHOT_PAGE_SIZE != 0 { size = size + (SNAPSHOT_PAGE_SIZE - size % SNAPSHOT_PAGE_SIZE);