test(fuzz): Cover read_zipfile_from_stream, finish_into_readable and merge_archive in fuzzing

This commit is contained in:
Chris Hennick 2024-05-06 14:10:40 -07:00
parent 0518194ae2
commit 0e1b1688be
No known key found for this signature in database
GPG key ID: DA47AABA4961C509
2 changed files with 42 additions and 15 deletions

View file

@ -1,6 +1,7 @@
#![no_main] #![no_main]
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use std::io::Read; use std::io::{Read, Seek, SeekFrom};
use zip::read::read_zipfile_from_stream;
const MAX_BYTES_TO_READ: u64 = 1 << 24; const MAX_BYTES_TO_READ: u64 = 1 << 24;
@ -12,7 +13,11 @@ fn decompress_all(data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let mut file = zip.by_index(i)?.take(MAX_BYTES_TO_READ); let mut file = zip.by_index(i)?.take(MAX_BYTES_TO_READ);
std::io::copy(&mut file, &mut std::io::sink())?; std::io::copy(&mut file, &mut std::io::sink())?;
} }
let mut reader = zip.into_inner();
reader.seek(SeekFrom::Start(0))?;
while let Ok(Some(mut file)) = read_zipfile_from_stream(&mut reader) {
std::io::copy(&mut file, &mut std::io::sink())?;
}
Ok(()) Ok(())
} }

View file

@ -9,7 +9,7 @@ use std::path::PathBuf;
#[derive(Arbitrary, Clone, Debug)] #[derive(Arbitrary, Clone, Debug)]
pub enum BasicFileOperation<'k> { pub enum BasicFileOperation<'k> {
WriteNormalFile { WriteNormalFile {
contents: Vec<Vec<u8>>, contents: Box<[Box<[u8]>]>,
options: zip::write::FullFileOptions<'k>, options: zip::write::FullFileOptions<'k>,
}, },
WriteDirectory(zip::write::FullFileOptions<'k>), WriteDirectory(zip::write::FullFileOptions<'k>),
@ -19,13 +19,21 @@ pub enum BasicFileOperation<'k> {
}, },
ShallowCopy(Box<FileOperation<'k>>), ShallowCopy(Box<FileOperation<'k>>),
DeepCopy(Box<FileOperation<'k>>), DeepCopy(Box<FileOperation<'k>>),
MergeWithOtherFile(Box<[FileOperation<'k>]>)
}
#[derive(Arbitrary, Clone, Debug)]
pub enum ReopenOption {
DoNotReopen,
ViaFinish,
ViaFinishIntoReadable
} }
#[derive(Arbitrary, Clone, Debug)] #[derive(Arbitrary, Clone, Debug)]
pub struct FileOperation<'k> { pub struct FileOperation<'k> {
basic: BasicFileOperation<'k>, basic: BasicFileOperation<'k>,
path: PathBuf, path: PathBuf,
reopen: bool, reopen: ReopenOption,
// 'abort' flag is separate, to prevent trying to copy an aborted file // 'abort' flag is separate, to prevent trying to copy an aborted file
} }
@ -53,14 +61,14 @@ where
options, options,
.. ..
} => { } => {
let uncompressed_size = contents.iter().map(Vec::len).sum::<usize>(); let uncompressed_size = contents.iter().map(|chunk| chunk.len()).sum::<usize>();
let mut options = (*options).to_owned(); let mut options = (*options).to_owned();
if uncompressed_size >= u32::MAX as usize { if uncompressed_size >= u32::MAX as usize {
options = options.large_file(true); options = options.large_file(true);
} }
writer.start_file_from_path(path, options)?; writer.start_file_from_path(path, options)?;
for chunk in contents { for chunk in contents.iter() {
writer.write_all(chunk.as_slice())?; writer.write_all(&chunk)?;
} }
} }
BasicFileOperation::WriteDirectory(options) => { BasicFileOperation::WriteDirectory(options) => {
@ -77,19 +85,33 @@ where
do_operation(writer, &base, false, flush_on_finish_file)?; do_operation(writer, &base, false, flush_on_finish_file)?;
writer.deep_copy_file_from_path(&base.path, &path)?; writer.deep_copy_file_from_path(&base.path, &path)?;
} }
BasicFileOperation::MergeWithOtherFile(other_ops) => {
let mut other_writer = zip::ZipWriter::new(Cursor::new(Vec::new()));
other_ops.iter().for_each(|operation| {
let _ = do_operation(
&mut other_writer,
&operation,
abort,
false,
);
});
writer.merge_archive(other_writer.finish_into_readable()?)?;
}
} }
if abort { if abort {
writer.abort_file().unwrap(); writer.abort_file().unwrap();
} }
if operation.reopen { let old_comment = writer.get_raw_comment().to_owned();
let old_comment = writer.get_raw_comment().to_owned(); match operation.reopen {
replace_with_or_abort(writer, |old_writer: zip::ZipWriter<T>| { ReopenOption::DoNotReopen => {},
let new_writer = ReopenOption::ViaFinish => replace_with_or_abort(writer, |old_writer: zip::ZipWriter<T>| {
zip::ZipWriter::new_append(old_writer.finish().unwrap()).unwrap(); zip::ZipWriter::new_append(old_writer.finish().unwrap()).unwrap()
assert_eq!(&old_comment, new_writer.get_raw_comment()); }),
new_writer ReopenOption::ViaFinishIntoReadable => replace_with_or_abort(writer, |old_writer: zip::ZipWriter<T>| {
}); zip::ZipWriter::new_append(old_writer.finish_into_readable().unwrap().into_inner()).unwrap()
}),
} }
assert_eq!(&old_comment, writer.get_raw_comment());
Ok(()) Ok(())
} }