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] #[global_allocator]
static A: Bump<[u8; 512 * 1024 * 1024]> = Bump::uninit(); static A: Bump<[u8; 512 * 1024 * 1024]> = Bump::uninit();
#[cfg(not(test))]
#[panic_handler] #[panic_handler]
fn panic(_info: &PanicInfo) -> ! { fn panic(_info: &PanicInfo) -> ! {
#[cfg(unix)] #[cfg(unix)]

View File

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

View File

@ -1,3 +1,3 @@
#!/bin/bash #!/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 ## Enable multi-machine support
multi_machine = ["tokio", "std", "enumflags2", "ahash/std"] multi_machine = ["tokio", "std", "enumflags2", "ahash/std"]
## Dump state of each client on exit
dump_state = ["std"]
## Enables the `NaiveTokenizer` and `StacktraceObserver` ## Enables the `NaiveTokenizer` and `StacktraceObserver`
regex = ["std", "dep:regex"] regex = ["std", "dep:regex"]

View File

@ -177,7 +177,7 @@ where
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
state.on_restart()?; 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.reset();
self.staterestorer.save(&( self.staterestorer.save(&(
if self.save_state.on_restart() { 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")))] #[cfg(any(windows, not(feature = "fork")))]
unsafe { unsafe {
#[cfg(windows)] #[cfg(windows)]
@ -614,7 +614,7 @@ where
#[cfg(any(windows, not(feature = "fork")))] #[cfg(any(windows, not(feature = "fork")))]
let child_status = child_status.code().unwrap_or_default(); 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 child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() {
// if ctrl-c is pressed, we end up in this branch // if ctrl-c is pressed, we end up in this branch

View File

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

View File

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

View File

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

View File

@ -82,8 +82,9 @@ pub use owned_map::*;
/// # InMemoryCorpus::<BytesInput>::new(), /// # InMemoryCorpus::<BytesInput>::new(),
/// # InMemoryCorpus::new(), /// # InMemoryCorpus::new(),
/// # &mut feedback, /// # &mut feedback,
/// # &mut () /// # &mut (),
/// # ).unwrap(); /// # ).unwrap();
///
/// # feedback.init_state(&mut state).unwrap(); /// # feedback.init_state(&mut state).unwrap();
/// ///
/// let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new()); /// 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 std::os::raw::{c_long, c_void};
use log::info;
use num_enum::FromPrimitive; use num_enum::FromPrimitive;
pub use windows::Win32::{ pub use windows::Win32::{
Foundation::{BOOL, NTSTATUS}, Foundation::{BOOL, NTSTATUS},
@ -462,7 +461,7 @@ unsafe fn internal_handle_exception(
.unwrap(); .unwrap();
match &EXCEPTION_HANDLERS[index] { match &EXCEPTION_HANDLERS[index] {
Some(handler_holder) => { Some(handler_holder) => {
info!( log::info!(
"{:?}: Handling exception {}", "{:?}: Handling exception {}",
std::process::id(), std::process::id(),
exception_code exception_code
@ -472,7 +471,7 @@ unsafe fn internal_handle_exception(
EXCEPTION_CONTINUE_EXECUTION EXCEPTION_CONTINUE_EXECUTION
} }
None => { None => {
info!( log::info!(
"{:?}: No handler for exception {}", "{:?}: No handler for exception {}",
std::process::id(), std::process::id(),
exception_code exception_code
@ -601,11 +600,11 @@ pub(crate) unsafe fn setup_ctrl_handler<T: 'static + CtrlHandler>(
let result = SetConsoleCtrlHandler(Some(ctrl_handler), true); let result = SetConsoleCtrlHandler(Some(ctrl_handler), true);
match result { match result {
Ok(()) => { Ok(()) => {
info!("SetConsoleCtrlHandler succeeded"); log::info!("SetConsoleCtrlHandler succeeded");
Ok(()) Ok(())
} }
Err(err) => { Err(err) => {
info!("SetConsoleCtrlHandler failed"); log::info!("SetConsoleCtrlHandler failed");
Err(Error::from(err)) 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)); let handler = ptr::read_volatile(addr_of!(CTRL_HANDLER));
match handler { match handler {
Some(handler_holder) => { 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(); let handler = &mut *handler_holder.handler.get();
if let Some(ctrl_handler) = handler.as_mut() { if let Some(ctrl_handler) = handler.as_mut() {
(*ctrl_handler).handle(ctrl_type).into() (*ctrl_handler).handle(ctrl_type).into()

View File

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

View File

@ -9,3 +9,6 @@ edition = "2021"
[dependencies] [dependencies]
petgraph = "0.6" 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 petgraph::{graph::NodeIndex, Direction, Graph};
use serde::Serialize;
/// A node of the network /// A node of the network
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct MultiMachineNode {} 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 /// The tree
pub struct MultiMachineTree { 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 { impl MultiMachineNode {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new(addr: String) -> Self {
Self {} Self { addr }
} }
} }
@ -25,12 +53,13 @@ impl MultiMachineTree {
/// - machines: machines to add. /// - machines: machines to add.
/// - `max_children_per_parent`: each parent will have at most this amount of children /// - `max_children_per_parent`: each parent will have at most this amount of children
#[must_use] #[must_use]
pub fn generate(machines: &[SocketAddr], max_children_per_parent: u64) -> Self { pub fn generate(machines: &[String], max_children_per_parent: u64) -> Self {
let mut graph = Graph::<MultiMachineNode, ()>::new(); let mut graph = Graph::<MultiMachineNode, MultiMachineEdge>::new();
let mut machines = Vec::from(machines); let mut machines = Vec::from(machines);
machines.reverse();
let root = if let Some(_root) = machines.pop() { let root = if let Some(root) = machines.pop() {
graph.add_node(MultiMachineNode::new()) graph.add_node(MultiMachineNode::new(root))
} else { } else {
return Self { graph }; return Self { graph };
}; };
@ -43,17 +72,17 @@ impl MultiMachineTree {
let mut nodes_to_populate_later: Vec<NodeIndex> = Vec::new(); let mut nodes_to_populate_later: Vec<NodeIndex> = Vec::new();
// place all the machines in the graph // 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]) if graph.nb_children(nodes_to_populate_now[populate_idx as usize])
== max_children_per_parent == 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 populate_idx = 0; // should be useless
} }
let new_child = graph.add_child( let new_child = graph.add_child(
nodes_to_populate_now[populate_idx as usize], nodes_to_populate_now[populate_idx as usize],
MultiMachineNode::new(), MultiMachineNode::new(machine),
); );
nodes_to_populate_later.push(new_child); nodes_to_populate_later.push(new_child);
@ -65,7 +94,7 @@ impl MultiMachineTree {
fn add_child(&mut self, parent: NodeIndex, child: MultiMachineNode) -> NodeIndex { fn add_child(&mut self, parent: NodeIndex, child: MultiMachineNode) -> NodeIndex {
let child_idx = self.graph.add_node(child); 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 child_idx
} }
@ -74,4 +103,30 @@ impl MultiMachineTree {
.neighbors_directed(node, Direction::Incoming) .neighbors_directed(node, Direction::Incoming)
.count() as u64 .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. //! 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 petgraph::dot::Dot;
use crate::graph::MultiMachineTree; use crate::graph::MultiMachineTree;
pub mod graph; 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() { fn main() {
let machines = [ let opt = Opt::parse();
SocketAddr::from_str("0.0.0.1:50000").unwrap(),
SocketAddr::from_str("0.0.0.2:50000").unwrap(), let machine_file = File::open(opt.machines_file.as_path()).unwrap();
SocketAddr::from_str("0.0.0.3:50000").unwrap(), let machines: Vec<String> = io::BufReader::new(machine_file)
SocketAddr::from_str("0.0.0.4:50000").unwrap(), .lines()
SocketAddr::from_str("0.0.0.5:50000").unwrap(), .map(|m| m.unwrap())
SocketAddr::from_str("0.0.0.6:50000").unwrap(), .collect();
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 multi_machine_graph = MultiMachineTree::generate(&machines, 3); 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();
}
} }