@@ -13,7 +13,7 @@ use rand::seq::SliceRandom;
1313use rand:: thread_rng;
1414use std:: {
1515 collections:: HashMap ,
16- ffi:: OsString ,
16+ ffi:: { OsStr , OsString } ,
1717 path:: { Path , PathBuf } ,
1818 process:: Stdio ,
1919} ;
@@ -64,17 +64,23 @@ impl LibFuzzer {
6464 }
6565
6666 // Build an async `Command`.
67- async fn build_command (
67+ fn build_command (
6868 & self ,
6969 fault_dir : Option < & Path > ,
7070 corpus_dir : Option < & Path > ,
7171 extra_corpus_dirs : Option < & [ & Path ] > ,
72+ extra_args : Option < & [ & OsStr ] > ,
73+ custom_arg_filter : Option < & dyn Fn ( String ) -> Option < String > > ,
7274 ) -> Result < Command > {
73- let std_cmd = self
74- . build_std_command ( fault_dir, corpus_dir, extra_corpus_dirs)
75- . await ?;
76-
77- // Make async.
75+ let std_cmd = self . build_std_command (
76+ fault_dir,
77+ corpus_dir,
78+ extra_corpus_dirs,
79+ extra_args,
80+ custom_arg_filter,
81+ ) ?;
82+
83+ // Make async (turn into tokio::process::Command):
7884 let mut cmd = Command :: from ( std_cmd) ;
7985
8086 // Terminate the process if the `Child` handle is dropped.
@@ -84,11 +90,13 @@ impl LibFuzzer {
8490 }
8591
8692 // Build a non-async `Command`.
87- pub async fn build_std_command (
93+ pub fn build_std_command (
8894 & self ,
8995 fault_dir : Option < & Path > ,
9096 corpus_dir : Option < & Path > ,
9197 extra_corpus_dirs : Option < & [ & Path ] > ,
98+ extra_args : Option < & [ & OsStr ] > ,
99+ custom_arg_filter : Option < & dyn Fn ( String ) -> Option < String > > ,
92100 ) -> Result < std:: process:: Command > {
93101 let mut cmd = std:: process:: Command :: new ( & self . exe ) ;
94102 cmd. env ( PATH , get_path_with_directory ( PATH , & self . setup_dir ) ?)
@@ -107,38 +115,36 @@ impl LibFuzzer {
107115
108116 let expand = Expand :: new ( & self . machine_identity )
109117 . machine_id ( )
110- . await ?
111118 . target_exe ( & self . exe )
112119 . target_options ( & self . options )
113120 . setup_dir ( & self . setup_dir )
114- . set_optional ( self . extra_dir . as_ref ( ) , |expand, extra_dir| {
115- expand. extra_dir ( extra_dir)
116- } )
117- . set_optional ( corpus_dir, |e, corpus_dir| e. input_corpus ( corpus_dir) )
118- . set_optional ( fault_dir, |e, fault_dir| e. crashes ( fault_dir) ) ;
121+ . set_optional ( self . extra_dir . as_ref ( ) , Expand :: extra_dir)
122+ . set_optional ( corpus_dir, Expand :: input_corpus)
123+ . set_optional ( fault_dir, Expand :: crashes) ;
119124
125+ // Expand and set environment variables:
120126 for ( k, v) in & self . env {
121127 cmd. env ( k, expand. evaluate_value ( v) ?) ;
122128 }
123129
124- // Pass custom option arguments.
125- for o in expand. evaluate ( & self . options ) ? {
126- cmd. arg ( o) ;
127- }
128-
129- // Set the read/written main corpus directory.
130+ // Set the read/written main corpus directory:
130131 if let Some ( corpus_dir) = corpus_dir {
131132 cmd. arg ( corpus_dir) ;
132133 }
133134
134- // Set extra corpus directories that will be periodically rescanned.
135+ // Set extra (readonly) corpus directories that will also be used:
135136 if let Some ( extra_corpus_dirs) = extra_corpus_dirs {
136- for dir in extra_corpus_dirs {
137- cmd. arg ( dir) ;
138- }
137+ cmd. args ( extra_corpus_dirs) ;
139138 }
140139
141- // check if a max_time is already set
140+ // Pass any extra arguments that we need; this is done in this function
141+ // rather than the caller so that they can come before any custom options
142+ // that might interfere (e.g. -help=1 must come before -ignore_remaining_args=1).
143+ if let Some ( extra_args) = extra_args {
144+ cmd. args ( extra_args) ;
145+ }
146+
147+ // Check if a max time is already set by the custom options, and set if not:
142148 if !self
143149 . options
144150 . iter ( )
@@ -147,6 +153,18 @@ impl LibFuzzer {
147153 cmd. arg ( format ! ( "-max_total_time={DEFAULT_MAX_TOTAL_SECONDS}" ) ) ;
148154 }
149155
156+ // Pass custom option arguments last, to lessen the chance that they
157+ // interfere with standard options (e.g. use of -ignore_remaining_args=1),
158+ // and also to allow last-one-wins overriding if needed.
159+ //
160+ // We also allow filtering out parameters as well:
161+ cmd. args (
162+ expand
163+ . evaluate ( & self . options ) ?
164+ . into_iter ( )
165+ . filter_map ( custom_arg_filter. unwrap_or ( & Some ) ) ,
166+ ) ;
167+
150168 Ok ( cmd)
151169 }
152170
@@ -194,28 +212,35 @@ impl LibFuzzer {
194212 Ok ( ( ) )
195213 }
196214
215+ // Verify that the libfuzzer exits with a zero return code with a known
216+ // good input, which libfuzzer works as we expect.
197217 async fn check_input ( & self , input : & Path ) -> Result < ( ) > {
198- // Verify that the libfuzzer exits with a zero return code with a known
199- // good input, which libfuzzer works as we expect.
200-
201- let mut cmd = self . build_command ( None , None , None ) . await ?;
202-
203- // Override any arg of the form `-runs=<N>` (last occurrence wins).
204- // In this command invocation, we only ever want to test inputs once.
205- //
206- // Assumes that the presence of an `-args` option was an iteration limit meant
207- // for fuzzing mode. We are only mutating the args of a local `Command`, so the
208- // command used by the `fuzz()` method will still receive the iteration limit.
209- cmd. arg ( "-runs=1" ) ;
210-
211- cmd. arg ( input) ;
218+ let mut cmd = self . build_command (
219+ None ,
220+ None ,
221+ None ,
222+ // Custom args for this run: supply the required input. In this mode,
223+ // LibFuzzer will only execute one run of fuzzing unless overridden
224+ Some ( & [ input. as_ref ( ) ] ) ,
225+ // Filter out any argument starting with `-runs=` from the custom
226+ // target options, if supplied, so that it doesn't make more than
227+ // one run happen:
228+ Some ( & |arg : String | {
229+ if arg. starts_with ( "-runs=" ) {
230+ None
231+ } else {
232+ Some ( arg)
233+ }
234+ } ) ,
235+ ) ?;
212236
213237 let result = cmd
214238 . spawn ( )
215239 . with_context ( || format_err ! ( "libfuzzer failed to start: {}" , self . exe. display( ) ) ) ?
216240 . wait_with_output ( )
217241 . await
218242 . with_context ( || format_err ! ( "libfuzzer failed to run: {}" , self . exe. display( ) ) ) ?;
243+
219244 if !result. status . success ( ) {
220245 bail ! (
221246 "libFuzzer failed when parsing an initial seed {:?}: stdout:{:?} stderr:{:?}" ,
@@ -224,15 +249,15 @@ impl LibFuzzer {
224249 String :: from_utf8_lossy( & result. stderr) ,
225250 ) ;
226251 }
252+
227253 Ok ( ( ) )
228254 }
229255
230256 /// Invoke `{target_exe} -help=1`. If this succeeds, then the dynamic linker is at
231257 /// least able to satisfy the fuzzer's shared library dependencies. User-authored
232258 /// dynamic loading may still fail later on, e.g. in `LLVMFuzzerInitialize()`.
233259 async fn check_help ( & self ) -> Result < ( ) > {
234- let mut cmd = self . build_command ( None , None , None ) . await ?;
235- cmd. arg ( "-help=1" ) ;
260+ let mut cmd = self . build_command ( None , None , None , Some ( & [ "-help=1" . as_ref ( ) ] ) , None ) ?;
236261
237262 let result = cmd
238263 . spawn ( )
@@ -263,7 +288,7 @@ impl LibFuzzer {
263288 }
264289
265290 async fn find_missing_libraries ( & self ) -> Result < Vec < String > > {
266- let cmd = self . build_std_command ( None , None , None ) . await ?;
291+ let cmd = self . build_std_command ( None , None , None , None , None ) ?;
267292
268293 #[ cfg( target_os = "linux" ) ]
269294 let blocking = move || dynamic_library:: linux:: find_missing ( cmd) ;
@@ -284,13 +309,6 @@ impl LibFuzzer {
284309 extra_corpus_dirs : & [ impl AsRef < Path > ] ,
285310 ) -> Result < Child > {
286311 let extra_corpus_dirs: Vec < & Path > = extra_corpus_dirs. iter ( ) . map ( |x| x. as_ref ( ) ) . collect ( ) ;
287- let mut cmd = self
288- . build_command (
289- Some ( fault_dir. as_ref ( ) ) ,
290- Some ( corpus_dir. as_ref ( ) ) ,
291- Some ( & extra_corpus_dirs) ,
292- )
293- . await ?;
294312
295313 // When writing a new faulting input, the libFuzzer runtime _exactly_
296314 // prepends the value of `-artifact_prefix` to the new file name. To
@@ -300,7 +318,13 @@ impl LibFuzzer {
300318 let artifact_prefix: OsString =
301319 format ! ( "-artifact_prefix={}/" , fault_dir. as_ref( ) . display( ) ) . into ( ) ;
302320
303- cmd. arg ( & artifact_prefix) ;
321+ let mut cmd = self . build_command (
322+ Some ( fault_dir. as_ref ( ) ) ,
323+ Some ( corpus_dir. as_ref ( ) ) ,
324+ Some ( & extra_corpus_dirs) ,
325+ Some ( & [ & artifact_prefix] ) ,
326+ None ,
327+ ) ?;
304328
305329 let child = cmd
306330 . spawn ( )
@@ -344,10 +368,13 @@ impl LibFuzzer {
344368 extra_corpus_dirs : & [ impl AsRef < Path > ] ,
345369 ) -> Result < LibFuzzerMergeOutput > {
346370 let extra_corpus_dirs: Vec < & Path > = extra_corpus_dirs. iter ( ) . map ( |x| x. as_ref ( ) ) . collect ( ) ;
347- let mut cmd = self
348- . build_command ( None , Some ( corpus_dir. as_ref ( ) ) , Some ( & extra_corpus_dirs) )
349- . await ?;
350- cmd. arg ( "-merge=1" ) ;
371+ let mut cmd = self . build_command (
372+ None ,
373+ Some ( corpus_dir. as_ref ( ) ) ,
374+ Some ( & extra_corpus_dirs) ,
375+ Some ( & [ "-merge=1" . as_ref ( ) ] ) ,
376+ None ,
377+ ) ?;
351378
352379 let output = cmd
353380 . spawn ( )
0 commit comments