Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 108 additions & 15 deletions rust/cargo_fuzztest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl CargoFuzzTestOptions {
.fuzz_for
.unwrap_or(RunDuration::Indefinitely),
jobs: self.fuzztest_options.jobs,
continue_after_crash: self.fuzztest_options.continue_after_crash,
})
} else {
mode
Expand Down Expand Up @@ -248,11 +249,14 @@ impl FuzztestRunner {
}

ExecutionMode::Fuzz(fuzz_options) => {
let FuzzOptions { fuzz_for, jobs } = fuzz_options;
let FuzzOptions { fuzz_for, jobs, continue_after_crash } = fuzz_options;
cmd.env("FUZZTEST_FUZZ_FOR", fuzz_for.to_string());
if let Some(jobs) = jobs {
cmd.env("FUZZTEST_JOBS", jobs.to_string());
}
if continue_after_crash {
cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
}
}

ExecutionMode::ReplayCrash(replay_options) => {
Expand All @@ -273,6 +277,9 @@ impl FuzztestRunner {
TimeBudgetType::Total => "total",
};
cmd.env("FUZZTEST_TIME_BUDGET_TYPE", time_budget_str);
if replay_corpus_options.continue_after_crash {
cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
}
}

ExecutionMode::ListCrashIds(list_crash_ids_options) => {
Expand Down Expand Up @@ -447,7 +454,7 @@ mod tests {
.expect("valid replay options should parse successfully");

assert_eq!(parsed.fuzztest_options.replay_id.as_deref(), Some("crash_12345"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some(Path::new("/tmp/corpus_db")));

let mode = parsed.execution_mode().expect("valid execution mode");
assert_eq!(
Expand Down Expand Up @@ -475,7 +482,7 @@ mod tests {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_id: Some("crash_12345".to_string()),
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
..Default::default()
Expand All @@ -490,7 +497,7 @@ mod tests {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_id: Some("crash_12345".to_string()),
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
Expand Down Expand Up @@ -531,7 +538,7 @@ mod tests {
.expect("valid replay-findings options should parse successfully");

assert!(parsed.fuzztest_options.replay_findings);
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some(Path::new("/tmp/corpus_db")));

let mode = parsed.execution_mode().expect("valid execution mode");
assert_eq!(mode, ExecutionMode::ReplayAllCrashes);
Expand All @@ -542,7 +549,7 @@ mod tests {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_findings: true,
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
..Default::default()
Expand All @@ -557,7 +564,7 @@ mod tests {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_findings: true,
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
Expand Down Expand Up @@ -600,7 +607,7 @@ mod tests {

assert_eq!(parsed.fuzztest_options.replay_corpus_for, Some("10s".parse().unwrap()));
assert_eq!(parsed.fuzztest_options.time_budget_type, TimeBudgetType::PerTest);
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some(Path::new("/tmp/corpus_db")));

let mode = parsed.execution_mode().expect("valid execution mode");
assert_eq!(
Expand All @@ -609,6 +616,7 @@ mod tests {
replay_corpus_for: "10s".parse().expect("valid duration string"),
time_budget_type: TimeBudgetType::PerTest,
jobs: None,
continue_after_crash: false,
})
);
}
Expand All @@ -628,7 +636,7 @@ mod tests {

assert_eq!(parsed.fuzztest_options.replay_corpus_for, Some(RunDuration::Indefinitely));
assert_eq!(parsed.fuzztest_options.time_budget_type, TimeBudgetType::PerTest);
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some(Path::new("/tmp/corpus_db")));

let mode = parsed.execution_mode().expect("valid execution mode");
assert_eq!(
Expand All @@ -637,6 +645,7 @@ mod tests {
replay_corpus_for: RunDuration::Indefinitely,
time_budget_type: TimeBudgetType::PerTest,
jobs: None,
continue_after_crash: false,
})
);
}
Expand All @@ -662,6 +671,7 @@ mod tests {
replay_corpus_for: RunDuration::Indefinitely,
time_budget_type: TimeBudgetType::PerTest,
jobs: None,
continue_after_crash: false,
})
);
}
Expand Down Expand Up @@ -691,6 +701,7 @@ mod tests {
replay_corpus_for: "10s".parse().unwrap(),
time_budget_type: TimeBudgetType::Total,
jobs: None,
continue_after_crash: false,
})
);
}
Expand All @@ -700,7 +711,7 @@ mod tests {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_corpus_for: Some("10s".parse().unwrap()),
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
..Default::default()
Expand All @@ -716,7 +727,7 @@ mod tests {
fuzztest_options: FuzzTestOptions {
replay_corpus_for: Some("10s".parse().unwrap()),
time_budget_type: TimeBudgetType::Total,
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
Expand Down Expand Up @@ -765,7 +776,7 @@ mod tests {
parsed.fuzztest_options.list_crash_ids_file.as_deref(),
Some("/tmp/crashes.txt")
);
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some(Path::new("/tmp/corpus_db")));

let mode = parsed.execution_mode().expect("valid execution mode");
assert_eq!(
Expand Down Expand Up @@ -797,7 +808,7 @@ mod tests {
fuzztest_options: FuzzTestOptions {
list_crash_ids: true,
list_crash_ids_file: Some("/tmp/crashes.txt".to_string()),
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
..Default::default()
Expand All @@ -813,7 +824,7 @@ mod tests {
fuzztest_options: FuzzTestOptions {
list_crash_ids: true,
list_crash_ids_file: Some("/tmp/crashes.txt".to_string()),
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
Expand Down Expand Up @@ -850,7 +861,7 @@ mod tests {
fuzztest_options: FuzzTestOptions {
replay_corpus_for: Some(RunDuration::Indefinitely),
time_budget_type: TimeBudgetType::Total,
corpus_db: Some("/tmp/corpus_db".to_string()),
corpus_db: Some("/tmp/corpus_db".into()),
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
Expand Down Expand Up @@ -879,4 +890,86 @@ mod tests {
Some("/custom/centipede".to_string())
)));
}

#[gtest]
fn test_cli_option_parsing_continue_after_crash_flag() {
let parsed = CargoFuzzTestOptions::try_parse_from([
"cargo-fuzztest",
"--fuzz-for",
"5s",
"--continue-after-crash",
"--centipede-binary-path",
"/custom/centipede",
])
.unwrap();

assert!(parsed.fuzztest_options.continue_after_crash);
let mode = parsed.execution_mode().unwrap();
let expected_duration = "5s".parse().unwrap();
assert_eq!(
mode,
ExecutionMode::Fuzz(FuzzOptions {
fuzz_for: RunDuration::Fixed(expected_duration),
jobs: None,
continue_after_crash: true,
})
);
}

#[gtest]
fn test_build_run_command_fuzz_with_continue_after_crash() {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
fuzz_for: Some("5s".parse().unwrap()),
continue_after_crash: true,
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
..Default::default()
};
let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
let cmd =
runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command");

let envs: Vec<(String, Option<String>)> = cmd
.get_envs()
.map(|(k, v)| {
(k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string()))
})
.collect();

assert!(
envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string())))
);
assert!(envs.contains(&("FUZZTEST_FUZZ_FOR".to_string(), Some("5s".to_string()))));
}

#[gtest]
fn test_build_run_command_replay_corpus_with_continue_after_crash() {
let options = CargoFuzzTestOptions {
fuzztest_options: FuzzTestOptions {
replay_corpus_for: Some("10s".parse().expect("valid duration")),
corpus_db: Some("/tmp/corpus_db".into()),
continue_after_crash: true,
..Default::default()
},
centipede_binary_path: Some("/custom/centipede".to_string()),
..Default::default()
};
let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
let cmd =
runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command");

let envs: Vec<(String, Option<String>)> = cmd
.get_envs()
.map(|(k, v)| {
(k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string()))
})
.collect();

assert!(
envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string())))
);
assert!(envs.contains(&("FUZZTEST_REPLAY_CORPUS_FOR".to_string(), Some("10s".to_string()))));
}
}
29 changes: 29 additions & 0 deletions rust/cargo_fuzztest/tests/e2e_cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ fn test_cargo_fuzztest_e2e_replay_by_id() {
let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
cmd.arg(test_target)
.arg("--fuzz-for=5s")
.arg("--continue-after-crash")
.env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
.arg("--centipede-binary-path")
.arg(&centipede_bin)
Expand Down Expand Up @@ -248,6 +249,7 @@ fn test_cargo_fuzztest_e2e_replay_all_crashes() {
let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
cmd.arg(test_target)
.arg("--fuzz-for=5s")
.arg("--continue-after-crash")
.env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
.arg("--centipede-binary-path")
.arg(&centipede_bin)
Expand Down Expand Up @@ -412,6 +414,7 @@ fn test_cargo_fuzztest_e2e_list_crash_ids() {
let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
cmd.arg(test_target)
.arg("--fuzz-for=5s")
.arg("--continue-after-crash")
.env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
.arg("--centipede-binary-path")
.arg(&centipede_bin)
Expand Down Expand Up @@ -448,3 +451,29 @@ fn test_cargo_fuzztest_e2e_list_crash_ids() {
// good check here?
expect_true!(!crash_ids.is_empty());
}

#[gtest]
fn test_cargo_fuzztest_e2e_continue_after_crash() {
let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate");
let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target";

let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory");
let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH")
.expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test");

let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
cmd.arg(test_target)
.arg("--fuzz-for=3s")
.arg("--continue-after-crash")
.env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
.arg("--centipede-binary-path")
.arg(&centipede_bin)
.env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true");

let output = cmd.output().expect("Failed to run cargo-fuzztest with continue-after-crash");
let stderr_str = String::from_utf8_lossy(&output.stderr);

expect_true!(output.status.success());
expect_true!(stderr_str.contains("Property function ran but crashed."));
expect_true!(stderr_str.contains("Crashing bug found!"));
}
Loading
Loading