Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ jobs:
strategy:
fail-fast: false
matrix:
build: &default-build
build:
- &debug-build
name: "debug"
profile: "debug"
Expand Down Expand Up @@ -335,7 +335,19 @@ jobs:
- frr.dataplane
- dataplane
- validator
build: *default-build
build:
- name: "debug"
profile: "debug"
sanitize: ""
instrument: "none"
- name: "release"
profile: "release"
sanitize: ""
instrument: "none"
- name: "thread"
profile: "fuzz"
sanitize: "thread"
instrument: "none"
Comment on lines +352 to +355
exclude:
- nix-target: validator
build:
Expand Down
53 changes: 31 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions dataplane/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Open Network Fabric Authors

/// Detects which `-Zsanitizer=<kind>` (if any) is active and re-exports it as
/// an ordinary `cfg`.
///
/// We cannot read the built-in `cfg(sanitize = "...")` from source without the
/// (currently) "unstable" `cfg_sanitize` feature, and gating the whole binary on
/// a nightly feature would break a plain `cargo build` on stable. Sniffing the
/// rustflags here keeps the crate stable-buildable while still letting
/// `src/sanitizer.rs` compile in the matching runtime hooks (e.g. baked-in
/// `ThreadSanitizer` suppressions) for -- and only for -- the relevant sanitizer
/// build.
Comment on lines +10 to +13
fn detect_sanitizers() {
const SANITIZERS: [(&str, &str); 2] = [
("sanitizer=thread", "sanitize_thread"),
("sanitizer=address", "sanitize_address"),
];

// Declare every cfg we might set so the crate's `unexpected_cfgs` lint stays
// quiet even in the builds where we don't set it.
for (_, cfg) in SANITIZERS {
println!("cargo::rustc-check-cfg=cfg({cfg})");
}

// Build scripts receive the effective rustflags via `CARGO_ENCODED_RUSTFLAGS`
// (unit-separated); fall back to the plain `RUSTFLAGS` string.
let rustflags = std::env::var("CARGO_ENCODED_RUSTFLAGS")
.map(|encoded| encoded.replace('\x1f', " "))
.or_else(|_| std::env::var("RUSTFLAGS"))
.unwrap_or_default();

for (token, cfg) in SANITIZERS {
if rustflags.contains(token) {
println!("cargo::rustc-cfg={cfg}");
}
}

println!("cargo::rerun-if-env-changed=CARGO_ENCODED_RUSTFLAGS");
println!("cargo::rerun-if-env-changed=RUSTFLAGS");
}

fn main() {
detect_sanitizers();
}
2 changes: 2 additions & 0 deletions dataplane/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ mod packet_processor;
#[cfg(not(feature = "loom"))]
mod runtime;
#[cfg(not(feature = "loom"))]
mod sanitizer;
#[cfg(not(feature = "loom"))]
mod statistics;
26 changes: 26 additions & 0 deletions dataplane/src/sanitizer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Open Network Fabric Authors

//! Compiled-in sanitizer configuration.
//!
//! Sanitizer runtimes look up a handful of weakly-defined `extern "C"` hooks at
//! startup (e.g. `__tsan_default_suppressions`). By defining strong versions of
//! those hooks we bake our suppression lists straight into the binary: no
//! external suppression file and no `TSAN_OPTIONS=suppressions=...` env var is
//! required to run a sanitized `dataplane`.
//!
//! Each hook is gated on a `cfg` emitted by `build.rs` (which sniffs
//! `-Zsanitizer=<kind>` out of the rustflags), so the symbol is only present in
//! the matching sanitizer build. To add suppressions for another sanitizer,
//! register its rustflag token in `build.rs` and add the corresponding hook
//! here.

#[cfg(sanitize_thread)]
#[unsafe(no_mangle)]
extern "C" fn __tsan_default_suppressions() -> *const core::ffi::c_char {
// Trailing `"\0"` makes this a valid C string: the sanitizer hook returns a raw
// `*const c_char` that the TSan runtime reads until a NUL, and `include_str!`
// alone yields no terminator. `suppress.txt` never contains an interior NUL, so
// the whole file survives to the terminator.
Comment on lines +32 to +35
concat!(include_str!("tsan.suppress"), "\0").as_ptr().cast()
}
11 changes: 11 additions & 0 deletions dataplane/src/sanitizer/tsan.suppress
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# tokio's I/O driver runs on its own worker thread(s). When another thread
# registers an I/O source , tokio allocates and initializes a `ScheduledIo`,
# then hands the fd to the kernel via `epoll_ctl`. The driver worker only ever
# sees that `ScheduledIo` after `epoll_wait` returns its token (strictly
# after the registration syscall). That happens-before edge is real but runs
# through the kernel, which ThreadSanitizer cannot instrument, so it reports a
# false positive between RegistrationSet::allocate (init) and
# Driver::turn -> ScheduledIo::{set_readiness,wake} (use). Matching either
# tokio frame silences that pattern without hiding races in our own fd handling.
race:tokio::runtime::io::scheduled_io
race:tokio::runtime::io::registration_set
Loading
Loading