Skip to content

Commit e27ec26

Browse files
authored
Add TargetBytesConverter to allow Nautilus for ForkserverExecutor (#2630)
* Add TargetBytesConverter to allow Nautilus for ForkserverExecutor * ci * ci * More * fmt
1 parent 261b6b5 commit e27ec26

File tree

14 files changed

+519
-43
lines changed

14 files changed

+519
-43
lines changed

.github/workflows/build_and_test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ jobs:
272272
- ./fuzzers/structure_aware/baby_fuzzer_multi
273273
- ./fuzzers/structure_aware/baby_fuzzer_custom_input
274274
- ./fuzzers/structure_aware/baby_fuzzer_nautilus
275+
- ./fuzzers/structure_aware/forkserver_simple_nautilus
275276

276277
# In-process
277278
- ./fuzzers/fuzz_anything/cargo_fuzz

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ vendor
66

77
.DS_Store
88
.env
9+
.vscode
910

1011
*.test
1112
*.tmp

fuzzers/structure_aware/baby_fuzzer_nautilus/src/main.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,9 @@ pub fn main() {
7676
)
7777
.unwrap();
7878

79-
if state
80-
.metadata_map()
81-
.get::<NautilusChunksMetadata>()
82-
.is_none()
83-
{
84-
state.add_metadata(NautilusChunksMetadata::new("/tmp/".into()));
85-
}
79+
let _ = state.metadata_or_insert_with::<NautilusChunksMetadata>(|| {
80+
NautilusChunksMetadata::new("/tmp/".into())
81+
});
8682

8783
// The Monitor trait define how the fuzzer stats are reported to the user
8884
let monitor = SimpleMonitor::new(|s| println!("{s}"));
@@ -139,9 +135,11 @@ pub fn main() {
139135
*/
140136

141137
// Generate 8 initial inputs
142-
state
143-
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
144-
.expect("Failed to generate the initial corpus");
138+
if state.must_load_initial_inputs() {
139+
state
140+
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
141+
.expect("Failed to generate the initial corpus");
142+
}
145143

146144
// Setup a mutational stage with a basic bytes mutator
147145
let mutator = StdScheduledMutator::with_max_stack_pow(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
forkserver_simple
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "forkserver_simple"
3+
version = "0.13.2"
4+
authors = ["tokatoka <tokazerkje@outlook.com>"]
5+
edition = "2021"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[profile.dev]
10+
panic = "abort"
11+
12+
[profile.release]
13+
panic = "abort"
14+
lto = true
15+
codegen-units = 1
16+
opt-level = 3
17+
18+
[dependencies]
19+
clap = { version = "4.5.18", features = ["derive"] }
20+
env_logger = "0.11.5"
21+
libafl = { path = "../../../libafl", features = ["std", "derive"] }
22+
libafl_bolts = { path = "../../../libafl_bolts" }
23+
log = { version = "0.4.22", features = ["release_max_level_info"] }
24+
nix = { version = "0.29.0", features = ["signal"] }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Simple Forkserver Fuzzer
2+
3+
This is a simple example fuzzer to fuzz a executable instrumented by afl-cc.
4+
## Usage
5+
You can build this example by `cargo build --release`.
6+
This downloads AFLplusplus/AFLplusplus and compiles the example harness program in src/program.c with afl-cc
7+
8+
## Run
9+
After you build it you can run
10+
`cp ./target/release/forkserver_simple .` to copy the fuzzer into this directory,
11+
and you can run
12+
`taskset -c 1 ./forkserver_simple ./target/release/program ./corpus/ -t 1000` to run the fuzzer.
13+
`taskset` binds this process to a specific core to improve the throughput.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::{
2+
env,
3+
path::Path,
4+
process::{exit, Command},
5+
};
6+
7+
const AFL_URL: &str = "https://github.com/AFLplusplus/AFLplusplus";
8+
9+
fn main() {
10+
if cfg!(windows) {
11+
println!("cargo:warning=No support for windows yet.");
12+
exit(0);
13+
}
14+
15+
env::remove_var("DEBUG");
16+
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
17+
18+
let afl = format!("{}/AFLplusplus", &cwd);
19+
let afl_cc = format!("{}/AFLplusplus/afl-cc", &cwd);
20+
21+
let afl_path = Path::new(&afl);
22+
let afl_cc_path = Path::new(&afl_cc);
23+
24+
if !afl_path.is_dir() {
25+
println!("cargo:warning=AFL++ not found, downloading...");
26+
Command::new("git")
27+
.arg("clone")
28+
.arg(AFL_URL)
29+
.status()
30+
.unwrap();
31+
}
32+
33+
if !afl_cc_path.is_file() {
34+
let mut afl_cc_make = Command::new("make");
35+
afl_cc_make.arg("all").current_dir(afl_path);
36+
if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
37+
if !llvm_config.is_empty() {
38+
afl_cc_make.env("LLVM_CONFIG", llvm_config);
39+
}
40+
}
41+
afl_cc_make.status().unwrap();
42+
}
43+
44+
let mut compile_command = Command::new(afl_cc_path);
45+
compile_command
46+
.args(["src/program.c", "-o"])
47+
.arg(format!("{cwd}/target/release/program"));
48+
49+
if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
50+
if !llvm_config.is_empty() {
51+
compile_command.env("LLVM_CONFIG", llvm_config);
52+
}
53+
}
54+
55+
compile_command.status().unwrap();
56+
57+
println!("cargo:rerun-if-changed=build.rs");
58+
println!("cargo:rerun-if-changed=src/");
59+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aaa
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use core::time::Duration;
2+
use std::path::PathBuf;
3+
4+
use clap::Parser;
5+
use libafl::{
6+
corpus::{InMemoryCorpus, OnDiskCorpus},
7+
events::SimpleEventManager,
8+
executors::{forkserver::ForkserverExecutor, HasObservers},
9+
feedback_and_fast, feedback_or,
10+
feedbacks::{
11+
CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback, TimeFeedback,
12+
},
13+
fuzzer::{Fuzzer, StdFuzzer},
14+
generators::{NautilusContext, NautilusGenerator},
15+
inputs::{NautilusInput, NautilusTargetBytesConverter},
16+
monitors::SimpleMonitor,
17+
mutators::{
18+
NautilusRandomMutator, NautilusRecursionMutator, NautilusSpliceMutator,
19+
StdScheduledMutator, Tokens,
20+
},
21+
observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver},
22+
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
23+
stages::mutational::StdMutationalStage,
24+
state::StdState,
25+
HasMetadata,
26+
};
27+
use libafl_bolts::{
28+
current_nanos,
29+
rands::StdRand,
30+
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
31+
tuples::{tuple_list, Handled},
32+
AsSliceMut, Truncate,
33+
};
34+
use nix::sys::signal::Signal;
35+
36+
/// The commandline args this fuzzer accepts
37+
#[derive(Debug, Parser)]
38+
#[command(
39+
name = "forkserver_simple",
40+
about = "This is a simple example fuzzer to fuzz a executable instrumented by afl-cc, using Nautilus grammar.",
41+
author = "tokatoka <tokazerkje@outlook.com>, dmnk <domenukk@gmail.com>"
42+
)]
43+
struct Opt {
44+
#[arg(
45+
help = "The instrumented binary we want to fuzz",
46+
name = "EXEC",
47+
required = true
48+
)]
49+
executable: String,
50+
51+
#[arg(
52+
help = "Timeout for each individual execution, in milliseconds",
53+
short = 't',
54+
long = "timeout",
55+
default_value = "1200"
56+
)]
57+
timeout: u64,
58+
59+
#[arg(
60+
help = "If not set, the child's stdout and stderror will be redirected to /dev/null",
61+
short = 'd',
62+
long = "debug-child",
63+
default_value = "false"
64+
)]
65+
debug_child: bool,
66+
67+
#[arg(
68+
help = "Arguments passed to the target",
69+
name = "arguments",
70+
num_args(1..),
71+
allow_hyphen_values = true,
72+
)]
73+
arguments: Vec<String>,
74+
75+
#[arg(
76+
help = "Signal used to stop child",
77+
short = 's',
78+
long = "signal",
79+
value_parser = str::parse::<Signal>,
80+
default_value = "SIGKILL"
81+
)]
82+
signal: Signal,
83+
84+
#[arg(help = "The nautilus grammar file", short)]
85+
grammar: PathBuf,
86+
}
87+
88+
#[allow(clippy::similar_names)]
89+
pub fn main() {
90+
env_logger::init();
91+
const MAP_SIZE: usize = 65536;
92+
93+
let opt = Opt::parse();
94+
95+
let mut shmem_provider = UnixShMemProvider::new().unwrap();
96+
97+
// The coverage map shared between observer and executor
98+
let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap();
99+
// let the forkserver know the shmid
100+
shmem.write_to_env("__AFL_SHM_ID").unwrap();
101+
let shmem_buf = shmem.as_slice_mut();
102+
103+
// Create an observation channel using the signals map
104+
let edges_observer = unsafe {
105+
HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)).track_indices()
106+
};
107+
108+
// Create an observation channel to keep track of the execution time
109+
let time_observer = TimeObserver::new("time");
110+
111+
let context = NautilusContext::from_file(15, opt.grammar);
112+
113+
// Feedback to rate the interestingness of an input
114+
// This one is composed by two Feedbacks in OR
115+
let mut feedback = feedback_or!(
116+
// New maximization map feedback linked to the edges observer and the feedback state
117+
MaxMapFeedback::new(&edges_observer),
118+
// Time feedback, this one does not need a feedback state
119+
TimeFeedback::new(&time_observer),
120+
// Nautilus context
121+
NautilusFeedback::new(&context),
122+
);
123+
124+
// A feedback to choose if an input is a solution or not
125+
// We want to do the same crash deduplication that AFL does
126+
let mut objective = feedback_and_fast!(
127+
// Must be a crash
128+
CrashFeedback::new(),
129+
// Take it only if trigger new coverage over crashes
130+
// Uses `with_name` to create a different history from the `MaxMapFeedback` in `feedback` above
131+
MaxMapFeedback::with_name("mapfeedback_metadata_objective", &edges_observer)
132+
);
133+
134+
// create a State from scratch
135+
let mut state = StdState::new(
136+
// RNG
137+
StdRand::with_seed(current_nanos()),
138+
// Corpus that will be evolved, we keep it in memory for performance
139+
InMemoryCorpus::<NautilusInput>::new(),
140+
// Corpus in which we store solutions (crashes in this example),
141+
// on disk so the user can get them after stopping the fuzzer
142+
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
143+
// States of the feedbacks.
144+
// The feedbacks can report the data that should persist in the State.
145+
&mut feedback,
146+
// Same for objective feedbacks
147+
&mut objective,
148+
)
149+
.unwrap();
150+
151+
let _ = state.metadata_or_insert_with::<NautilusChunksMetadata>(|| {
152+
NautilusChunksMetadata::new("/tmp/".into())
153+
});
154+
155+
// The Monitor trait define how the fuzzer stats are reported to the user
156+
let monitor = SimpleMonitor::new(|s| println!("{s}"));
157+
158+
// The event manager handle the various events generated during the fuzzing loop
159+
// such as the notification of the addition of a new item to the corpus
160+
let mut mgr = SimpleEventManager::new(monitor);
161+
162+
// A minimization+queue policy to get testcasess from the corpus
163+
let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());
164+
165+
// A fuzzer with feedbacks and a corpus scheduler
166+
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
167+
168+
// If we should debug the child
169+
let debug_child = opt.debug_child;
170+
171+
// Create the executor for the forkserver
172+
let args = opt.arguments;
173+
174+
let observer_ref = edges_observer.handle();
175+
176+
let mut tokens = Tokens::new();
177+
let mut executor = ForkserverExecutor::builder()
178+
.program(opt.executable)
179+
.debug_child(debug_child)
180+
.shmem_provider(&mut shmem_provider)
181+
.autotokens(&mut tokens)
182+
.parse_afl_cmdline(args)
183+
.coverage_map_size(MAP_SIZE)
184+
.timeout(Duration::from_millis(opt.timeout))
185+
.kill_signal(opt.signal)
186+
.target_bytes_converter(NautilusTargetBytesConverter::new(&context))
187+
.build(tuple_list!(time_observer, edges_observer))
188+
.unwrap();
189+
190+
if let Some(dynamic_map_size) = executor.coverage_map_size() {
191+
executor.observers_mut()[&observer_ref]
192+
.as_mut()
193+
.truncate(dynamic_map_size);
194+
}
195+
196+
let mut generator = NautilusGenerator::new(&context);
197+
198+
if state.must_load_initial_inputs() {
199+
state
200+
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
201+
.expect("Failed to generate inputs");
202+
}
203+
204+
state.add_metadata(tokens);
205+
206+
// Setup a mutational stage with a basic bytes mutator
207+
let mutator = StdScheduledMutator::with_max_stack_pow(
208+
tuple_list!(
209+
NautilusRandomMutator::new(&context),
210+
NautilusRandomMutator::new(&context),
211+
NautilusRandomMutator::new(&context),
212+
NautilusRandomMutator::new(&context),
213+
NautilusRandomMutator::new(&context),
214+
NautilusRandomMutator::new(&context),
215+
NautilusRecursionMutator::new(&context),
216+
NautilusSpliceMutator::new(&context),
217+
NautilusSpliceMutator::new(&context),
218+
NautilusSpliceMutator::new(&context),
219+
),
220+
2,
221+
);
222+
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
223+
224+
fuzzer
225+
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
226+
.expect("Error in the fuzzing loop");
227+
}

0 commit comments

Comments
 (0)