phink_lib/fuzzer/
environment.rs1use crate::{
2 cli::{
3 config::{
4 PFiles::{
5 AllowlistPath,
6 CorpusPath,
7 DictPath,
8 },
9 PhinkFiles,
10 },
11 env::PhinkEnv::AllowList,
12 ziggy::ZiggyConfig,
13 },
14 contract::selectors::{
15 database::SelectorDatabase,
16 selector::Selector,
17 },
18 EmptyResult,
19 ResultOf,
20};
21use anyhow::Context;
22use std::{
23 fs,
24 fs::{
25 File,
26 OpenOptions,
27 },
28 io,
29 io::Write,
30 path::PathBuf,
31};
32
33pub struct AllowListBuilder;
34
35impl AllowListBuilder {
36 pub const FUNCTIONS: [&str; 5] = [
37 "*_ZN9phink_lib6fuzzer6parser*",
38 "*redirect_coverage*",
39 "*decode*",
40 "*harness*",
41 "*black_box*",
42 ];
43 pub fn build(fuzz_output: PathBuf) -> io::Result<()> {
45 let allowlist_path = PhinkFiles::new(fuzz_output).path(AllowlistPath);
46
47 if allowlist_path.exists() {
48 println!("❗ {AllowList} already exists... skipping");
49 return Ok(());
50 }
51
52 fs::create_dir_all(allowlist_path.parent().unwrap())?;
53 let mut allowlist_file = File::create(allowlist_path)?;
54
55 for func in Self::FUNCTIONS {
56 writeln!(allowlist_file, "fun: {}", func)?;
57 }
58
59 println!("✅ {AllowList} created successfully");
60 Ok(())
61 }
62}
63
64pub struct Dict {
65 file_path: PathBuf,
66}
67
68impl Dict {
69 pub fn write_dict_entry(&self, selector: &Selector) -> EmptyResult {
70 let mut file = OpenOptions::new()
71 .append(true)
72 .open(&self.file_path)
73 .with_context(|| format!("Failed to open file for appending: {:?}", self.file_path))?;
74
75 writeln!(file, "\"{}\"", selector)
76 .with_context(|| format!("Couldn't write selector '{}' into the dict", selector))?;
77
78 Ok(())
79 }
80
81 pub fn new(phink_file: PhinkFiles, max_message: usize) -> io::Result<Dict> {
82 let path_buf = phink_file.path(DictPath);
83 if let Some(parent) = path_buf.parent() {
84 fs::create_dir_all(parent)?;
85 }
86 let mut dict_file = OpenOptions::new()
87 .write(true)
88 .create(true)
89 .truncate(true)
90 .open(path_buf.clone())?;
91
92 writeln!(dict_file, "# Dictionary file for selectors")?;
93 writeln!(
94 dict_file,
95 "# Lines starting with '#' and empty lines are ignored."
96 )?;
97
98 if max_message > 1 {
100 writeln!(dict_file, "delimiter=\"********\"")?;
101 }
102
103 Ok(Self {
104 file_path: path_buf,
105 })
106 }
107}
108
109#[derive(Clone, Debug)]
110pub struct CorpusManager {
111 corpus_dir: PathBuf,
112}
113
114impl CorpusManager {
115 pub fn new(phink_file: &PhinkFiles) -> ResultOf<CorpusManager> {
116 let corpus_dir = phink_file.path(CorpusPath);
117 if !corpus_dir.exists() {
118 fs::create_dir_all(&corpus_dir)?;
119 }
120 Ok(Self { corpus_dir })
121 }
122
123 pub fn write_seed(&self, name: &str, seed: &[u8]) -> io::Result<()> {
126 let mut data = vec![0x00, 0x00, 0x00, 0x00, 0x01];
127 let file_path = self.corpus_dir.join(format!("{name}.bin"));
128 data.extend_from_slice(seed);
129 fs::write(file_path, data)
130 }
131
132 pub fn write_corpus_file(&self, index: usize, selector: &Selector) -> io::Result<()> {
133 let mut data = vec![0x00, 0x00, 0x00, 0x00, 0x01]; let file_path = self.corpus_dir.join(format!("selector_{index}.bin"));
135 data.extend_from_slice(selector.0.as_ref());
136 data.extend(vec![0x0, 0x0]); fs::write(file_path, data)
138 }
139}
140
141pub struct EnvironmentBuilder {
142 database: SelectorDatabase,
143}
144
145impl EnvironmentBuilder {
146 pub fn new(database: SelectorDatabase) -> EnvironmentBuilder {
147 Self { database }
148 }
149
150 pub fn build_env(self, conf: ZiggyConfig) -> EmptyResult {
152 let phink_file = PhinkFiles::new(conf.clone().fuzz_output());
153
154 let dict = Dict::new(
155 phink_file.clone(),
156 conf.config().max_messages_per_exec.unwrap_or_default(),
157 )?;
158 let corpus_manager =
159 CorpusManager::new(&phink_file).context("Couldn't create a new corpus manager")?;
160
161 for (i, selector) in self
162 .database
163 .get_unique_messages()
164 .context("Couldn't load messages")?
165 .iter()
166 .enumerate()
167 {
168 corpus_manager
169 .write_corpus_file(i, selector)
170 .context("Couldn't write corpus file")?;
171
172 dict.write_dict_entry(selector)
173 .context("Couldn't write the dictionnary entries")?;
174 }
175
176 Ok(())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::EmptyResult;
184 use std::{
185 fs,
186 io,
187 };
188 use tempfile::tempdir;
189
190 fn create_test_selector() -> Selector {
191 Selector([0x01, 0x02, 0x03, 0x04])
192 }
193
194 fn create_temp_phink_file(file_name: &str) -> PathBuf {
195 let dir = tempdir().unwrap();
196 dir.path().join(file_name)
197 }
198
199 #[test]
200 fn test_dict_new_creates_file() -> io::Result<()> {
201 let path = create_temp_phink_file("test_dict");
202 let phink_file = PhinkFiles::new(path.clone());
203
204 let dict = Dict::new(phink_file, 4)?;
205 assert!(dict.file_path.exists());
206
207 let contents = fs::read_to_string(dict.file_path)?;
208 assert!(contents.contains("# Dictionary file for selectors"));
209
210 Ok(())
211 }
212
213 #[test]
214 fn test_write_dict_entry() -> EmptyResult {
215 let path = create_temp_phink_file("test_dict");
216 let phink_file = PhinkFiles::new(path.clone());
217 let dict = Dict::new(phink_file, 3)?;
218 let selector = create_test_selector(); dict.write_dict_entry(&selector)?;
220 let contents = fs::read_to_string(dict.file_path)?;
221 assert!(contents.contains("01020304"));
222
223 Ok(())
224 }
225
226 #[test]
227 fn test_corpus_manager_new_creates_dir() -> EmptyResult {
228 let path = create_temp_phink_file("test_corpus");
229 let phink_file = PhinkFiles::new(path.clone());
230
231 let corpus_manager = CorpusManager::new(&phink_file)?;
232 assert!(corpus_manager.corpus_dir.exists());
233
234 Ok(())
235 }
236
237 #[test]
238 fn test_write_corpus_file() -> io::Result<()> {
239 let path = create_temp_phink_file("test_corpus");
240 let phink_file = PhinkFiles::new(path.clone());
241 let corpus_manager = CorpusManager::new(&phink_file).unwrap();
242
243 let selector = create_test_selector();
244 corpus_manager.write_corpus_file(0, &selector)?;
245
246 let file_path = corpus_manager.corpus_dir.join("selector_0.bin");
247 assert!(file_path.exists());
248
249 let data = fs::read(file_path)?;
250 assert_eq!(data[5..9], selector.0);
251
252 Ok(())
253 }
254}