Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reduce command causes tool to crash #137

Open
adpaco-aws opened this issue Apr 4, 2023 · 6 comments
Open

reduce command causes tool to crash #137

adpaco-aws opened this issue Apr 4, 2023 · 6 comments
Labels
[F] Crash Bolero crashed

Comments

@adpaco-aws
Copy link
Collaborator

Wrote a simple bolero test like this one:

#[test]
fn function() {
    check!()
        .with_type::<i32>()
        .for_each(|x| {
            assert!(*x < 100_i32);
        })
}

When I run cargo bolero reduce function it crashes:

running 1 test
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1694551407
INFO: Loaded 1 modules   (294218 inline 8-bit counters): 294218 [0x107c22948, 0x107c6a692), 
INFO: Loaded 1 PC tables (294218 PCs): 294218 [0x107c6a698,0x1080e7b38), 
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 1048576 bytes
MERGE-INNER: using the control file '/var/folders/qb/tckm4wwd0gzbl9ypc53ncdb8xg9k_h/T/.tmpoaEcQo'
MERGE: failed to parse the control file (unexpected error)
error: process exited with status 1

Looks like it's failing to parse a file.

@adpaco-aws adpaco-aws added the [F] Crash Bolero crashed label Apr 4, 2023
@Ekleog
Copy link
Contributor

Ekleog commented Feb 2, 2024

@camshaft Is reduce working correctly for you? I'm hitting the same issue locally. I seem to remember ~2 years ago things were working fine so I'm a bit at a loss as to what could have changed, but then trying to bisect led me to not finding any good commit: it goes straight from "does not build on my system" to "reduce fails with this error"

@camshaft
Copy link
Owner

camshaft commented Feb 3, 2024

I think it may have been broken awhile back after upgrading libFuzzer versions... When I first integrated it I ran into problems so had to apply quite a few workarounds to get it working. But we should revisit to see what's wrong and have some tests in place to make sure it doesn't regress again.

@Ekleog
Copy link
Contributor

Ekleog commented Feb 4, 2024

Hmm I see thank you! Out of curiosity, I just started this thread at libFuzzer upstream, because I feel like we could upstream part of the stuff currently in bolero and that could help. Do you think this could be related to the issue here?

And if not, do you remember what kind of workarounds you had to apply, or a rough timeframe where I could look for commits in the git history?

@camshaft
Copy link
Owner

camshaft commented Feb 11, 2024

I just went through the commit history and didn't see any commit messages standing out, so it was either before the initial commit or just included as part of another.

But all of the reduce logic is here:

pub(crate) fn reduce(selection: &Selection, reduce: &reduce::Args) -> Result<()> {
let test_target = selection.test_target(FLAGS, "libfuzzer")?;
let corpus_dir = test_target.default_corpus_dir();
let tmp_corpus = test_target.temp_dir()?;
fs::create_dir_all(&corpus_dir)?;
fs::create_dir_all(&tmp_corpus)?;
let mut control_file = tempfile::NamedTempFile::new()?;
let inputs = write_control_file(&mut control_file, &corpus_dir)?;
let mut cmd = test_target.command();
let mut args = vec![
format!("-merge_control_file={}", control_file.as_ref().display()),
"-merge_inner=1".to_string(),
];
args.extend(reduce.engine_args.iter().cloned());
cmd.env("BOLERO_LIBFUZZER_ARGS", args.join(" "));
exec(cmd)?;
let results = parse_control_file(&mut BufReader::new(control_file).lines(), &inputs)?;
let mut covered_features = BitSet::<u64>::default();
for result in results {
let prev_len = covered_features.len();
covered_features.union_with(&result.features);
if prev_len != covered_features.len() {
let new_file = tmp_corpus.path().join(result.path.file_name().unwrap());
fs::rename(result.path, new_file)?;
}
}
let backup = corpus_dir.parent().unwrap().join("_corpus_bkp");
fs::rename(&corpus_dir, &backup)?;
fs::rename(&tmp_corpus, &corpus_dir)?;
fs::remove_dir_all(&backup)?;
Ok(())
}
fn write_control_file<W: Write>(file: &mut W, corpus_dir: &Path) -> Result<Vec<PathBuf>> {
let mut inputs = vec![];
for entry in fs::read_dir(corpus_dir)? {
inputs.push(entry?.path());
}
inputs.sort();
// The control file example:
//
// 3 # The number of inputs
// 1 # The number of inputs in the first corpus, <= the previous number
// file0
// file1
// file2 # One file name per line.
// STARTED 0 123 # FileID, file size
// FT 0 1 4 6 8 # FileID COV1 COV2 ...
// COV 0 7 8 9 # FileID COV1 COV1
// STARTED 1 456 # If FT is missing, the input crashed while processing.
// STARTED 2 567
// FT 2 8 9
// COV 2 11 12
writeln!(file, "{}", inputs.len())?;
writeln!(file, "{}", inputs.len())?;
for input in inputs.iter() {
writeln!(file, "{}", input.display())?;
}
Ok(inputs)
}
#[derive(Debug)]
struct ControlResult<'a> {
size: usize,
features: BitSet<u64>,
path: &'a Path,
}
fn parse_control_file<'a, I: Iterator<Item = IOResult<String>>>(
lines: &mut I,
inputs: &'a [PathBuf],
) -> Result<Vec<ControlResult<'a>>> {
let mut results: Vec<_> = (0..inputs.len()).map(|_| None).collect();
let mut state = None;
for line in lines {
let line = line?;
let mut controls = line.split(' ');
match controls.next() {
Some("STARTED") => {
let id: usize = controls.next().unwrap().parse()?;
let size = controls.next().unwrap().parse()?;
results[id] = Some(ControlResult {
size,
features: BitSet::default(),
path: &inputs[id],
});
state = Some(results[id].as_mut().unwrap());
}
Some("COV") => {
// We only use features
continue;
}
Some("FT") => {
let res = state.as_mut().unwrap();
for id in controls {
let id = id.parse()?;
res.features.insert(id);
}
}
None => {
continue;
}
_ => {
return Err(anyhow!("invalid control"));
}
}
}
let mut results: Vec<_> = results.drain(..).flatten().collect();
results.sort_by(|a, b| {
let size_cmp = a.size.cmp(&b.size);
if size_cmp == Ordering::Equal {
a.features.len().cmp(&b.features.len())
} else {
size_cmp
}
});
Ok(results)
}
. for the short term, we can probably just fix the control file writer/parser and it should fix this issue. Longer term, it'd be better to delegate entirely on to libFuzzer and not do any control file parsing in cargo-bolero.

@camshaft
Copy link
Owner

On the libFuzzer side, the control file parser logic (the part that's printing the error) is here
https://github.com/llvm/llvm-project/blob/b45de48be24695b613f48ed21bb35f844454193b/compiler-rt/lib/fuzzer/FuzzerMerge.cpp#L38

@Ekleog
Copy link
Contributor

Ekleog commented Aug 14, 2024

Thank you for the information! It's been a while, but just to give an update: my dayjob has strayed farther away from fuzzing than it originally was, and so I haven't had much love to give bolero recently. If anyone wants to pick this up, please feel free!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[F] Crash Bolero crashed
Projects
None yet
Development

No branches or pull requests

3 participants