4
4
//! For a lower memory footprint, consider using [`crate::corpus::CachedOnDiskCorpus`]
5
5
//! which only stores a certain number of [`Testcase`]s and removes additional ones in a FIFO manner.
6
6
7
- use alloc:: string:: String ;
7
+ use alloc:: string:: { String , ToString } ;
8
8
use core:: cell:: { Ref , RefCell , RefMut } ;
9
9
use std:: {
10
10
fs,
11
11
fs:: { File , OpenOptions } ,
12
12
io,
13
- io:: Write ,
13
+ io:: { Read , Seek , SeekFrom , Write } ,
14
14
path:: { Path , PathBuf } ,
15
15
} ;
16
16
17
+ use fs2:: FileExt ;
17
18
#[ cfg( feature = "gzip" ) ]
18
19
use libafl_bolts:: compress:: GzipCompressor ;
19
20
use serde:: { Deserialize , Serialize } ;
@@ -33,7 +34,11 @@ use crate::{
33
34
/// If the create fails for _any_ reason, including, but not limited to, a preexisting existing file of that name,
34
35
/// it will instead return the respective [`io::Error`].
35
36
fn create_new < P : AsRef < Path > > ( path : P ) -> Result < File , io:: Error > {
36
- OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( path)
37
+ OpenOptions :: new ( )
38
+ . write ( true )
39
+ . read ( true )
40
+ . create_new ( true )
41
+ . open ( path)
37
42
}
38
43
39
44
/// Tries to create the given `path` and returns `None` _only_ if the file already existed.
85
90
fn add ( & mut self , testcase : Testcase < I > ) -> Result < CorpusId , Error > {
86
91
let id = self . inner . add ( testcase) ?;
87
92
let testcase = & mut self . get ( id) . unwrap ( ) . borrow_mut ( ) ;
88
- self . save_testcase ( testcase, id ) ?;
93
+ self . save_testcase ( testcase, Some ( id ) ) ?;
89
94
* testcase. input_mut ( ) = None ;
90
95
Ok ( id)
91
96
}
95
100
fn add_disabled ( & mut self , testcase : Testcase < I > ) -> Result < CorpusId , Error > {
96
101
let id = self . inner . add_disabled ( testcase) ?;
97
102
let testcase = & mut self . get_from_all ( id) . unwrap ( ) . borrow_mut ( ) ;
98
- self . save_testcase ( testcase, id ) ?;
103
+ self . save_testcase ( testcase, Some ( id ) ) ?;
99
104
* testcase. input_mut ( ) = None ;
100
105
Ok ( id)
101
106
}
@@ -106,7 +111,7 @@ where
106
111
let entry = self . inner . replace ( id, testcase) ?;
107
112
self . remove_testcase ( & entry) ?;
108
113
let testcase = & mut self . get ( id) . unwrap ( ) . borrow_mut ( ) ;
109
- self . save_testcase ( testcase, id ) ?;
114
+ self . save_testcase ( testcase, Some ( id ) ) ?;
110
115
* testcase. input_mut ( ) = None ;
111
116
Ok ( entry)
112
117
}
@@ -309,12 +314,19 @@ impl<I> InMemoryOnDiskCorpus<I> {
309
314
310
315
/// Sets the filename for a [`Testcase`].
311
316
/// If an error gets returned from the corpus (i.e., file exists), we'll have to retry with a different filename.
317
+ /// Renaming testcases will most likely cause duplicate testcases to not be handled correctly
318
+ /// if testcases with the same input are not given the same filename.
319
+ /// Only rename when you know what you are doing.
312
320
#[ inline]
313
321
pub fn rename_testcase (
314
322
& self ,
315
323
testcase : & mut Testcase < I > ,
316
324
filename : String ,
317
- ) -> Result < ( ) , Error > {
325
+ id : Option < CorpusId > ,
326
+ ) -> Result < ( ) , Error >
327
+ where
328
+ I : Input ,
329
+ {
318
330
if testcase. filename ( ) . is_some ( ) {
319
331
// We are renaming!
320
332
@@ -327,36 +339,10 @@ impl<I> InMemoryOnDiskCorpus<I> {
327
339
return Ok ( ( ) ) ;
328
340
}
329
341
330
- if self . locking {
331
- let new_lock_filename = format ! ( ".{new_filename}.lafl_lock" ) ;
332
-
333
- // Try to create lock file for new testcases
334
- if let Err ( err) = create_new ( self . dir_path . join ( & new_lock_filename) ) {
335
- * testcase. filename_mut ( ) = Some ( old_filename) ;
336
- return Err ( Error :: illegal_state ( format ! (
337
- "Unable to create lock file {new_lock_filename} for new testcase: {err}"
338
- ) ) ) ;
339
- }
340
- }
341
-
342
342
let new_file_path = self . dir_path . join ( & new_filename) ;
343
-
344
- fs:: rename ( testcase. file_path ( ) . as_ref ( ) . unwrap ( ) , & new_file_path) ?;
345
-
346
- let new_metadata_path = {
347
- if let Some ( old_metadata_path) = testcase. metadata_path ( ) {
348
- // We have metadata. Let's rename it.
349
- let new_metadata_path = self . dir_path . join ( format ! ( ".{new_filename}.metadata" ) ) ;
350
- fs:: rename ( old_metadata_path, & new_metadata_path) ?;
351
-
352
- Some ( new_metadata_path)
353
- } else {
354
- None
355
- }
356
- } ;
357
-
358
- * testcase. metadata_path_mut ( ) = new_metadata_path;
343
+ self . remove_testcase ( testcase) ?;
359
344
* testcase. filename_mut ( ) = Some ( new_filename) ;
345
+ self . save_testcase ( testcase, id) ?;
360
346
* testcase. file_path_mut ( ) = Some ( new_file_path) ;
361
347
362
348
Ok ( ( ) )
@@ -367,42 +353,54 @@ impl<I> InMemoryOnDiskCorpus<I> {
367
353
}
368
354
}
369
355
370
- fn save_testcase ( & self , testcase : & mut Testcase < I > , id : CorpusId ) -> Result < ( ) , Error >
356
+ fn save_testcase ( & self , testcase : & mut Testcase < I > , id : Option < CorpusId > ) -> Result < ( ) , Error >
371
357
where
372
358
I : Input ,
373
359
{
374
- let file_name_orig = testcase. filename_mut ( ) . take ( ) . unwrap_or_else ( || {
360
+ let file_name = testcase. filename_mut ( ) . take ( ) . unwrap_or_else ( || {
375
361
// TODO walk entry metadata to ask for pieces of filename (e.g. :havoc in AFL)
376
- testcase. input ( ) . as_ref ( ) . unwrap ( ) . generate_name ( Some ( id ) )
362
+ testcase. input ( ) . as_ref ( ) . unwrap ( ) . generate_name ( id )
377
363
} ) ;
378
364
379
- // New testcase, we need to save it.
380
- let mut file_name = file_name_orig. clone ( ) ;
381
-
382
- let mut ctr = 2 ;
383
- let file_name = if self . locking {
384
- loop {
385
- let lockfile_name = format ! ( ".{file_name}.lafl_lock" ) ;
386
- let lockfile_path = self . dir_path . join ( lockfile_name) ;
387
-
388
- if try_create_new ( lockfile_path) ?. is_some ( ) {
389
- break file_name;
390
- }
365
+ let mut ctr = String :: new ( ) ;
366
+ if self . locking {
367
+ let lockfile_name = format ! ( ".{file_name}" ) ;
368
+ let lockfile_path = self . dir_path . join ( lockfile_name) ;
369
+
370
+ let mut lockfile = try_create_new ( & lockfile_path) ?. unwrap_or (
371
+ OpenOptions :: new ( )
372
+ . write ( true )
373
+ . read ( true )
374
+ . open ( & lockfile_path) ?,
375
+ ) ;
376
+ lockfile. lock_exclusive ( ) ?;
377
+
378
+ lockfile. read_to_string ( & mut ctr) ?;
379
+ ctr = if ctr. is_empty ( ) {
380
+ String :: from ( "1" )
381
+ } else {
382
+ ( ctr. trim ( ) . parse :: < u32 > ( ) ? + 1 ) . to_string ( )
383
+ } ;
391
384
392
- file_name = format ! ( "{file_name_orig}-{ctr}" ) ;
393
- ctr += 1 ;
394
- }
395
- } else {
396
- file_name
397
- } ;
385
+ lockfile. seek ( SeekFrom :: Start ( 0 ) ) ?;
386
+ lockfile. write_all ( ctr. as_bytes ( ) ) ?;
387
+ }
398
388
399
389
if testcase. file_path ( ) . is_none ( ) {
400
390
* testcase. file_path_mut ( ) = Some ( self . dir_path . join ( & file_name) ) ;
401
391
}
402
392
* testcase. filename_mut ( ) = Some ( file_name) ;
403
393
404
394
if self . meta_format . is_some ( ) {
405
- let metafile_name = format ! ( ".{}.metadata" , testcase. filename( ) . as_ref( ) . unwrap( ) ) ;
395
+ let metafile_name = if self . locking {
396
+ format ! (
397
+ ".{}_{}.metadata" ,
398
+ testcase. filename( ) . as_ref( ) . unwrap( ) ,
399
+ ctr
400
+ )
401
+ } else {
402
+ format ! ( ".{}.metadata" , testcase. filename( ) . as_ref( ) . unwrap( ) )
403
+ } ;
406
404
let metafile_path = self . dir_path . join ( & metafile_name) ;
407
405
let mut tmpfile_path = metafile_path. clone ( ) ;
408
406
tmpfile_path. set_file_name ( format ! ( ".{metafile_name}.tmp" , ) ) ;
@@ -445,15 +443,36 @@ impl<I> InMemoryOnDiskCorpus<I> {
445
443
446
444
fn remove_testcase ( & self , testcase : & Testcase < I > ) -> Result < ( ) , Error > {
447
445
if let Some ( filename) = testcase. filename ( ) {
446
+ let mut ctr = String :: new ( ) ;
447
+ if self . locking {
448
+ let lockfile_path = self . dir_path . join ( format ! ( ".{filename}" ) ) ;
449
+ let mut lockfile = OpenOptions :: new ( )
450
+ . write ( true )
451
+ . read ( true )
452
+ . open ( & lockfile_path) ?;
453
+
454
+ lockfile. lock_exclusive ( ) ?;
455
+ lockfile. read_to_string ( & mut ctr) ?;
456
+ ctr = ctr. trim ( ) . to_string ( ) ;
457
+
458
+ if ctr == "1" {
459
+ FileExt :: unlock ( & lockfile) ?;
460
+ drop ( fs:: remove_file ( lockfile_path) ) ;
461
+ } else {
462
+ lockfile. seek ( SeekFrom :: Start ( 0 ) ) ?;
463
+ lockfile. write_all ( & ( ctr. parse :: < u32 > ( ) ? - 1 ) . to_le_bytes ( ) ) ?;
464
+ return Ok ( ( ) ) ;
465
+ }
466
+ }
467
+
448
468
fs:: remove_file ( self . dir_path . join ( filename) ) ?;
449
469
if self . meta_format . is_some ( ) {
450
- fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}.metadata" ) ) ) ?;
470
+ if self . locking {
471
+ fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}_{ctr}.metadata" ) ) ) ?;
472
+ } else {
473
+ fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}.metadata" ) ) ) ?;
474
+ }
451
475
}
452
- // also try to remove the corresponding `.lafl_lock` file if it still exists
453
- // (even though it shouldn't exist anymore, at this point in time)
454
- drop ( fs:: remove_file (
455
- self . dir_path . join ( format ! ( ".{filename}.lafl_lock" ) ) ,
456
- ) ) ;
457
476
}
458
477
Ok ( ( ) )
459
478
}
0 commit comments