global-state-detector 0.1.0

Detect persistent writable global state between fuzzer iterations (Rust bindings for the global-state-detector C library)
Documentation
//! Detect persistent writable state between fuzzer iterations.
//!
//! Rust bindings for the [`global-state-detector`] C library. Operates
//! on writable, non-executable ELF `PT_LOAD` segments (`.data` / `.bss`)
//! of the main binary and every loaded shared object, filtering out
//! libc / ld-linux / libpthread / libstdc++ / vDSO noise.
//!
//! # Usage
//!
//! Call [`init`] once after any one-time target initialization is done.
//! Then, on each fuzzer iteration, [`rebaseline`] just before invoking
//! the target and [`check`] just after. That pattern attributes drift
//! to the target rather than to the fuzzer's own bookkeeping between
//! callbacks.
//!
//! # Required linker flags for the final binary
//!
//! Cargo does not propagate `rustc-link-arg` from rlib dependencies, so
//! consumers must arrange for the linker to receive `-rdynamic` and
//! `-Wl,-z,now` themselves. The simplest way is a `.cargo/config.toml`
//! in the consuming project (or its `fuzz/` subdirectory):
//!
//! ```toml
//! [target.'cfg(target_os = "linux")']
//! rustflags = ["-C", "link-arg=-rdynamic", "-C", "link-arg=-Wl,-z,now"]
//! ```
//!
//! Without `-rdynamic`, symbol names in the main executable are not
//! resolvable via `dladdr` and reports show `?+0x...` instead of real
//! symbols. Without `-Wl,-z,now`, lazy PLT/GOT binding produces noise
//! on the first iteration.
//!
//! # Rust-specific caveats
//!
//! * `static` and `static mut` live in `.data`/`.bss` — fully covered.
//! * `AtomicU*`, `Mutex<T>` (the lock word), `RwLock` — covered.
//! * `OnceCell`, `OnceLock`, `LazyLock`, `lazy_static!` — only the
//!   pointer / discriminant in `.bss` is visible. The actual heap-
//!   allocated payload is NOT tracked. You will see "this static was
//!   first-used in this iteration" but not what value it took.
//! * `thread_local!` lives in TLS, not `.data`/`.bss` — not tracked
//!   here. TLS is per-thread anyway, less of a fuzzing concern.
//! * Symbols come out Rust-mangled (`_ZN8...` / `_R...`). Pipe stderr
//!   through `rustfilt` to demangle:
//!       ./fuzzer 2>&1 | rustfilt
//!   (cargo install rustfilt)
//!
//! # Thread safety
//!
//! The underlying C implementation is not thread-safe. Use it from a
//! single-threaded harness, or add external synchronization.
//!
//! [`global-state-detector`]: https://github.com/AFLplusplus/global-state-detector

use std::os::raw::c_int;

extern "C" {
    fn global_state_detector_init();
    fn global_state_detector_check(rebaseline: c_int) -> c_int;
    fn global_state_detector_rebaseline();
}

/// Snapshot all writable `PT_LOAD` segments of the main binary and
/// every currently loaded shared object.
///
/// Call once after the target has been fully initialized (i.e. after
/// any one-time setup your harness performs).
pub fn init() {
    unsafe { global_state_detector_init() }
}

/// Diff current memory against the last snapshot.
///
/// Returns the number of pages that changed. If `rebaseline` is true,
/// the snapshot is updated to the current state so the next call only
/// shows new deltas; pass `false` to keep comparing against the
/// previous snapshot for cumulative drift.
pub fn check(rebaseline: bool) -> i32 {
    unsafe { global_state_detector_check(rebaseline as c_int) }
}

/// Force a full re-snapshot without reporting any diffs.
pub fn rebaseline() {
    unsafe { global_state_detector_rebaseline() }
}