@@ -15,13 +15,91 @@ use libafl_bolts::impl_serdeany;
1515use serde:: { Deserialize , Serialize } ;
1616
1717use crate :: {
18- Error , HasMetadata ,
18+ Error ,
19+ common:: HasMetadata ,
1920 corpus:: { Corpus , CorpusId , Testcase } ,
20- inputs:: Input ,
21+ fuzzer:: HasTargetBytesConverter ,
22+ inputs:: { Input , ToTargetBytes } ,
2123 stages:: { Restartable , Stage } ,
2224 state:: { HasCorpus , HasRand , HasSolutions } ,
2325} ;
2426
27+ /// The default function to generate a filename for a testcase
28+ pub fn generate_filename < I : Input > ( testcase : & Testcase < I > , id : & CorpusId ) -> String {
29+ [
30+ Some ( id. 0 . to_string ( ) ) ,
31+ testcase. filename ( ) . clone ( ) ,
32+ testcase
33+ . input ( )
34+ . as_ref ( )
35+ . map ( |t| t. generate_name ( Some ( * id) ) ) ,
36+ ]
37+ . iter ( )
38+ . flatten ( )
39+ . map ( String :: as_str)
40+ . collect :: < Vec < _ > > ( )
41+ . join ( "-" )
42+ }
43+
44+ /// The default function to create the directories if they don't exist
45+ fn create_dirs_if_needed < A , B > ( corpus_dir : A , solutions_dir : B ) -> Result < ( PathBuf , PathBuf ) , Error >
46+ where
47+ A : Into < PathBuf > ,
48+ B : Into < PathBuf > ,
49+ {
50+ let corpus_dir = corpus_dir. into ( ) ;
51+ if let Err ( e) = fs:: create_dir ( & corpus_dir) {
52+ if !corpus_dir. is_dir ( ) {
53+ return Err ( Error :: os_error (
54+ e,
55+ format ! ( "Error creating directory {}" , corpus_dir. display( ) ) ,
56+ ) ) ;
57+ }
58+ }
59+ let solutions_dir = solutions_dir. into ( ) ;
60+ if let Err ( e) = fs:: create_dir ( & solutions_dir) {
61+ if !solutions_dir. is_dir ( ) {
62+ return Err ( Error :: os_error (
63+ e,
64+ format ! ( "Error creating directory {}" , solutions_dir. display( ) ) ,
65+ ) ) ;
66+ }
67+ }
68+ Ok ( ( corpus_dir, solutions_dir) )
69+ }
70+
71+ /// The default function to dump a corpus to disk
72+ fn dump_from_corpus < C , F , G , I , P > (
73+ corpus : & C ,
74+ dir : & Path ,
75+ generate_filename : & mut G ,
76+ get_bytes : & mut F ,
77+ start_id : Option < CorpusId > ,
78+ ) -> Result < ( ) , Error >
79+ where
80+ C : Corpus < I > ,
81+ I : Input ,
82+ F : FnMut ( & mut Testcase < I > ) -> Result < Vec < u8 > , Error > ,
83+ G : FnMut ( & Testcase < I > , & CorpusId ) -> P ,
84+ P : AsRef < Path > ,
85+ {
86+ let mut id = start_id. or_else ( || corpus. first ( ) ) ;
87+ while let Some ( i) = id {
88+ let testcase = corpus. get ( i) ?;
89+ let fname = dir. join ( generate_filename ( & testcase. borrow ( ) , & i) ) ;
90+
91+ let mut testcase = testcase. borrow_mut ( ) ;
92+ corpus. load_input_into ( & mut testcase) ?;
93+ let bytes = get_bytes ( & mut testcase) ?;
94+
95+ let mut f = File :: create ( fname) ?;
96+ f. write_all ( & bytes) ?;
97+
98+ id = corpus. next ( i) ;
99+ }
100+ Ok ( ( ) )
101+ }
102+
25103/// Metadata used to store information about disk dump indexes for names
26104#[ cfg_attr(
27105 any( not( feature = "serdeany_autoreg" ) , miri) ,
50128 CB1 : FnMut ( & Testcase < I > , & S ) -> Vec < u8 > ,
51129 CB2 : FnMut ( & Testcase < I > , & CorpusId ) -> P ,
52130 S : HasCorpus < I > + HasSolutions < I > + HasRand + HasMetadata ,
131+ I : Input ,
53132 P : AsRef < Path > ,
54133{
55134 #[ inline]
@@ -60,7 +139,40 @@ where
60139 state : & mut S ,
61140 _manager : & mut EM ,
62141 ) -> Result < ( ) , Error > {
63- self . dump_state_to_disk ( state)
142+ let ( last_corpus, last_solution) =
143+ if let Some ( meta) = state. metadata_map ( ) . get :: < DumpToDiskMetadata > ( ) {
144+ (
145+ meta. last_corpus . and_then ( |x| state. corpus ( ) . next ( x) ) ,
146+ meta. last_solution . and_then ( |x| state. solutions ( ) . next ( x) ) ,
147+ )
148+ } else {
149+ ( state. corpus ( ) . first ( ) , state. solutions ( ) . first ( ) )
150+ } ;
151+
152+ let mut get_bytes = |tc : & mut Testcase < I > | Ok ( ( self . to_bytes ) ( tc, state) ) ;
153+
154+ dump_from_corpus (
155+ state. corpus ( ) ,
156+ & self . corpus_dir ,
157+ & mut self . generate_filename ,
158+ & mut get_bytes,
159+ last_corpus,
160+ ) ?;
161+
162+ dump_from_corpus (
163+ state. solutions ( ) ,
164+ & self . solutions_dir ,
165+ & mut self . generate_filename ,
166+ & mut get_bytes,
167+ last_solution,
168+ ) ?;
169+
170+ state. add_metadata ( DumpToDiskMetadata {
171+ last_corpus : state. corpus ( ) . last ( ) ,
172+ last_solution : state. solutions ( ) . last ( ) ,
173+ } ) ;
174+
175+ Ok ( ( ) )
64176 }
65177}
66178
@@ -94,29 +206,11 @@ where
94206 {
95207 Self :: new_with_custom_filenames (
96208 to_bytes,
97- Self :: generate_filename, // This is now of type `fn(&Testcase<EM::Input>, &CorpusId) -> String`
209+ generate_filename, // This is now of type `fn(&Testcase<EM::Input>, &CorpusId) -> String`
98210 corpus_dir,
99211 solutions_dir,
100212 )
101213 }
102-
103- /// Default `generate_filename` function.
104- #[ expect( clippy:: trivially_copy_pass_by_ref) ]
105- fn generate_filename ( testcase : & Testcase < I > , id : & CorpusId ) -> String {
106- [
107- Some ( id. 0 . to_string ( ) ) ,
108- testcase. filename ( ) . clone ( ) ,
109- testcase
110- . input ( )
111- . as_ref ( )
112- . map ( |t| t. generate_name ( Some ( * id) ) ) ,
113- ]
114- . iter ( )
115- . flatten ( )
116- . map ( String :: as_str)
117- . collect :: < Vec < _ > > ( )
118- . join ( "-" )
119- }
120214}
121215
122216impl < CB1 , CB2 , EM , I , S , Z > DumpToDiskStage < CB1 , CB2 , EM , I , S , Z >
@@ -134,24 +228,7 @@ where
134228 A : Into < PathBuf > ,
135229 B : Into < PathBuf > ,
136230 {
137- let corpus_dir = corpus_dir. into ( ) ;
138- if let Err ( e) = fs:: create_dir ( & corpus_dir) {
139- if !corpus_dir. is_dir ( ) {
140- return Err ( Error :: os_error (
141- e,
142- format ! ( "Error creating directory {}" , corpus_dir. display( ) ) ,
143- ) ) ;
144- }
145- }
146- let solutions_dir = solutions_dir. into ( ) ;
147- if let Err ( e) = fs:: create_dir ( & solutions_dir) {
148- if !solutions_dir. is_dir ( ) {
149- return Err ( Error :: os_error (
150- e,
151- format ! ( "Error creating directory {}" , solutions_dir. display( ) ) ,
152- ) ) ;
153- }
154- }
231+ let ( corpus_dir, solutions_dir) = create_dirs_if_needed ( corpus_dir, solutions_dir) ?;
155232 Ok ( Self {
156233 to_bytes,
157234 generate_filename,
@@ -160,15 +237,38 @@ where
160237 phantom : PhantomData ,
161238 } )
162239 }
240+ }
241+
242+ /// A stage that dumps the corpus and the solutions to disk,
243+ /// using the fuzzer's [`crate::fuzzer::HasTargetBytesConverter`] (if available).
244+ ///
245+ /// Set the converter using the fuzzer builder's [`crate::fuzzer::StdFuzzerBuilder::target_bytes_converter`].
246+ #[ derive( Debug ) ]
247+ pub struct DumpTargetBytesToDiskStage < CB , EM , I , S , Z > {
248+ solutions_dir : PathBuf ,
249+ corpus_dir : PathBuf ,
250+ generate_filename : CB ,
251+ phantom : PhantomData < ( EM , I , S , Z ) > ,
252+ }
163253
254+ impl < CB , E , EM , I , S , P , Z > Stage < E , EM , S , Z > for DumpTargetBytesToDiskStage < CB , EM , I , S , Z >
255+ where
256+ CB : FnMut ( & Testcase < I > , & CorpusId ) -> P ,
257+ S : HasCorpus < I > + HasSolutions < I > + HasRand + HasMetadata ,
258+ P : AsRef < Path > ,
259+ Z : HasTargetBytesConverter ,
260+ Z :: Converter : ToTargetBytes < I > ,
261+ I : Input ,
262+ {
164263 #[ inline]
165- fn dump_state_to_disk < P : AsRef < Path > > ( & mut self , state : & mut S ) -> Result < ( ) , Error >
166- where
167- S : HasCorpus < I > ,
168- CB1 : FnMut ( & Testcase < I > , & S ) -> Vec < u8 > ,
169- CB2 : FnMut ( & Testcase < I > , & CorpusId ) -> P ,
170- {
171- let ( mut corpus_id, mut solutions_id) =
264+ fn perform (
265+ & mut self ,
266+ fuzzer : & mut Z ,
267+ _executor : & mut E ,
268+ state : & mut S ,
269+ _manager : & mut EM ,
270+ ) -> Result < ( ) , Error > {
271+ let ( last_corpus, last_solution) =
172272 if let Some ( meta) = state. metadata_map ( ) . get :: < DumpToDiskMetadata > ( ) {
173273 (
174274 meta. last_corpus . and_then ( |x| state. corpus ( ) . next ( x) ) ,
@@ -178,33 +278,26 @@ where
178278 ( state. corpus ( ) . first ( ) , state. solutions ( ) . first ( ) )
179279 } ;
180280
181- while let Some ( i ) = corpus_id {
182- let mut testcase = state . corpus ( ) . get ( i ) ? . borrow_mut ( ) ;
183- state . corpus ( ) . load_input_into ( & mut testcase ) ? ;
184- let bytes = ( self . to_bytes ) ( & testcase , state ) ;
281+ let mut get_bytes = | tc : & mut Testcase < I > | {
282+ let input = tc . input ( ) . as_ref ( ) . unwrap ( ) ;
283+ Ok ( fuzzer . to_target_bytes ( input ) . to_vec ( ) )
284+ } ;
185285
186- let fname = self
187- . corpus_dir
188- . join ( ( self . generate_filename ) ( & testcase, & i) ) ;
189- let mut f = File :: create ( fname) ?;
190- drop ( f. write_all ( & bytes) ) ;
191-
192- corpus_id = state. corpus ( ) . next ( i) ;
193- }
286+ dump_from_corpus (
287+ state. corpus ( ) ,
288+ & self . corpus_dir ,
289+ & mut self . generate_filename ,
290+ & mut get_bytes,
291+ last_corpus,
292+ ) ?;
194293
195- while let Some ( i) = solutions_id {
196- let mut testcase = state. solutions ( ) . get ( i) ?. borrow_mut ( ) ;
197- state. solutions ( ) . load_input_into ( & mut testcase) ?;
198- let bytes = ( self . to_bytes ) ( & testcase, state) ;
199-
200- let fname = self
201- . solutions_dir
202- . join ( ( self . generate_filename ) ( & testcase, & i) ) ;
203- let mut f = File :: create ( fname) ?;
204- drop ( f. write_all ( & bytes) ) ;
205-
206- solutions_id = state. solutions ( ) . next ( i) ;
207- }
294+ dump_from_corpus (
295+ state. solutions ( ) ,
296+ & self . solutions_dir ,
297+ & mut self . generate_filename ,
298+ & mut get_bytes,
299+ last_solution,
300+ ) ?;
208301
209302 state. add_metadata ( DumpToDiskMetadata {
210303 last_corpus : state. corpus ( ) . last ( ) ,
@@ -214,3 +307,60 @@ where
214307 Ok ( ( ) )
215308 }
216309}
310+
311+ impl < EM , I , S , Z > Restartable < S >
312+ for DumpTargetBytesToDiskStage < fn ( & Testcase < I > , & CorpusId ) -> String , EM , I , S , Z >
313+ {
314+ #[ inline]
315+ fn should_restart ( & mut self , _state : & mut S ) -> Result < bool , Error > {
316+ // Not executing the target, so restart safety is not needed
317+ Ok ( true )
318+ }
319+
320+ #[ inline]
321+ fn clear_progress ( & mut self , _state : & mut S ) -> Result < ( ) , Error > {
322+ // Not executing the target, so restart safety is not needed
323+ Ok ( ( ) )
324+ }
325+ }
326+
327+ /// Implementation for `DumpTargetBytesToDiskStage` with a default `generate_filename` function.
328+ impl < EM , I , S , Z > DumpTargetBytesToDiskStage < fn ( & Testcase < I > , & CorpusId ) -> String , EM , I , S , Z >
329+ where
330+ S : HasSolutions < I > + HasRand + HasMetadata ,
331+ I : Input ,
332+ {
333+ /// Create a new [`DumpTargetBytesToDiskStage`] with a default `generate_filename` function.
334+ pub fn new < A , B > ( corpus_dir : A , solutions_dir : B ) -> Result < Self , Error >
335+ where
336+ A : Into < PathBuf > ,
337+ B : Into < PathBuf > ,
338+ {
339+ Self :: new_with_custom_filenames ( generate_filename, corpus_dir, solutions_dir)
340+ }
341+ }
342+
343+ impl < CB , EM , I , S , Z > DumpTargetBytesToDiskStage < CB , EM , I , S , Z >
344+ where
345+ S : HasMetadata + HasSolutions < I > ,
346+ I : Input ,
347+ {
348+ /// Create a new [`DumpTargetBytesToDiskStage`] with a custom `generate_filename` function.
349+ pub fn new_with_custom_filenames < A , B > (
350+ generate_filename : CB ,
351+ corpus_dir : A ,
352+ solutions_dir : B ,
353+ ) -> Result < Self , Error >
354+ where
355+ A : Into < PathBuf > ,
356+ B : Into < PathBuf > ,
357+ {
358+ let ( corpus_dir, solutions_dir) = create_dirs_if_needed ( corpus_dir, solutions_dir) ?;
359+ Ok ( Self {
360+ generate_filename,
361+ solutions_dir,
362+ corpus_dir,
363+ phantom : PhantomData ,
364+ } )
365+ }
366+ }
0 commit comments