├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── harness.rs ├── plot.plt ├── plot_timecrunch.plt └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /data 3 | /guided* 4 | /histo.plt 5 | /output 6 | /output.png 7 | /test* 8 | /*.txt 9 | /time* 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "fuzztheory" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzztheory" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Brandon Falk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | <> 4 | 5 | -------------------------------------------------------------------------------- /harness.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::time::Instant; 4 | use std::sync::{Arc, Mutex}; 5 | use std::collections::{BTreeMap, BTreeSet}; 6 | 7 | /// Maximum number of simulated cores 8 | const MAX_SIMULATED_CORES: usize = 2001; 9 | 10 | struct Rng(usize); 11 | 12 | impl Rng { 13 | fn new() -> Self { 14 | Rng(unsafe { std::arch::x86_64::_rdtsc() as usize }) 15 | } 16 | fn rand(&mut self) -> usize { 17 | let orig = self.0; 18 | self.0 ^= self.0 << 13; 19 | self.0 ^= self.0 >> 17; 20 | self.0 ^= self.0 << 43; 21 | orig 22 | } 23 | } 24 | 25 | struct Fuzzer { 26 | /// A random number generator 27 | rng: Rng, 28 | 29 | /// Should the fuzzer use input corpus data to build upon. Eg. should it be 30 | /// a coverage guided fuzzer 31 | coverage_guided: bool, 32 | 33 | /// Should the fuzzer share inputs between simulated cores. This allows 34 | /// the cores to collaboratively share coverage information and build off 35 | /// eachothers progress. 36 | shared_inputs: bool, 37 | 38 | /// Should the fuzzer share results between simulated cores. This allows 39 | /// the coverage databases to be shared between the cores, thus making 40 | /// them work together towards the same goal. 41 | shared_results: bool, 42 | 43 | /// How many simulated cores should run the fuzzer. This is used to 44 | /// evaluate the properties of scaling the fuzzer, but does not actually 45 | /// cause any parallelism to be used. 46 | workers: usize, 47 | 48 | /// Database used to keep track of per-worker coverage frequencies 49 | coverage: Box<[[u64; NUM_COVERAGE]; MAX_SIMULATED_CORES]>, 50 | 51 | /// Database used to keep track of per-worker input databases 52 | inputs: Box<[Vec<[u8; NUM_BYTES]>; MAX_SIMULATED_CORES]>, 53 | 54 | /// Total number of invocations of `crashme` 55 | fuzz_cases: u64, 56 | 57 | /// Maximum amount of time to execute for 58 | time_constraint: Option, 59 | } 60 | 61 | impl Fuzzer { 62 | fn new() -> Self { 63 | let mut coverage = std::mem::ManuallyDrop::new(Vec::new()); 64 | for _ in 0..MAX_SIMULATED_CORES { 65 | coverage.push([0u64; NUM_COVERAGE]); 66 | } 67 | let coverage = unsafe { 68 | Box::from_raw( 69 | coverage.as_mut_ptr() as *mut [[u64; NUM_COVERAGE]; MAX_SIMULATED_CORES]) 70 | }; 71 | 72 | let mut inputs = std::mem::ManuallyDrop::new(Vec::new()); 73 | for _ in 0..MAX_SIMULATED_CORES { 74 | inputs.push(Vec::<[u8; NUM_BYTES]>::new()); 75 | } 76 | let inputs = unsafe { 77 | Box::from_raw( 78 | inputs.as_mut_ptr() as *mut [Vec<[u8; NUM_BYTES]>; MAX_SIMULATED_CORES]) 79 | }; 80 | 81 | Fuzzer { 82 | rng: Rng::new(), 83 | coverage_guided: false, 84 | shared_inputs: false, 85 | shared_results: false, 86 | workers: 1, 87 | fuzz_cases: 0, 88 | coverage: coverage, 89 | inputs: inputs, 90 | time_constraint: None, 91 | } 92 | } 93 | 94 | fn start(&mut self) -> Result { 95 | // Get access to the RNG 96 | let rng = &mut self.rng; 97 | 98 | // If the workers are collaborative, share a single database. 99 | let num_input_dbs = if self.shared_inputs { 1 } else { self.workers }; 100 | let num_output_dbs = if self.shared_results { 1 } else { self.workers }; 101 | 102 | // Fuzz input starts as all zeros 103 | let mut input = [0u8; NUM_BYTES]; 104 | 105 | // Number of fuzz cases performed, shared between all workers. 106 | let mut cases = 0u64; 107 | 108 | // Clear input databases 109 | for idb in 0..num_input_dbs { 110 | self.inputs[idb].clear(); 111 | } 112 | 113 | // Clear result databases 114 | for odb in 0..num_output_dbs { 115 | self.coverage[odb].iter_mut().for_each(|x| *x = 0); 116 | } 117 | 118 | // Fuzz loop 119 | loop { 120 | for worker in 0..self.workers { 121 | // Update number of cases (shared between all workers) 122 | cases += 1; 123 | 124 | // Get access to the worker-specfic database 125 | let input_db = &mut self.inputs[worker % num_input_dbs]; 126 | let coverage = &mut self.coverage[worker % num_output_dbs]; 127 | 128 | // Select an input from the input database, if it is not empty 129 | if self.coverage_guided && input_db.len() > 0 { 130 | input.copy_from_slice( 131 | &input_db[rng.rand() % input_db.len()]); 132 | } 133 | 134 | // Randomly replace up to 8 bytes with a random value at random 135 | // locations 136 | for _ in 0..rng.rand() % 8 + 1 { 137 | input[rng.rand() % input.len()] = rng.rand() as u8; 138 | } 139 | 140 | // Invoke the "program" we're fuzzing 141 | let new_coverage = crashme(&input, coverage); 142 | self.fuzz_cases += 1; 143 | 144 | // Get the uptime (assuming workers are parallel we compute 145 | // this by dividing fuzz cases by number of workers) 146 | let uptime = cases as f64 / self.workers as f64; 147 | 148 | if self.time_constraint.is_some() && 149 | Some(uptime) >= self.time_constraint { 150 | // Determine the number of known coverage 151 | let found_coverage = 152 | coverage.iter().filter(|&&x| x > 0).count(); 153 | return Err(found_coverage); 154 | } 155 | 156 | // Save the input if it generated new coverage 157 | if new_coverage { 158 | // Save this input as we caused new coverage 159 | input_db.push(input); 160 | 161 | // Determine the number of known coverage 162 | let found_coverage = 163 | coverage.iter().filter(|&&x| x > 0).count(); 164 | 165 | // Fuzzing complete if we found all coverage 166 | if found_coverage == coverage.len() { 167 | return Ok(uptime); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | fn doit(time_constraint: Option) { 176 | /// Number of threads to use to perform the analysis 177 | const NUM_THREADS: usize = 1; 178 | 179 | // Compute the base for an exponential function which generates 180 | // `MAX_X_RESOULTION` datapoints such that 181 | // expbase^MAX_X_RESOLUTION = MAX_SIMULATED_CORES 182 | const MAX_X_RESOLUTION: usize = 100; 183 | 184 | /// Number of iterations of each fuzz attempt to perform, to generate an 185 | /// average value per data point. 186 | const AVERAGES: usize = 1000; 187 | 188 | // List of active threads such that we can join() on their completion 189 | let mut threads = Vec::new(); 190 | 191 | // Generate a list of things to do 192 | let mut todo = BTreeSet::new(); 193 | for &shared_inputs in &[false, true] { 194 | for &shared_results in &[true] { 195 | for &guided in &[true] { 196 | for x in (1..=MAX_X_RESOLUTION).step_by(1) { 197 | let num_workers = if false { 198 | let expbase = (MAX_SIMULATED_CORES as f64) 199 | .powf(1. / MAX_X_RESOLUTION as f64); 200 | expbase.powf(x as f64) 201 | } else { 202 | (x as f64 / MAX_X_RESOLUTION as f64) * 203 | MAX_SIMULATED_CORES as f64 204 | } as usize; 205 | todo.insert( 206 | (guided, shared_inputs, shared_results, num_workers)); 207 | } 208 | } 209 | } 210 | } 211 | let todo: Vec<_> = todo.into_iter().collect(); 212 | 213 | // Wrap up the todo in a mutex and an arc so we can share it between 214 | // workers 215 | let todo = Arc::new(Mutex::new(todo)); 216 | 217 | // The results which map filenames to (core, mean, stddev) tuples which 218 | // can be sorted before writing to a file 219 | let results = Arc::new(Mutex::new(BTreeMap::new())); 220 | 221 | for _ in 0..NUM_THREADS { 222 | // Make a clone of the arc so we can move it into the thread 223 | let todo = todo.clone(); 224 | let results = results.clone(); 225 | 226 | threads.push(std::thread::spawn(move || { 227 | let it = Instant::now(); 228 | 229 | let mut fuzzer = Fuzzer::new(); 230 | 231 | loop { 232 | // Get some work to do 233 | let work = { 234 | //print!("Todo {}\n", todo.lock().unwrap().len()); 235 | todo.lock().unwrap().pop() 236 | }; 237 | 238 | // Check if we have work to do 239 | if let Some((guided, si, sr, workers)) = work { 240 | fuzzer.coverage_guided = guided; 241 | fuzzer.shared_inputs = si; 242 | fuzzer.shared_results = sr; 243 | fuzzer.workers = workers; 244 | fuzzer.time_constraint = time_constraint; 245 | 246 | // Generate the filename we're going to use for this data 247 | // point. 248 | let fname = format!( 249 | "coverage_{}_inputshare_{}_resultshare_{}.txt", 250 | guided, si, sr); 251 | 252 | // Track if any of the tests found all possible coverage 253 | // during a time constrained mode. This will indicate that 254 | // the data is invalid and should not be used. 255 | let mut exhaust = false; 256 | 257 | // Run the worker multiple times, generating the averages 258 | let mut sum = 0f64; 259 | let mut sum_pow2 = 0f64; 260 | for _ in 0..AVERAGES { 261 | // Run the fuzz case! 262 | let tmp = fuzzer.start(); 263 | 264 | if false { 265 | let elapsed = (Instant::now() - it).as_secs_f64(); 266 | print!("fcps {:10.0}\n", 267 | fuzzer.fuzz_cases as f64 / elapsed); 268 | } 269 | 270 | let ret = if time_constraint.is_some() { 271 | if tmp.is_ok() { 272 | // We ran out of coverage to gain, stop early 273 | exhaust = true; 274 | break; 275 | } 276 | 277 | // Get the number of coverage records at the 278 | // timeout, otherwise if it completed it's equal 279 | // to the total amount of possible coverage 280 | // events. 281 | tmp.err().unwrap_or(NUM_COVERAGE) as f64 282 | } else { 283 | // Get the time it took to get full coverage and 284 | tmp.unwrap() 285 | }; 286 | 287 | sum += ret; 288 | sum_pow2 += ret * ret; 289 | } 290 | let mean = sum / AVERAGES as f64; 291 | let std = ((sum_pow2 / AVERAGES as f64) - (mean * mean)) 292 | .sqrt(); 293 | 294 | // Record the results 295 | results.lock().unwrap().entry(fname).or_insert(Vec::new()) 296 | .push((workers, mean, std, exhaust)); 297 | } else { 298 | // No more work, stop running the thread 299 | break; 300 | } 301 | } 302 | })); 303 | } 304 | 305 | for thr in threads { thr.join().unwrap(); } 306 | 307 | let mut results = results.lock().unwrap(); 308 | 309 | // Sort and log the results 310 | for (filename, records) in results.iter_mut() { 311 | records.sort_by_key(|x| x.0); 312 | 313 | let mut fd = File::create(filename).unwrap(); 314 | for (num_workers, mean, stddev, exhaust) in records { 315 | write!(fd, "{:10} {:20.10} {:20.10} {:6}\n", 316 | num_workers, mean, stddev, exhaust) 317 | .unwrap(); 318 | } 319 | } 320 | 321 | let shared = 322 | &results["coverage_true_inputshare_true_resultshare_true.txt"]; 323 | let unshared = 324 | &results["coverage_true_inputshare_false_resultshare_true.txt"]; 325 | for (shared, unshared) in shared.iter().zip(unshared.iter()) { 326 | assert!(shared.0 == unshared.0); 327 | 328 | let invalid = shared.3 | unshared.3; 329 | 330 | if !invalid { 331 | /* 332 | print!("{:10} {:10.6} {:15.8}\n", shared.0, 333 | time_constraint.unwrap_or(0.), 334 | (shared.1 - unshared.1) / unshared.1);*/ 335 | } 336 | } 337 | } 338 | 339 | pub fn gen_heatmap() { 340 | /*// Get a reasonable fastest time to find all coverage 341 | let mut fuzzer = Fuzzer::new(); 342 | fuzzer.coverage_guided = true; 343 | fuzzer.shared_inputs = true; 344 | fuzzer.shared_results = true; 345 | fuzzer.workers = MAX_SIMULATED_CORES; 346 | 347 | print!("Calibating upper bound\n"); 348 | let tmp = fuzzer.start(); 349 | panic!("{:?}\n", tmp);*/ 350 | 351 | const MAX_Y_RESOLUTION: usize = 100; 352 | const MAX_Y_POINT: f64 = 1.0; 353 | 354 | for timeout in 1..=MAX_Y_RESOLUTION { 355 | let timeout = if false { 356 | let expbase = (2. as f64) 357 | .powf(1.0 / MAX_Y_RESOLUTION as f64); 358 | expbase.powf(timeout as f64) - 1. 359 | } else { 360 | (timeout as f64 / MAX_Y_RESOLUTION as f64) * MAX_Y_POINT 361 | }; 362 | //print!("{}\n", timeout); 363 | doit(Some(timeout)); 364 | } 365 | } 366 | 367 | pub fn perf() { 368 | let mut fuzzer = Fuzzer::new(); 369 | 370 | let it = Instant::now(); 371 | loop { 372 | fuzzer.coverage_guided = true; 373 | fuzzer.shared_inputs = false; 374 | fuzzer.shared_results = false; 375 | fuzzer.workers = 1; 376 | fuzzer.start(); 377 | 378 | let elapsed = (Instant::now() - it).as_secs_f64(); 379 | print!("{:12.2} fuzz cases/second\n", fuzzer.fuzz_cases as f64 / elapsed); 380 | } 381 | } 382 | 383 | fn main() { 384 | perf(); 385 | } 386 | 387 | -------------------------------------------------------------------------------- /plot.plt: -------------------------------------------------------------------------------- 1 | set terminal wxt size 3000,2000 2 | #set output "output.png" 3 | set logscale xy 4 | set grid 5 | set title "Time to find all known bugs with different fuzzer configurations" 6 | set xlabel "Number of Cores" 7 | set ylabel "Average \"time\" to all bugs and coverage (in fuzz cases / cores)" 8 | set key font "monospace,14" 9 | set key right 10 | plot \ 11 | "coverage_false_inputshare_false_resultshare_false.txt" u 1:2 w lp t "Feedback: No, Share input: No, Share results: No ", \ 12 | "coverage_false_inputshare_false_resultshare_true.txt" u 1:2 w lp t "Feedback: No, Share input: No, Share results: Yes", \ 13 | "coverage_false_inputshare_true_resultshare_false.txt" u 1:2 w lp t "Feedback: No, Share input: Yes, Share results: No ", \ 14 | "coverage_false_inputshare_true_resultshare_true.txt" u 1:2 w lp t "Feedback: No, Share input: Yes, Share results: Yes", \ 15 | "coverage_true_inputshare_false_resultshare_false.txt" u 1:2 w lp t "Feedback: Yes, Share input: No, Share results: No ", \ 16 | "coverage_true_inputshare_false_resultshare_true.txt" u 1:2 w lp t "Feedback: Yes, Share input: No, Share results: Yes", \ 17 | "coverage_true_inputshare_true_resultshare_false.txt" u 1:2 w lp t "Feedback: Yes, Share input: Yes, Share results: No ", \ 18 | "coverage_true_inputshare_true_resultshare_true.txt" u 1:2 w lp t "Feedback: Yes, Share input: Yes, Share results: Yes" 19 | 20 | -------------------------------------------------------------------------------- /plot_timecrunch.plt: -------------------------------------------------------------------------------- 1 | set terminal wxt size 1440,900 2 | #set output "output.png" 3 | set logscale x 4 | set grid 5 | set title "Coverage numbers with a given number of cores at a fixed time budget" 6 | set xlabel "Number of Cores" 7 | set ylabel "Coverage numbers at a fixed time limit" 8 | set key font "monospace,14" 9 | set key left 10 | plot \ 11 | "coverage_false_inputshare_false_resultshare_false.txt" u 1:2 w lp t "Feedback: No, Share input: No, Share results: No ", \ 12 | "coverage_false_inputshare_false_resultshare_true.txt" u 1:2 w lp t "Feedback: No, Share input: No, Share results: Yes", \ 13 | "coverage_false_inputshare_true_resultshare_false.txt" u 1:2 w lp t "Feedback: No, Share input: Yes, Share results: No ", \ 14 | "coverage_false_inputshare_true_resultshare_true.txt" u 1:2 w lp t "Feedback: No, Share input: Yes, Share results: Yes", \ 15 | "coverage_true_inputshare_false_resultshare_false.txt" u 1:2 w lp t "Feedback: Yes, Share input: No, Share results: No ", \ 16 | "coverage_true_inputshare_false_resultshare_true.txt" u 1:2 w lp t "Feedback: Yes, Share input: No, Share results: Yes", \ 17 | "coverage_true_inputshare_true_resultshare_false.txt" u 1:2 w lp t "Feedback: Yes, Share input: Yes, Share results: No ", \ 18 | "coverage_true_inputshare_true_resultshare_true.txt" u 1:2 w lp t "Feedback: Yes, Share input: Yes, Share results: Yes" 19 | 20 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::collections::BTreeSet; 3 | use std::process::Command; 4 | 5 | struct Rng(usize); 6 | impl Rng { 7 | fn new() -> Self { 8 | //Rng(unsafe { std::arch::x86_64::_rdtsc() as usize }) 9 | Rng(0x2f7151ffd59720b3) 10 | } 11 | fn rand(&mut self) -> usize { 12 | let orig = self.0; 13 | self.0 ^= self.0 << 13; 14 | self.0 ^= self.0 >> 17; 15 | self.0 ^= self.0 << 43; 16 | orig 17 | } 18 | } 19 | 20 | fn proggen() -> io::Result<()> { 21 | // Create an RNG 22 | let mut rng = Rng::new(); 23 | 24 | // Create a string to contain our output program source code 25 | let mut program = String::new(); 26 | 27 | // A set containing all of the bit indicies which have been used from the 28 | // input file. This allows us to allocate out bit slices from the input 29 | // file to generate different conditions. 30 | let mut used_bits: BTreeSet = BTreeSet::new(); 31 | 32 | // Maximum size of the input file in bits. This means bit indicies which 33 | // are used for the input of the program always are in a range of 34 | // [0, MAX_INPUT_SIZE_BITS). 35 | const MAX_INPUT_SIZE_BITS: usize = 1024; 36 | 37 | // !!! NOTE !!! 38 | // All the below chances are the "one in " chance figures. 39 | 40 | // Chance of generating an if statement 41 | const IF_CHANCE: usize = 4; 42 | 43 | // Chance of ending the current if statement (ending the block) 44 | const END_BLOCK_CHANCE: usize = 4; 45 | 46 | // Chance of ending the program generation, finishing all unfinished blocks 47 | // unconditionally. 48 | // This is effectively what limits the size of the program (and the 49 | // `MAX_INPUT_SIZE_BITS`) 50 | const DONE_CHANCE: usize = 128; 51 | 52 | // Minimum number of blocks to generate (exiting the loop will not occur 53 | // until at least this many blocks are generated). 54 | const MIN_BLOCKS: u64 = 5000; 55 | 56 | // Maximum number of bit allocation failures until we finally give up. 57 | // 58 | // Allowing failures effectively makes deeper branches less complex, which 59 | // typically will make the graph not very realistic to a real program as 60 | // it can go exponential as subsequent branches are easier to solve. 61 | const MAX_ALLOC_FAILURES: usize = 1; 62 | 63 | // Macro which will find unused bits by randomly generating bit slices and 64 | // only returning once a bit slice is found that is not already used. 65 | // Further, this will only look for bit slices which fit inside of a 66 | // byte value which is aligned. This ensures that the bit slice can be a 67 | // simple mask and compare against a single volatile byte read. 68 | macro_rules! find_unused_bits { 69 | ($num_bits:expr, $timeout:expr) => {{ 70 | // Make sure the number of bits fits within a byte 71 | assert!($num_bits > 0 && $num_bits <= 8, 72 | "Invalid bit size for find_unused_bits"); 73 | 74 | let mut iters = 0u64; 75 | 'try_another_slice: loop { 76 | // Give up on the search after a user-defined threshold 77 | if iters >= $timeout { 78 | break None; 79 | } 80 | iters += 1; 81 | 82 | // Find the start and end bit indicies [bit_start, bit_end] 83 | let bit_start = rng.rand() % MAX_INPUT_SIZE_BITS; 84 | let bit_end = bit_start + $num_bits - 1; 85 | 86 | // Bit overflow or bits spanning a byte boundary 87 | if bit_end >= MAX_INPUT_SIZE_BITS || 88 | (bit_start / 8) != (bit_end / 8) { 89 | continue 'try_another_slice; 90 | } 91 | 92 | // Go through each bit index looking for if it is used 93 | for bit in bit_start..bit_end + 1 { 94 | if used_bits.contains(&bit) { 95 | continue 'try_another_slice; 96 | } 97 | } 98 | 99 | // At this point the slice is free! Mark it as used! 100 | for bit in bit_start..bit_end + 1 { 101 | used_bits.insert(bit); 102 | } 103 | 104 | break Some((bit_start, bit_end)); 105 | } 106 | }} 107 | } 108 | 109 | // Tab/nested if depth of the program 110 | let mut depth = 1; 111 | 112 | // Number of blocks 113 | let mut num_blocks = 0u64; 114 | 115 | // Tab in the program by `depth` tabs 116 | macro_rules! tab { () => { for _ in 0..depth { program += " "; } } } 117 | 118 | // Generate a coverage record based on the unique block ID, then update 119 | // the number of blocks 120 | macro_rules! coverage { 121 | () => { 122 | tab!(); 123 | program += &format!( 124 | "if _coverage[{}] == 0 {{ new_coverage = true; }}\n", 125 | num_blocks); 126 | tab!(); 127 | program += &format!("_coverage[{}] += 1;\n", num_blocks); 128 | num_blocks += 1; 129 | } 130 | } 131 | 132 | // The good stuff 133 | // Returns `true` if new coverage was reached 134 | program += "#[inline(never)] fn crashme(_input: &[u8; NUM_BYTES], _coverage: &mut [u64; NUM_COVERAGE]) -> bool {\n"; 135 | 136 | tab!(); 137 | program += "let mut new_coverage = false;\n"; 138 | 139 | coverage!(); 140 | 141 | // Number of bit allocation failures 142 | let mut alloc_failures = 0; 143 | 144 | loop { 145 | // Random chance to generate an if statement 146 | if rng.rand() % IF_CHANCE == 0 { 147 | if let Some((start, end)) = 148 | find_unused_bits!(rng.rand() % 8 + 1, 1000) { 149 | 150 | let start_byte = start / 8; 151 | let start_bit = start % 8; 152 | let end_bit = end % 8; 153 | 154 | // Generate a byte mask for these bits 155 | let mask = (!0u8 >> start_bit) << start_bit; 156 | let mask = (mask << (7 - end_bit)) >> (7 - end_bit); 157 | 158 | // Generate a target value for these bits 159 | let target = rng.rand() as u8 & mask; 160 | 161 | tab!(); 162 | program += &format!( 163 | "if _input[{}] & {:#010b} == {:#010b} {{\n", 164 | start_byte, mask, target); 165 | depth += 1; 166 | 167 | coverage!(); 168 | } else { 169 | alloc_failures += 1; 170 | if alloc_failures >= MAX_ALLOC_FAILURES { 171 | // Fail if there were too many failed attempts to find 172 | // free bits. 173 | break; 174 | } 175 | } 176 | } 177 | 178 | // Random chance to de-tab 179 | if depth > 1 && rng.rand() % END_BLOCK_CHANCE == 0 { 180 | depth -= 1; 181 | tab!(); 182 | program += "}\n"; 183 | } 184 | 185 | // Random chance to end the loop 186 | if num_blocks >= MIN_BLOCKS && rng.rand() % DONE_CHANCE == 0 { break; } 187 | } 188 | 189 | // Clean out brackets 190 | while depth > 1 { 191 | depth -= 1; 192 | tab!(); 193 | program += "}\n"; 194 | } 195 | 196 | // Return value 197 | tab!(); 198 | program += "new_coverage\n"; 199 | 200 | // End the program 201 | program += "}\n"; 202 | 203 | program += &format!("const NUM_COVERAGE: usize = {};\n", num_blocks); 204 | program += &format!("const NUM_BYTES: usize = {};\n", 205 | ((MAX_INPUT_SIZE_BITS + 7) & !7) / 8); 206 | 207 | // Write out the program 208 | std::fs::write("test.rs", 209 | std::fs::read_to_string("harness.rs")? + &program)?; 210 | 211 | // Build the program 212 | assert!(Command::new("rustc") 213 | .arg("-g") 214 | .arg("-O") 215 | .arg("test.rs") 216 | .status()?.success()); 217 | 218 | // Print out the program "complexity" 219 | print!("Program complexity:\n\ 220 | Blocks: {}\n", num_blocks); 221 | 222 | // Run the program 223 | assert!(Command::new("./test") 224 | .arg("test.rs") 225 | .status()?.success()); 226 | 227 | Ok(()) 228 | } 229 | 230 | fn main() -> io::Result<()> { 231 | proggen() 232 | } 233 | 234 | --------------------------------------------------------------------------------