Multi machine follow-up (#2334)

* improved tree generator.
possibility to dump state on exit.

* warnings.

* clippy

* clippy

* aaaaaa

* aaaaaa

* make nix mandatory for unix

* stddddddd

* stddddddd

* stddddddd

* aaa

* clippy

* doc

* aaa

* aaa

* aaa

* aaa

* fix

* aaaaaaaa

* dump state

* aaa

* aaa

* aaa

* more minimal dump

* aaa

* aaa

* aaa

* simpler tc dump

* pub

* more pub

* revert a bit

* release by default

* delete Makefile.toml

* release

* dump execs

* merge

* delete stuff

* aa

* ff

* dig

* FMT cargo stuf

---------

Co-authored-by: Toka <tokazerkje@outlook.com>
This commit is contained in:
Romain Malmain 2024-09-04 18:42:54 +02:00 committed by GitHub
parent 1113879a34
commit 203d3d340a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 196 additions and 59 deletions

View File

@ -33,6 +33,7 @@ use static_alloc::Bump;
#[global_allocator]
static A: Bump<[u8; 512 * 1024 * 1024]> = Bump::uninit();
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
#[cfg(unix)]

View File

@ -6,14 +6,14 @@ if [ ! -d "sqlite3" ]; then
find ./sqlite3 -name "*.test" -exec cp {} corpus/ \;
fi
if [ "$1" = "release" ]; then
cargo build --release
else
if [ "$1" = "d" ]; then
cargo build
else
cargo build --release
fi
export CC=`pwd`/target/debug/libafl_cc
export CXX=`pwd`/target/debug/libafl_cxx
export CC=`pwd`/target/release/libafl_cc
export CXX=`pwd`/target/release/libafl_cxx
export CFLAGS='--libafl'
export CXXFLAGS='--libafl'
export CFLAGS="$CFLAGS -DSQLITE_MAX_LENGTH=128000000 \

View File

@ -1,3 +1,3 @@
#!/bin/bash
./ossfuzz --cores 4-7 --input ./corpus
./ossfuzz --cores 0-1 --input ./corpus

View File

@ -0,0 +1,64 @@
[package]
name = "libfuzzer_libpng_launcher_centralized_multi_machine"
version = "0.12.0"
authors = [
"Romain Malmain <romain.malmain@pm.me>",
"Andrea Fioraldi <andreafioraldi@gmail.com>",
"Dominik Maier <domenukk@gmail.com>",
]
edition = "2021"
[features]
default = ["std"]
std = []
[profile.release]
lto = true
codegen-units = 1
opt-level = 3
debug = true
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
which = "6.0"
[dependencies]
# no llmp compression for now, better perfs.
libafl = { path = "../../libafl", default-features = false, features = [
"std",
"derive",
"llmp_small_maps",
"llmp_broker_timeouts",
"rand_trait",
"fork",
"prelude",
"gzip",
"regex",
"serdeany_autoreg",
"tui_monitor",
"std",
"derive",
"rand_trait",
"fork",
"prelude",
"gzip",
"regex",
"scalability_introspection",
"multi_machine",
"errors_backtrace",
"dump_state",
] }
libafl_bolts = { path = "../../libafl_bolts", features = ["xxh3"] }
libafl_targets = { path = "../../libafl_targets", features = [
"sancov_pcguard_hitcounts",
"libfuzzer",
] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc" }
clap = { version = "4.0", features = ["derive"] }
mimalloc = { version = "*", default-features = false }
env_logger = "0.11"
[lib]
name = "libfuzzer_libpng"
crate-type = ["staticlib"]

View File

@ -102,6 +102,9 @@ tcp_compression = ["tcp_manager", "libafl_bolts/gzip"]
## Enable multi-machine support
multi_machine = ["tokio", "std", "enumflags2", "ahash/std"]
## Dump state of each client on exit
dump_state = ["std"]
## Enables the `NaiveTokenizer` and `StacktraceObserver`
regex = ["std", "dep:regex"]

View File

@ -177,7 +177,7 @@ where
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
state.on_restart()?;
// First, reset the page to 0 so the next iteration can read read from the beginning of this page
// First, reset the page to 0 so the next iteration can read from the beginning of this page
self.staterestorer.reset();
self.staterestorer.save(&(
if self.save_state.on_restart() {
@ -595,7 +595,7 @@ where
}
};
// If this guy wants to fork, then ignore sigit
// If this guy wants to fork, then ignore sigint
#[cfg(any(windows, not(feature = "fork")))]
unsafe {
#[cfg(windows)]
@ -614,7 +614,7 @@ where
#[cfg(any(windows, not(feature = "fork")))]
let child_status = child_status.code().unwrap_or_default();
compiler_fence(Ordering::SeqCst);
compiler_fence(Ordering::SeqCst); // really useful?
if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() {
// if ctrl-c is pressed, we end up in this branch

View File

@ -34,7 +34,9 @@ pub use broker_hooks::*;
#[cfg(feature = "std")]
pub use launcher::*;
#[cfg(all(unix, feature = "std"))]
use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal, CTRL_C_EXIT};
use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal};
#[cfg(all(unix, feature = "std"))]
use libafl_bolts::os::CTRL_C_EXIT;
use libafl_bolts::{
current_time,
tuples::{Handle, MatchNameRef},
@ -86,10 +88,7 @@ impl Handler for ShutdownSignalData {
_info: &mut siginfo_t,
_context: Option<&mut ucontext_t>,
) {
// println!("in handler! {}", std::process::id());
unsafe {
// println!("Exiting from the handler....");
#[cfg(unix)]
libc::_exit(CTRL_C_EXIT);

View File

@ -225,7 +225,7 @@ where
}
/// A testcase metadata holding a list of indexes of a map
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
allow(clippy::unsafe_derive_deserialize)

View File

@ -242,8 +242,8 @@ where
) -> Result<(), Error> {
let monitor_timeout = STATS_TIMEOUT_DEFAULT;
loop {
// log::info!("Starting another fuzz_loop");
manager.maybe_report_progress(state, monitor_timeout)?;
self.fuzz_one(stages, executor, state, manager)?;
}
}

View File

@ -82,8 +82,9 @@ pub use owned_map::*;
/// # InMemoryCorpus::<BytesInput>::new(),
/// # InMemoryCorpus::new(),
/// # &mut feedback,
/// # &mut ()
/// # &mut (),
/// # ).unwrap();
///
/// # feedback.init_state(&mut state).unwrap();
///
/// let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());

View File

@ -10,7 +10,6 @@ use core::{
};
use std::os::raw::{c_long, c_void};
use log::info;
use num_enum::FromPrimitive;
pub use windows::Win32::{
Foundation::{BOOL, NTSTATUS},
@ -462,7 +461,7 @@ unsafe fn internal_handle_exception(
.unwrap();
match &EXCEPTION_HANDLERS[index] {
Some(handler_holder) => {
info!(
log::info!(
"{:?}: Handling exception {}",
std::process::id(),
exception_code
@ -472,7 +471,7 @@ unsafe fn internal_handle_exception(
EXCEPTION_CONTINUE_EXECUTION
}
None => {
info!(
log::info!(
"{:?}: No handler for exception {}",
std::process::id(),
exception_code
@ -601,11 +600,11 @@ pub(crate) unsafe fn setup_ctrl_handler<T: 'static + CtrlHandler>(
let result = SetConsoleCtrlHandler(Some(ctrl_handler), true);
match result {
Ok(()) => {
info!("SetConsoleCtrlHandler succeeded");
log::info!("SetConsoleCtrlHandler succeeded");
Ok(())
}
Err(err) => {
info!("SetConsoleCtrlHandler failed");
log::info!("SetConsoleCtrlHandler failed");
Err(Error::from(err))
}
}
@ -615,7 +614,7 @@ unsafe extern "system" fn ctrl_handler(ctrl_type: u32) -> BOOL {
let handler = ptr::read_volatile(addr_of!(CTRL_HANDLER));
match handler {
Some(handler_holder) => {
info!("{:?}: Handling ctrl {}", std::process::id(), ctrl_type);
log::info!("{:?}: Handling ctrl {}", std::process::id(), ctrl_type);
let handler = &mut *handler_holder.handler.get();
if let Some(ctrl_handler) = handler.as_mut() {
(*ctrl_handler).handle(ctrl_type).into()

View File

@ -0,0 +1,2 @@
*.txt
*.dot

View File

@ -9,3 +9,6 @@ edition = "2021"
[dependencies]
petgraph = "0.6"
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -1,20 +1,48 @@
use std::net::SocketAddr;
use std::{
fmt::{Display, Formatter},
mem,
};
use petgraph::{graph::NodeIndex, Direction, Graph};
use serde::Serialize;
/// A node of the network
#[derive(Debug, Clone, Default)]
pub struct MultiMachineNode {}
#[derive(Debug, Clone)]
pub struct MultiMachineNode {
addr: String,
}
/// The final configuration of a node on the network
#[derive(Debug, Clone, Serialize)]
pub struct MultiMachineNodeConfig {
addr: String,
parent: Option<String>,
port: u16,
}
/// The tree
pub struct MultiMachineTree {
pub graph: Graph<MultiMachineNode, ()>,
pub graph: Graph<MultiMachineNode, MultiMachineEdge>,
}
pub struct MultiMachineEdge;
impl Display for MultiMachineEdge {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "")
}
}
impl Display for MultiMachineNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.addr)
}
}
impl MultiMachineNode {
#[must_use]
pub fn new() -> Self {
Self {}
pub fn new(addr: String) -> Self {
Self { addr }
}
}
@ -25,12 +53,13 @@ impl MultiMachineTree {
/// - machines: machines to add.
/// - `max_children_per_parent`: each parent will have at most this amount of children
#[must_use]
pub fn generate(machines: &[SocketAddr], max_children_per_parent: u64) -> Self {
let mut graph = Graph::<MultiMachineNode, ()>::new();
pub fn generate(machines: &[String], max_children_per_parent: u64) -> Self {
let mut graph = Graph::<MultiMachineNode, MultiMachineEdge>::new();
let mut machines = Vec::from(machines);
machines.reverse();
let root = if let Some(_root) = machines.pop() {
graph.add_node(MultiMachineNode::new())
let root = if let Some(root) = machines.pop() {
graph.add_node(MultiMachineNode::new(root))
} else {
return Self { graph };
};
@ -43,17 +72,17 @@ impl MultiMachineTree {
let mut nodes_to_populate_later: Vec<NodeIndex> = Vec::new();
// place all the machines in the graph
while let Some(_machine) = machines.pop() {
while let Some(machine) = machines.pop() {
if graph.nb_children(nodes_to_populate_now[populate_idx as usize])
== max_children_per_parent
{
nodes_to_populate_now = core::mem::take(&mut nodes_to_populate_later);
nodes_to_populate_now = mem::take(&mut nodes_to_populate_later);
populate_idx = 0; // should be useless
}
let new_child = graph.add_child(
nodes_to_populate_now[populate_idx as usize],
MultiMachineNode::new(),
MultiMachineNode::new(machine),
);
nodes_to_populate_later.push(new_child);
@ -65,7 +94,7 @@ impl MultiMachineTree {
fn add_child(&mut self, parent: NodeIndex, child: MultiMachineNode) -> NodeIndex {
let child_idx = self.graph.add_node(child);
self.graph.add_edge(child_idx, parent, ());
self.graph.add_edge(child_idx, parent, MultiMachineEdge);
child_idx
}
@ -74,4 +103,30 @@ impl MultiMachineTree {
.neighbors_directed(node, Direction::Incoming)
.count() as u64
}
fn get_parent(&self, node: NodeIndex) -> Option<NodeIndex> {
self.graph
.neighbors_directed(node, Direction::Outgoing)
.next()
}
#[must_use]
pub fn get_config(&self, default_port: u16) -> Vec<MultiMachineNodeConfig> {
let mut node_configs: Vec<MultiMachineNodeConfig> = Vec::new();
for node_idx in self.graph.node_indices() {
let node = &self.graph[node_idx];
let parent = self
.get_parent(node_idx)
.map(|parent_idx| self.graph[parent_idx].addr.clone());
node_configs.push(MultiMachineNodeConfig {
addr: node.addr.clone(),
parent,
port: default_port,
});
}
node_configs
}
}

View File

@ -6,39 +6,49 @@
//!
//! We suppose everyone is on the same network and the machines have the fuzzer ready to run on each machine.
use std::{fs, net::SocketAddr, str::FromStr};
use std::{fs, fs::File, io, io::BufRead, path::PathBuf};
use clap::Parser;
use petgraph::dot::Dot;
use crate::graph::MultiMachineTree;
pub mod graph;
#[derive(Parser)]
struct Opt {
#[arg(short, long)]
machines_file: PathBuf,
#[arg(long)]
dot_output: Option<PathBuf>,
#[arg(short, long)]
json_output: Option<PathBuf>,
#[arg(short, long, default_value_t = 50000)]
default_port: u16,
// #[arg(short, long)]
// cmd_file: PathBuf,
}
fn main() {
let machines = [
SocketAddr::from_str("0.0.0.1:50000").unwrap(),
SocketAddr::from_str("0.0.0.2:50000").unwrap(),
SocketAddr::from_str("0.0.0.3:50000").unwrap(),
SocketAddr::from_str("0.0.0.4:50000").unwrap(),
SocketAddr::from_str("0.0.0.5:50000").unwrap(),
SocketAddr::from_str("0.0.0.6:50000").unwrap(),
SocketAddr::from_str("0.0.0.7:50000").unwrap(),
SocketAddr::from_str("0.0.0.8:50000").unwrap(),
SocketAddr::from_str("0.0.0.9:50000").unwrap(),
SocketAddr::from_str("0.0.0.10:50000").unwrap(),
SocketAddr::from_str("0.0.0.11:50000").unwrap(),
SocketAddr::from_str("0.0.0.12:50000").unwrap(),
SocketAddr::from_str("0.0.0.13:50000").unwrap(),
SocketAddr::from_str("0.0.0.14:50000").unwrap(),
SocketAddr::from_str("0.0.0.15:50000").unwrap(),
SocketAddr::from_str("0.0.0.16:50000").unwrap(),
SocketAddr::from_str("0.0.0.17:50000").unwrap(),
SocketAddr::from_str("0.0.0.18:50000").unwrap(),
];
let opt = Opt::parse();
let machine_file = File::open(opt.machines_file.as_path()).unwrap();
let machines: Vec<String> = io::BufReader::new(machine_file)
.lines()
.map(|m| m.unwrap())
.collect();
let multi_machine_graph = MultiMachineTree::generate(&machines, 3);
let dot = Dot::new(&multi_machine_graph.graph);
// final graph
if let Some(dot_path) = opt.dot_output {
let dot = Dot::new(&multi_machine_graph.graph);
fs::write(dot_path, format!("{dot}")).unwrap();
}
fs::write("multi_machine.dot", format!("{dot:?}")).unwrap();
if let Some(json_path) = opt.json_output {
let cfg = multi_machine_graph.get_config(opt.default_port);
let cfg_json = serde_json::to_string_pretty(&cfg).unwrap();
fs::write(json_path, cfg_json).unwrap();
}
}