diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a0eb67fc1a..e79bf28a7d 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -145,7 +145,7 @@ jobs: # Skipping `python` as it has to be built with the `maturin` tool # `agpl`, `nautilus` require nightly # `sancov_pcguard_edges` is tested seperately - run: LLVM_CONFIG=llvm-config-15 cargo hack check --each-feature --clean-per-run --exclude-features=prelude,agpl,nautilus,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode --no-dev-deps + run: LLVM_CONFIG=llvm-config-15 cargo hack check --workspace --each-feature --clean-per-run --exclude-features=prelude,agpl,nautilus,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive --no-dev-deps --exclude libafl_libfuzzer - name: Check nightly features run: cargo +nightly check --features=agpl && cargo +nightly check --features=nautilus diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml index b20aa4c1bc..e7824bf5a7 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml @@ -32,7 +32,7 @@ crate-type = ["staticlib", "rlib"] [dependencies] libafl = { path = "../../libafl", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "regex", "errors_backtrace", "serdeany_autoreg", "tui_monitor"] } libafl_bolts = { path = "../../libafl_bolts", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "serdeany_autoreg", "errors_backtrace"] } -libafl_targets = { path = "../../libafl_targets", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "sanitizers_flags"] } +libafl_targets = { path = "../../libafl_targets", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "libfuzzer_interceptors", "sanitizers_flags", "whole_archive"] } ahash = { version = "0.8.3", default-features = false } libc = "0.2.139" diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index 1891f08949..9205f25ca3 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -21,6 +21,7 @@ std = ["libafl/std"] libfuzzer = ["std", "sanitizer_interfaces"] libfuzzer_no_link_main = ["libfuzzer"] libfuzzer_define_run_driver = ["libfuzzer"] +libfuzzer_interceptors = ["libfuzzer", "sancov_cmplog"] libfuzzer_oom = ["libfuzzer"] sanitizers_flags = [] pointer_maps = [] @@ -28,15 +29,17 @@ sancov_pcguard_edges = [] sancov_pcguard_hitcounts = [] sancov_value_profile = [] sancov_8bit = [] -sancov_cmplog = [] +sancov_cmplog = [] # Defines cmp and __sanitizer_weak_hook functions. Use libfuzzer_interceptors to define interceptors (only compatible with Linux) sancov_pcguard = ["sancov_pcguard_hitcounts"] sanitizer_interfaces = [] clippy = [] # Ignore compiler warnings during clippy observers = ["meminterval", "ahash"] +whole_archive = [] # use +whole-archive to ensure the presence of weak symbols [build-dependencies] bindgen = "0.68" cc = { version = "1.0", features = ["parallel"] } +rustversion = "1.0" [dependencies] libafl = { path = "../libafl", version = "0.11.1", default-features = false, features = [] } diff --git a/libafl_targets/build.rs b/libafl_targets/build.rs index 8ec21c937a..e0cfc44824 100644 --- a/libafl_targets/build.rs +++ b/libafl_targets/build.rs @@ -2,6 +2,10 @@ use std::{env, fs::File, io::Write, path::Path}; +#[cfg(feature = "whole_archive")] +#[rustversion::not(nightly)] +compile_error!("Must use a nightly release with whole_archive!"); + #[allow(clippy::too_many_lines)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); @@ -72,6 +76,10 @@ fn main() { #[cfg(unix)] sancov_cmp.flag("-Wno-sign-compare"); + #[cfg(feature = "whole_archive")] + { + sancov_cmp.link_lib_modifier("+whole-archive"); + } #[cfg(feature = "sancov_value_profile")] { @@ -125,12 +133,32 @@ fn main() { let mut libfuzzer = cc::Build::new(); libfuzzer.file(src_dir.join("libfuzzer.c")); + #[cfg(feature = "whole_archive")] + { + libfuzzer.link_lib_modifier("+whole-archive"); + } + #[cfg(feature = "libfuzzer_no_link_main")] libfuzzer.define("FUZZER_NO_LINK_MAIN", "1"); #[cfg(feature = "libfuzzer_define_run_driver")] libfuzzer.define("FUZZER_DEFINE_RUN_DRIVER", "1"); libfuzzer.compile("libfuzzer"); + + #[cfg(feature = "libfuzzer_interceptors")] + { + println!("cargo:rerun-if-changed=src/libfuzzer/FuzzerInterceptors.cpp"); + + let mut interceptors = cc::Build::new(); + interceptors.file(src_dir.join("libfuzzer/FuzzerInterceptors.cpp")); + + #[cfg(feature = "whole_archive")] + { + interceptors.link_lib_modifier("+whole-archive"); + } + + interceptors.cpp(true).compile("interceptors"); + } } println!("cargo:rerun-if-changed=src/common.h"); @@ -159,6 +187,11 @@ fn main() { let mut common = cc::Build::new(); + #[cfg(feature = "whole_archive")] + { + common.link_lib_modifier("+whole-archive"); + } + #[cfg(feature = "sanitizers_flags")] { common.define("DEFAULT_SANITIZERS_OPTIONS", "1"); @@ -168,7 +201,14 @@ fn main() { println!("cargo:rerun-if-changed=src/coverage.c"); - cc::Build::new() + let mut coverage = cc::Build::new(); + + #[cfg(feature = "whole_archive")] + { + coverage.link_lib_modifier("+whole-archive"); + } + + coverage .file(src_dir.join("coverage.c")) .define("EDGES_MAP_SIZE", Some(&*format!("{edges_map_size}"))) .define("ACCOUNTING_MAP_SIZE", Some(&*format!("{acc_map_size}"))) @@ -177,49 +217,47 @@ fn main() { println!("cargo:rerun-if-changed=src/cmplog.h"); println!("cargo:rerun-if-changed=src/cmplog.c"); - #[cfg(unix)] + let mut cmplog = cc::Build::new(); + + #[cfg(feature = "whole_archive")] { - cc::Build::new() - .flag("-Wno-pointer-sign") // UNIX ONLY FLAGS - .flag("-Wno-sign-compare") - .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}"))) - .define( - "AFLPP_CMPLOG_MAP_W", - Some(&*format!("{aflpp_cmplog_map_w}")), - ) - .define( - "AFLPP_CMPLOG_MAP_H", - Some(&*format!("{aflpp_cmplog_map_h}")), - ) - .define("CMPLOG_MAP_W", Some(&*format!("{cmplog_map_w}"))) - .define("CMPLOG_MAP_H", Some(&*format!("{cmplog_map_h}"))) - .file(src_dir.join("cmplog.c")) - .compile("cmplog"); + cmplog.link_lib_modifier("+whole-archive"); } - #[cfg(not(unix))] + #[cfg(unix)] { - cc::Build::new() - .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}"))) - .define( - "AFLPP_CMPLOG_MAP_W", - Some(&*format!("{aflpp_cmplog_map_w}")), - ) - .define( - "AFLPP_CMPLOG_MAP_H", - Some(&*format!("{aflpp_cmplog_map_h}")), - ) - .define("CMPLOG_MAP_W", Some(&*format!("{cmplog_map_w}"))) - .define("CMPLOG_MAP_H", Some(&*format!("{cmplog_map_h}"))) - .file(src_dir.join("cmplog.c")) - .compile("cmplog"); + cmplog + .flag("-Wno-pointer-sign") // UNIX ONLY FLAGS + .flag("-Wno-sign-compare"); } + cmplog + .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}"))) + .define( + "AFLPP_CMPLOG_MAP_W", + Some(&*format!("{aflpp_cmplog_map_w}")), + ) + .define( + "AFLPP_CMPLOG_MAP_H", + Some(&*format!("{aflpp_cmplog_map_h}")), + ) + .define("CMPLOG_MAP_W", Some(&*format!("{cmplog_map_w}"))) + .define("CMPLOG_MAP_H", Some(&*format!("{cmplog_map_h}"))) + .file(src_dir.join("cmplog.c")) + .compile("cmplog"); + #[cfg(unix)] { println!("cargo:rerun-if-changed=src/forkserver.c"); - cc::Build::new() + let mut forkserver = cc::Build::new(); + + #[cfg(feature = "whole_archive")] + { + forkserver.link_lib_modifier("+whole-archive"); + } + + forkserver .file(src_dir.join("forkserver.c")) .compile("forkserver"); } @@ -228,7 +266,14 @@ fn main() { { println!("cargo:rerun-if-changed=src/windows_asan.c"); - cc::Build::new() + let mut winasan = cc::Build::new(); + + #[cfg(feature = "whole_archive")] + { + winasan.link_lib_modifier("+whole-archive"); + } + + winasan .file(src_dir.join("windows_asan.c")) .compile("windows_asan"); } diff --git a/libafl_targets/src/lib.rs b/libafl_targets/src/lib.rs index 546a99d2cb..2c08f15cbd 100644 --- a/libafl_targets/src/lib.rs +++ b/libafl_targets/src/lib.rs @@ -1,6 +1,4 @@ //! `libafl_targets` contains runtime code, injected in the target itself during compilation. -//! -//! #![no_std] #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::all)] diff --git a/libafl_targets/src/libfuzzer/FuzzerInterceptors.cpp b/libafl_targets/src/libfuzzer/FuzzerInterceptors.cpp new file mode 100644 index 0000000000..e6dee99f42 --- /dev/null +++ b/libafl_targets/src/libfuzzer/FuzzerInterceptors.cpp @@ -0,0 +1,234 @@ +// modified from: +// https://raw.githubusercontent.com/llvm/llvm-project/5cda4dc7b4d28fcd11307d4234c513ff779a1c6f/compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp + +//===-- FuzzerInterceptors.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Intercept certain libc functions to aid fuzzing. +// Linked only when other RTs that define their own interceptors are not linked. +//===----------------------------------------------------------------------===// + +#include "../common.h" + +#if defined(__linux__) + + #define ATTRIBUTE_INTERFACE __attribute__((visibility("default"))) + + #define GET_CALLER_PC() __builtin_return_address(0) + + #define PTR_TO_REAL(x) real_##x + #define REAL(x) __interception::PTR_TO_REAL(x) + #define FUNC_TYPE(x) x##_type + #define DEFINE_REAL(ret_type, func, ...) \ + typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + FUNC_TYPE(func) PTR_TO_REAL(func); \ + } + + #include + #include + #include // for dlsym() + +static void *getFuncAddr(const char *name, uintptr_t wrapper_addr) { + void *addr = dlsym(RTLD_NEXT, name); + if (!addr) { + // If the lookup using RTLD_NEXT failed, the sanitizer runtime library is + // later in the library search order than the DSO that we are trying to + // intercept, which means that we cannot intercept this function. We still + // want the address of the real definition, though, so look it up using + // RTLD_DEFAULT. + addr = dlsym(RTLD_DEFAULT, name); + + // In case `name' is not loaded, dlsym ends up finding the actual wrapper. + // We don't want to intercept the wrapper and have it point to itself. + if (reinterpret_cast(addr) == wrapper_addr) addr = nullptr; + } + return addr; +} + +static int FuzzerInited = 0; +static bool FuzzerInitIsRunning; + +static void fuzzerInit(); + +static void ensureFuzzerInited() { + assert(!FuzzerInitIsRunning); + if (!FuzzerInited) { fuzzerInit(); } +} + +static int internal_strcmp_strncmp(const char *s1, const char *s2, bool strncmp, + size_t n) { + size_t i = 0; + while (true) { + if (strncmp) { + if (i == n) break; + i++; + } + unsigned c1 = *s1; + unsigned c2 = *s2; + if (c1 != c2) return (c1 < c2) ? -1 : 1; + if (c1 == 0) break; + s1++; + s2++; + } + return 0; +} + +static int internal_strncmp(const char *s1, const char *s2, size_t n) { + return internal_strcmp_strncmp(s1, s2, true, n); +} + +static int internal_strcmp(const char *s1, const char *s2) { + return internal_strcmp_strncmp(s1, s2, false, 0); +} + +static int internal_memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *t1 = static_cast(s1); + const uint8_t *t2 = static_cast(s2); + for (size_t i = 0; i < n; ++i, ++t1, ++t2) + if (*t1 != *t2) return *t1 < *t2 ? -1 : 1; + return 0; +} + +extern "C" { + +// Weak hooks forward-declared to avoid dependency on +// . +void __sanitizer_weak_hook_memcmp(void *called_pc, const void *s1, + const void *s2, size_t n, int result); +void __sanitizer_weak_hook_strncmp(void *called_pc, const char *s1, + const char *s2, size_t n, int result); +void __sanitizer_weak_hook_strncasecmp(void *called_pc, const char *s1, + const char *s2, size_t n, int result); +void __sanitizer_weak_hook_strcmp(void *called_pc, const char *s1, + const char *s2, int result); +void __sanitizer_weak_hook_strcasecmp(void *called_pc, const char *s1, + const char *s2, int result); +/* +void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1, + const char *s2, char *result); +void __sanitizer_weak_hook_strcasestr(void *called_pc, const char *s1, + const char *s2, char *result); +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result); +*/ + +DEFINE_REAL(int, bcmp, const void *, const void *, size_t) +DEFINE_REAL(int, memcmp, const void *, const void *, size_t) +DEFINE_REAL(int, strncmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcmp, const char *, const char *) +DEFINE_REAL(int, strncasecmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcasecmp, const char *, const char *) +/* +DEFINE_REAL(char *, strstr, const char *, const char *) +DEFINE_REAL(char *, strcasestr, const char *, const char *) +DEFINE_REAL(void *, memmem, const void *, size_t, const void *, size_t) +*/ + +ATTRIBUTE_INTERFACE int bcmp(const void *s1, const void *s2, size_t n) { + if (!FuzzerInited) return internal_memcmp(s1, s2, n); + int result = REAL(bcmp)(s1, s2, n); + __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int memcmp(const void *s1, const void *s2, size_t n) { + if (!FuzzerInited) return internal_memcmp(s1, s2, n); + int result = REAL(memcmp)(s1, s2, n); + __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) return internal_strncmp(s1, s2, n); + int result = REAL(strncmp)(s1, s2, n); + __sanitizer_weak_hook_strncmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcmp(const char *s1, const char *s2) { + if (!FuzzerInited) return internal_strcmp(s1, s2); + int result = REAL(strcmp)(s1, s2); + __sanitizer_weak_hook_strcmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncasecmp(const char *s1, const char *s2, size_t n) { + ensureFuzzerInited(); + int result = REAL(strncasecmp)(s1, s2, n); + __sanitizer_weak_hook_strncasecmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcasecmp(const char *s1, const char *s2) { + ensureFuzzerInited(); + int result = REAL(strcasecmp)(s1, s2); + __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +/* +ATTRIBUTE_INTERFACE char *strstr(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strstr(s1, s2); + char *result = REAL(strstr)(s1, s2); + __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE char *strcasestr(const char *s1, const char *s2) { + ensureFuzzerInited(); + char *result = REAL(strcasestr)(s1, s2); + __sanitizer_weak_hook_strcasestr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE +void *memmem(const void *s1, size_t len1, const void *s2, size_t len2) { + ensureFuzzerInited(); + void *result = REAL(memmem)(s1, len1, s2, len2); + __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result); + return result; +} +*/ + +__attribute__((section(".preinit_array"), + used)) static void (*__local_fuzzer_preinit)(void) = fuzzerInit; + +} // extern "C" + +static void fuzzerInit() { + assert(!FuzzerInitIsRunning); + if (FuzzerInited) return; + FuzzerInitIsRunning = true; + + REAL(bcmp) = reinterpret_cast( + getFuncAddr("bcmp", reinterpret_cast(&bcmp))); + REAL(memcmp) = reinterpret_cast( + getFuncAddr("memcmp", reinterpret_cast(&memcmp))); + REAL(strncmp) = reinterpret_cast( + getFuncAddr("strncmp", reinterpret_cast(&strncmp))); + REAL(strcmp) = reinterpret_cast( + getFuncAddr("strcmp", reinterpret_cast(&strcmp))); + REAL(strncasecmp) = reinterpret_cast( + getFuncAddr("strncasecmp", reinterpret_cast(&strncasecmp))); + REAL(strcasecmp) = reinterpret_cast( + getFuncAddr("strcasecmp", reinterpret_cast(&strcasecmp))); + /* + REAL(strstr) = reinterpret_cast( + getFuncAddr("strstr", reinterpret_cast(&strstr))); + REAL(strcasestr) = reinterpret_cast( + getFuncAddr("strcasestr", reinterpret_cast(&strcasestr))); + REAL(memmem) = reinterpret_cast( + getFuncAddr("memmem", reinterpret_cast(&memmem))); + */ + + FuzzerInitIsRunning = false; + FuzzerInited = 1; +} + +#endif