Skip to content

Commit 1d67aff

Browse files
authored
Fix racy file operations in libfuzzer_libpng_launcher & Sort outputs by ClientId in OnDiskTomlMonitor (#3847)
* Fix racey file operations in libfuzzer_libpng_launcher fuzzer * ClientStatsManager: Keep sorted list of client ids
1 parent cdd230d commit 1d67aff

4 files changed

Lines changed: 33 additions & 18 deletions

File tree

crates/libafl/src/monitors/disk.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Monitors that log to disk using different formats like `JSON` and `TOML`.
22
3-
use alloc::{string::String, vec::Vec};
3+
use alloc::string::String;
44
use core::time::Duration;
55
use std::{
66
fs::{File, OpenOptions},
@@ -61,19 +61,14 @@ exec_sec = {}
6161
)
6262
.expect("Failed to write to the Toml file");
6363

64-
let all_clients: Vec<ClientId> = client_stats_manager
65-
.client_stats()
66-
.keys()
67-
.copied()
68-
.collect();
69-
70-
for client_id in &all_clients {
64+
for idx in 0..client_stats_manager.client_ids_sorted().len() {
65+
let client_id = client_stats_manager.client_ids_sorted()[idx];
7166
let exec_sec = client_stats_manager
72-
.update_client_stats_for(*client_id, |client_stat| {
67+
.update_client_stats_for(client_id, |client_stat| {
7368
client_stat.execs_per_sec(cur_time)
7469
})?;
7570

76-
let client = client_stats_manager.client_stats_for(*client_id)?;
71+
let client = client_stats_manager.client_stats_for(client_id)?;
7772

7873
write!(
7974
&mut file,

crates/libafl/src/monitors/stats/manager.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Client statistics manager
22
3-
use alloc::{borrow::Cow, string::String};
3+
use alloc::{borrow::Cow, string::String, vec::Vec};
44
use core::time::Duration;
55

66
use hashbrown::HashMap;
@@ -19,6 +19,7 @@ use super::{
1919
/// Manager of all client's statistics
2020
#[derive(Debug)]
2121
pub struct ClientStatsManager {
22+
sorted_client_ids: Vec<ClientId>,
2223
client_stats: HashMap<ClientId, ClientStats>,
2324
/// Aggregated user stats value.
2425
///
@@ -36,6 +37,7 @@ impl ClientStatsManager {
3637
#[must_use]
3738
pub fn new() -> Self {
3839
Self {
40+
sorted_client_ids: Vec::new(),
3941
client_stats: HashMap::new(),
4042
cached_aggregated_user_stats: HashMap::new(),
4143
cached_global_stats: None,
@@ -49,6 +51,12 @@ impl ClientStatsManager {
4951
&self.client_stats
5052
}
5153

54+
/// Get sorted client ids
55+
#[must_use]
56+
pub fn client_ids_sorted(&self) -> &[ClientId] {
57+
&self.sorted_client_ids
58+
}
59+
5260
/// Get client with `client_id`
5361
pub fn get(&self, client_id: ClientId) -> Result<&ClientStats, Error> {
5462
self.client_stats
@@ -68,6 +76,16 @@ impl ClientStatsManager {
6876
};
6977
self.client_stats.insert(client_id, stats);
7078
self.cached_global_stats = None;
79+
80+
if self
81+
.sorted_client_ids
82+
.last()
83+
.map_or(true, |last| *last < client_id)
84+
{
85+
self.sorted_client_ids.push(client_id);
86+
} else if let Err(idx) = self.sorted_client_ids.binary_search(&client_id) {
87+
self.sorted_client_ids.insert(idx, client_id);
88+
}
7189
}
7290

7391
self.update_client_stats_for(client_id, |new_stat| {
@@ -110,6 +128,8 @@ impl ClientStatsManager {
110128
/// This will clear global stats cache.
111129
pub fn update_all_client_stats(&mut self, new_client_stats: HashMap<ClientId, ClientStats>) {
112130
self.client_stats = new_client_stats;
131+
self.sorted_client_ids = self.client_stats.keys().copied().collect();
132+
self.sorted_client_ids.sort_unstable();
113133
self.cached_global_stats = None;
114134
}
115135

crates/libafl/src/stages/afl_stats.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -538,13 +538,10 @@ where
538538
/// Writes a stats file, if a `stats_file_path` is set.
539539
fn maybe_write_fuzzer_stats(&self, stats: &AflFuzzerStats) -> Result<(), Error> {
540540
if let Some(stats_file_path) = &self.stats_file_path {
541-
let tmp_file = stats_file_path
542-
.parent()
543-
.expect("fuzzer_stats file must have a parent!")
544-
.join(".fuzzer_stats_tmp");
541+
let tmp_file = stats_file_path.with_extension("tmp");
542+
545543
std::fs::write(&tmp_file, stats.to_string())?;
546-
_ = std::fs::copy(&tmp_file, stats_file_path)?;
547-
std::fs::remove_file(tmp_file)?;
544+
_ = std::fs::rename(&tmp_file, stats_file_path)?;
548545
}
549546
Ok(())
550547
}

fuzzers/inprocess/libfuzzer_libpng_launcher/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,15 @@ pub extern "C" fn libafl_main() {
344344
]));
345345
}
346346

347+
// filename needs to be unique per client
348+
let stats_file = format!("fuzzer_stats{}", client_description.id());
349+
347350
// Setup a basic mutator with a mutational stage
348351
let mutator = HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
349352
let calibration = CalibrationStage::new(&map_feedback);
350353
let afl_stats = AflStatsStage::builder()
351354
.map_feedback(&map_feedback)
352-
.stats_file(opt.output.join("fuzzer_stats"))
355+
.stats_file(opt.output.join(stats_file))
353356
.report_interval(Duration::from_millis(1000))
354357
.core_id(core_id)
355358
.banner("libfuzzer_libpng".into())

0 commit comments

Comments
 (0)