| 13 | |
| 14 | impl FuzzerStage for TrimStage { |
| 15 | fn run(fuzzer: &mut Fuzzer, stats: &mut LocalStats) -> anyhow::Result<StageExit> { |
| 16 | let Some(input_id) = fuzzer.input_id |
| 17 | else { |
| 18 | return Ok(StageExit::Skip); |
| 19 | }; |
| 20 | fuzzer.copy_current_input(); |
| 21 | let initial_len = fuzzer.corpus[input_id].data.total_bytes(); |
| 22 | |
| 23 | // The bits that we want to ensure that the input still reaches after trimming. |
| 24 | let bits_to_keep = fuzzer.corpus[input_id].metadata.new_bits.clone(); |
| 25 | tracing::debug!("[{input_id}] attempting to trim keeping {bits_to_keep:?}"); |
| 26 | |
| 27 | let mut trim_log = TrimLogger::new(input_id, &fuzzer.workdir); |
| 28 | |
| 29 | // Keeps track of the location to trim next. |
| 30 | let Some(mut cursor) = MultiStreamTrimCursor::new(&fuzzer.state.input) |
| 31 | else { |
| 32 | return Ok(StageExit::Skip); |
| 33 | }; |
| 34 | |
| 35 | // The order we trim things in sometimes matter (e.g., one stream might be unable to be |
| 36 | // trimmed before another stream). We randomize the order here to ensure that the chosen |
| 37 | // order is less dependent on the ordering of the stream HashMap to avoid cases where a bad |
| 38 | // order is always chosen. |
| 39 | cursor.randomize_trim_order(&mut fuzzer.rng); |
| 40 | |
| 41 | // Keep track best input we have found so far that still hits the target bits. |
| 42 | let mut attempts = 0; |
| 43 | let mut saved_input = fuzzer.state.input.clone(); |
| 44 | while let Some((stream_key, offset, len)) = cursor.get(&fuzzer.state.input) { |
| 45 | // Remove part of the input |
| 46 | let stream_bytes = &mut fuzzer.state.input.streams.get_mut(&stream_key).unwrap().bytes; |
| 47 | stream_bytes.drain(offset..offset + len); |
| 48 | |
| 49 | // Run the modifed input in the emulator. |
| 50 | Snapshot::restore_initial(fuzzer); |
| 51 | fuzzer.reset_input_cursor().unwrap(); |
| 52 | fuzzer.write_input_to_target().unwrap(); |
| 53 | let Some(exit) = fuzzer.execute() |
| 54 | else { |
| 55 | return Ok(StageExit::Interrupted); |
| 56 | }; |
| 57 | attempts += 1; |
| 58 | fuzzer.auto_trim_input().unwrap(); |
| 59 | |
| 60 | // Check if the input finds a new path or is a crash, and update the monitor. |
| 61 | fuzzer.check_exit_state(exit)?; |
| 62 | fuzzer.update_stats(stats); |
| 63 | |
| 64 | // Check whether we still hit all the target bits, and we still exit in the same way. |
| 65 | let current_bits = fuzzer.coverage.get_bits(&mut fuzzer.vm); |
| 66 | let diverges = CrashKind::from(exit).is_crash() |
| 67 | || !bits_to_keep.iter().all(|bit| is_bit_set(current_bits, *bit)); |
| 68 | if diverges { |
| 69 | // Input no longer hits the target bits skip past this chunk and restore the input. |
| 70 | fuzzer.state.input.clone_from(&saved_input); |
| 71 | cursor.skip_current(); |
| 72 | trim_log.log(fuzzer, stream_key, offset, len, &bits_to_keep, false); |