libafl_targets 0.15.3

Common code for target instrumentation that can be used combined with LibAFL
Documentation
//! build.rs for `libafl_targets`

use std::{env, fs::File, io::Write, path::Path};

const TWO_MB: usize = 2_621_440;
const SIXTY_FIVE_KB: usize = 65_536;

#[rustversion::nightly]
fn enable_nightly() {
    println!("cargo:rustc-cfg=nightly");
}

#[rustversion::not(nightly)]
fn enable_nightly() {}

#[expect(clippy::too_many_lines)]
fn main() {
    println!("cargo:rustc-check-cfg=cfg(nightly)");
    enable_nightly();
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let out_dir = out_dir.to_string_lossy().to_string();
    //let out_dir_path = Path::new(&out_dir);
    #[allow(unused_variables)] // feature dependent
    let src_dir = Path::new("src");

    let dest_path = Path::new(&out_dir).join("constants.rs");
    let mut constants_file = File::create(dest_path).expect("Could not create file");

    let edges_map_allocated_size: usize = option_env!("LIBAFL_EDGES_MAP_ALLOCATED_SIZE")
        .or(option_env!("LIBAFL_EDGES_MAP_ALLOCATED_SIZE")) // keep old env for retrocompatibility
        .map_or(Ok(TWO_MB), str::parse)
        .expect("Could not parse LIBAFL_EDGES_MAP_ALLOCATED_SIZE");

    let edges_map_default_size: usize = option_env!("LIBAFL_EDGES_MAP_DEFAULT_SIZE")
        .or(option_env!("LIBAFL_EDGES_MAP_DEFAULT_SIZE")) // keep old env for retrocompatibility
        .map_or(Ok(SIXTY_FIVE_KB), str::parse)
        .expect("Could not parse LIBAFL_EDGES_MAP_DEFAULT_SIZE");

    let cmp_map_size: usize = option_env!("LIBAFL_CMP_MAP_SIZE")
        .map_or(Ok(SIXTY_FIVE_KB), str::parse)
        .expect("Could not parse LIBAFL_CMP_MAP_SIZE");

    let cmplog_map_w: usize = option_env!("LIBAFL_CMPLOG_MAP_W")
        .map_or(Ok(SIXTY_FIVE_KB), str::parse)
        .expect("Could not parse LIBAFL_CMPLOG_MAP_W");

    let cmplog_map_h: usize = option_env!("LIBAFL_CMPLOG_MAP_H")
        .map_or(Ok(32), str::parse)
        .expect("Could not parse LIBAFL_CMPLOG_MAP_H");

    let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE")
        .map_or(Ok(SIXTY_FIVE_KB), str::parse)
        .expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE");

    assert!(edges_map_default_size <= edges_map_allocated_size);
    assert!(edges_map_default_size.is_power_of_two());

    write!(
        constants_file,
        "// These constants are autogenerated by build.rs

        /// The default size of the edges map the fuzzer uses
        pub const EDGES_MAP_DEFAULT_SIZE: usize = {edges_map_default_size};
        /// The real allocated size of the edges map
        pub const EDGES_MAP_ALLOCATED_SIZE: usize = {edges_map_allocated_size};
        /// The size of the cmps map
        pub const CMP_MAP_SIZE: usize = {cmp_map_size};
        /// The width of the `CmpLog` map
        pub const CMPLOG_MAP_W: usize = {cmplog_map_w};
        /// The height of the `CmpLog` map
        pub const CMPLOG_MAP_H: usize = {cmplog_map_h};
        /// The size of the accounting maps
        pub const ACCOUNTING_MAP_SIZE: usize = {acc_map_size};
"
    )
    .expect("Could not write file");

    println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_DEFAULT_SIZE");
    println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_DEFAULT_SIZE");
    println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_ALLOCATED_SIZE");
    println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_ALLOCATED_SIZE");
    println!("cargo:rerun-if-env-changed=LIBAFL_CMP_MAP_SIZE");
    println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_W");
    println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_H");
    println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE");

    #[cfg(feature = "common")]
    {
        println!("cargo:rerun-if-changed=src/common.h");
        println!("cargo:rerun-if-changed=src/common.c");

        let mut common = cc::Build::new();

        #[cfg(feature = "sanitizers_flags")]
        {
            common.define("DEFAULT_SANITIZERS_OPTIONS", "1");
        }

        #[cfg(feature = "whole_archive")]
        {
            common.link_lib_modifier("+whole-archive");
        }

        common.file(src_dir.join("common.c")).compile("common");
    }

    #[cfg(any(feature = "sancov_value_profile", feature = "sancov_cmplog"))]
    {
        println!("cargo:rerun-if-changed=src/sancov_cmp.c");

        let mut sancov_cmp = cc::Build::new();

        #[cfg(unix)]
        sancov_cmp.flag("-Wno-sign-compare");

        #[cfg(feature = "sancov_value_profile")]
        {
            sancov_cmp.define("SANCOV_VALUE_PROFILE", "1");
            println!("cargo:rerun-if-changed=src/value_profile.h");
        }

        #[cfg(feature = "sancov_cmplog")]
        {
            sancov_cmp.define("SANCOV_CMPLOG", "1");

            println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_weak_hook_memcmp");
            println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_weak_hook_strncmp");
            println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_weak_hook_strncasecmp");
            println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_weak_hook_strcmp");
            println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_weak_hook_strcasecmp");
        }

        #[cfg(feature = "whole_archive")]
        {
            sancov_cmp.link_lib_modifier("+whole-archive");
        }

        sancov_cmp
            .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}")))
            .define("CMPLOG_MAP_W", Some(&*format!("{cmplog_map_w}")))
            .define("CMPLOG_MAP_H", Some(&*format!("{cmplog_map_h}")))
            .file(src_dir.join("sancov_cmp.c"))
            .compile("sancov_cmp");

        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_cmp1");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_cmp2");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_cmp4");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_cmp8");

        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_const_cmp1");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_const_cmp2");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_const_cmp4");
        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_const_cmp8");

        println!("cargo:rustc-link-arg=-Wl,--undefined=__sanitizer_cov_trace_switch");
    }

    #[cfg(feature = "libfuzzer")]
    {
        println!("cargo:rerun-if-changed=src/libfuzzer.c");

        let mut libfuzzer = cc::Build::new();
        libfuzzer.file(src_dir.join("libfuzzer.c"));

        #[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");

        #[cfg(feature = "whole_archive")]
        {
            libfuzzer.link_lib_modifier("+whole-archive");
        }

        libfuzzer.compile("libfuzzer");
    }

    #[cfg(feature = "coverage")]
    {
        println!("cargo:rerun-if-changed=src/coverage.c");

        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_ALLOCATED_SIZE",
                Some(&*format!("{edges_map_allocated_size}")),
            )
            .define("ACCOUNTING_MAP_SIZE", Some(&*format!("{acc_map_size}")))
            .compile("coverage");
    }

    #[cfg(feature = "cmplog")]
    {
        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 = "cmplog_extended_instrumentation")]
            cmplog.define("CMPLOG_EXTENDED", Some("1"));

            #[cfg(feature = "whole_archive")]
            {
                cmplog.link_lib_modifier("+whole-archive");
            }

            cmplog
                .flag("-Wno-pointer-sign") // UNIX ONLY FLAGS
                .flag("-Wno-sign-compare")
                .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}")))
                .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(not(unix))]
        {
            let mut cmplog = cc::Build::new();

            #[cfg(feature = "whole_archive")]
            {
                cmplog.link_lib_modifier("+whole-archive");
            }

            cmplog
                .define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}")))
                .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(feature = "windows_asan")]
    {
        let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
        if target_family == "windows" {
            println!("cargo:rerun-if-changed=src/windows_asan.c");

            let mut windows_asan = cc::Build::new();

            #[cfg(feature = "whole_archive")]
            {
                windows_asan.link_lib_modifier("+whole-archive");
            }

            windows_asan
                .file(src_dir.join("windows_asan.c"))
                .compile("windows_asan");
        }
    }

    // NOTE: Sanitizer interfaces doesn't require common
    #[cfg(feature = "sanitizer_interfaces")]
    if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "64" {
        println!("cargo:rerun-if-changed=src/sanitizer_interfaces.h");

        let build = bindgen::builder()
            .header("src/sanitizer_interfaces.h")
            .use_core()
            .generate_comments(true)
            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
            .generate()
            .expect("Couldn't generate the sanitizer headers!");

        build
            .write_to_file(Path::new(&out_dir).join("sanitizer_interfaces.rs"))
            .expect("Couldn't write the sanitizer headers!");
    } else {
        let mut file = File::create(Path::new(&out_dir).join("sanitizer_interfaces.rs"))
            .expect("Could not create file");
        write!(file, "").unwrap();
    }

    #[cfg(feature = "libfuzzer_interceptors")]
    {
        println!("cargo:rerun-if-changed=src/libfuzzer/FuzzerInterceptors.cpp");

        let mut libfuzzer_interceptors = cc::Build::new();
        libfuzzer_interceptors.file(src_dir.join("libfuzzer/FuzzerInterceptors.cpp"));

        #[cfg(feature = "whole_archive")]
        {
            libfuzzer_interceptors.link_lib_modifier("+whole-archive");
        }

        libfuzzer_interceptors.cpp(true).compile("interceptors");
    }

    println!("cargo:rustc-link-search=native={}", &out_dir);

    println!("cargo:rerun-if-changed=build.rs");
}