Skip to main content

aha_misc/common/
utils.rs

1//! Some basic tools
2
3use std::{
4    collections::VecDeque,
5    fs::File,
6    io::{self, BufRead, BufReader},
7    path::{Path, PathBuf},
8    sync::{Arc, Mutex, Once},
9    time::{SystemTime, UNIX_EPOCH},
10};
11
12use libafl::{HasNamedMetadata, inputs::Input, mutators::Tokens, state::HasRand};
13use libafl_bolts::rands::Rand;
14
15use crate::common::cli::ReplayOptions;
16
17/// Read a file and convert each line to a vector of bytes.
18///
19/// # Arguments
20/// * `file_path` - The path to the file to read.
21///
22/// # Returns
23/// A `Result` containing a vector of byte vectors (`Vec<Vec<u8>>`), where each inner vector
24/// represents a line from the file as bytes. Returns an empty vector if the path is empty.
25/// Returns an `io::Error` if file operations fail.
26pub fn read_file_as_vecs<P: AsRef<Path>>(file_path: P) -> io::Result<Vec<Vec<u8>>> {
27    let path = file_path.as_ref();
28
29    if path.as_os_str().is_empty() {
30        return Ok(Vec::new());
31    }
32    let file = File::open(path)?;
33    let reader = BufReader::new(file);
34    let mut result = Vec::new();
35
36    for line in reader.lines() {
37        let line = line?;
38        result.push(line.into_bytes());
39    }
40
41    Ok(result)
42}
43
44/// Save input queue to a directory.
45///
46/// # Arguments
47/// * `save_queue_dir` - The root directory to save the inputs.
48/// * `input_queue` - The input queue to save.
49///
50/// # Returns
51/// Return path of subdir if success, None if failed.
52pub fn save_queue<I>(
53    save_queue_dir: &Path,
54    input_queue: &Arc<Mutex<VecDeque<I>>>,
55) -> Option<PathBuf>
56where
57    I: Input,
58{
59    if save_queue_dir.as_os_str().is_empty() {
60        log::debug!("Crash directory is not set. Skipping saving inputs.");
61        return None;
62    }
63
64    let queue = match input_queue.lock() {
65        Ok(queue) => queue,
66        Err(e) => {
67            log::error!("Failed to lock input queue: {}", e);
68            return None;
69        }
70    };
71
72    if queue.is_empty() {
73        log::debug!("No inputs to save");
74        return None;
75    }
76
77    let timestamp = SystemTime::now()
78        .duration_since(UNIX_EPOCH)
79        .expect("SystemTime before UNIX EPOCH!")
80        .as_secs();
81
82    let save_subdir = save_queue_dir.join(format!("crash_{}", timestamp));
83
84    if let Err(e) = std::fs::create_dir_all(&save_subdir) {
85        log::error!("Failed to create crash subdirectory: {}", e);
86        return None;
87    }
88
89    for (idx, input) in queue.iter().enumerate() {
90        let file_path = save_subdir.join(format!("{}.inp", idx));
91        if let Err(e) = input.to_file(file_path) {
92            log::error!("Failed to write input to file: {}", e);
93            continue;
94        }
95    }
96
97    log::debug!(
98        "Saved {} inputs to directory: {:?}",
99        queue.len(),
100        save_subdir
101    );
102
103    Some(save_subdir)
104}
105
106/// Replay inputs from files using the provided harness function.
107///
108/// # Arguments
109/// * `harness` - A mutable function that accepts a `BytesInput` reference and produces some result.
110/// * `replay_cfg` - Configuration options for the replay process.
111///
112/// # Returns
113/// `Ok(())` if all files were replayed successfully, or an `Error` if any file operations fail.
114///
115/// # Description
116/// This function iterates through a list of files specified in the replay configuration,
117/// reads each file's contents, converts them to `BytesInput` objects, and passes them
118/// to the provided harness function. It respects the start and end indices in the configuration
119/// and provides progress updates every 100 files processed.
120pub fn replay<I, F, R>(
121    harness: &mut F,
122    replay_cfg: &ReplayOptions,
123) -> Result<(), Box<dyn std::error::Error>>
124where
125    I: Input,
126    F: FnMut(&I) -> R,
127{
128    let mut count = 0;
129
130    let end = replay_cfg.get_end();
131
132    for (index, file) in replay_cfg.get_replay_files().iter().enumerate() {
133        if index < replay_cfg.start || index >= end {
134            continue;
135        }
136
137        if replay_cfg.debug {
138            println!("Replaying file: {}", file.display());
139        }
140
141        // let data = fs::read(file)?;
142        let input = I::from_file(file).unwrap();
143        (*harness)(&input);
144
145        count += 1;
146        if count % 100 == 0 {
147            println!("Send {} messages", count);
148        }
149    }
150
151    println!("Send {} messages", count);
152
153    Ok(())
154}
155
156/// Display the content of a seed file.
157pub fn show_seed<I>(filename: &PathBuf)
158where
159    I: Input,
160{
161    if let Ok(input) = I::from_file(filename) {
162        println!("Input from file {}: {:?}", filename.display(), input);
163    } else {
164        println!("Failed to read input from file {}", filename.display());
165    }
166}
167
168static INIT: Once = Once::new();
169
170/// Initialize a logger
171pub fn setup_logger() {
172    INIT.call_once(|| {
173        let _ = env_logger::try_init();
174    });
175}
176
177/// Get a random token from a named metadata Tokens.
178///
179/// # Arguments
180/// * `state` - A State implements `HasRand` and `HasNamedMetadata`
181/// * `name` - Name of the metadata.
182///
183/// # Returns
184/// `Some(Vec<u8>)`, or `None` if metadata is empty.
185pub fn get_random_from_tokens<S>(state: &mut S, name: &str) -> Option<Vec<u8>>
186where
187    S: HasRand + HasNamedMetadata,
188{
189    let Some(meta) = state.named_metadata_map().get::<Tokens>(name) else {
190        return None;
191    };
192
193    if meta.tokens().len() == 0 {
194        return None;
195    }
196
197    let tokens_len = meta.tokens().len();
198    // Old meta lifetime end
199
200    let token_idx = state.rand_mut().below_or_zero(tokens_len);
201
202    // A New meta lifetime
203    let Some(meta) = state.named_metadata_map().get::<Tokens>(name) else {
204        return None;
205    };
206    Some(meta.tokens()[token_idx].clone())
207}