fuzz/fuzz_targets/guard_manifest.rs
what this file is for · fuzz · 53 lines · the same file on GitHub
The file as it is in the tree, in the colours you chose. A line number is a link, and so is every box in this file’s diagram: it opens here with the function it names marked.
// SPDX-License-Identifier: GPL-3.0-or-later
//! The integrity manifest parser, coverage-guided.
//!
//! A text format rather than a packed one, which removes a whole class of
//! length-field bug and introduces a different one: it is split on a delimiter,
//! indexed by byte offset, and sliced for display. `Change::describe` takes
//! `&digest[..8]`, and slicing a `String` at a byte offset that is not a
//! character boundary panics.
//!
//! The manifest is normally the user's own record, but "normally" is not a
//! security property: `veilvoice guard check` will read whichever file is at
//! the path, and a record is exactly the kind of thing somebody hands you.
#![no_main]
use libfuzzer_sys::fuzz_target;
use veilvoice_guard::Manifest;
fuzz_target!(|data: &[u8]| {
// Not every byte string is UTF-8, and the loader would refuse those before
// the parser ever ran.
let Ok(text) = std::str::from_utf8(data) else {
return;
};
let Ok(manifest) = Manifest::parse(text) else {
return;
};
// Serialising and re-parsing must be a fixed point. If it is not, two
// manifests of the same files are not comparable, which is the only thing
// this type does.
let written = manifest.to_text();
let reparsed = Manifest::parse(&written).expect("a manifest we wrote must parse");
assert_eq!(reparsed, manifest, "the text format is not a fixed point");
assert_eq!(reparsed.to_text(), written, "serialisation is not stable");
// Every recorded path must survive the round trip intact, and the digest
// slicing in `describe` must not panic on any of them.
for path in manifest.paths() {
assert!(!path.is_empty(), "an empty path was recorded");
assert!(
!path.contains('\n') && !path.contains('\r'),
"a path with a line break would forge a record: {path:?}"
);
}
// `check` is deliberately not called: it reads the filesystem, which is
// both slow and nondeterministic, and would make a crash unreproducible.
// The display path is exercised directly instead.
for change in manifest.check::<&std::path::Path>(&[]).changes.iter().take(8) {
let _ = change.describe();
}
});