Coverage Report

Created: 2025-06-04 06:23

/src/zip/fuzz/fuzz_targets/structured_fuzz_reader.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2023 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
//limitations under the License.
14
//
15
//###################
16
17
#![no_main]
18
19
use arbitrary::Arbitrary;
20
use libfuzzer_sys::fuzz_target;
21
use std::hint::black_box;
22
use std::io::Cursor;
23
24
932k
#[derive(Arbitrary, Debug)]
<structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary::{closure#0}
Line
Count
Source
24
14.3k
#[derive(Arbitrary, Debug)]
<structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary::{closure#1}
Line
Count
Source
24
917k
#[derive(Arbitrary, Debug)]
<structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary::{closure#2}
Line
Count
Source
24
14.3k
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#2}
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::try_size_hint::{closure#0}
25
enum ReadOperations<'a> {
26
    ReadByName(String),
27
    ReadByNameDecrypt { name: String, password: &'a [u8] },
28
    ReadByIterableNames,
29
    ReadByIndex(usize),
30
    ReadByIndexDecrypt { index: usize, password: &'a [u8] },
31
    ReadByIterableIndicies,
32
    ReadComment,
33
}
34
35
46.6k
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
<structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}
Line
Count
Source
35
23.3k
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#2}
<structured_fuzz_reader::Driver as arbitrary::Arbitrary>::try_size_hint::{closure#0}
Line
Count
Source
35
23.3k
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary::{closure#1}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary::{closure#2}
36
#[repr(C)]
37
struct Driver<'a> {
38
    // NOTE: we are defining repr(C) so that the struct field ordering is consistent.
39
    // This consistent ordering means that we still generate a set of zip files
40
    // for the fuzz corpus, which will become the .zip_file, and any leftover data
41
    // will be used for structured fuzzing.
42
    zip_file: &'a [u8],
43
    read_operations: Vec<ReadOperations<'a>>,
44
}
45
46
376k
fn read_file_attributes(file: &mut zip::read::ZipFile) -> Result<(), zip::result::ZipError> {
47
    use std::io::Read;
48
376k
    let _unused = black_box(file.name());
49
376k
    let _unused = black_box(file.mangled_name());
50
376k
    let _unused = black_box(file.enclosed_name());
51
376k
    let _unused = black_box(file.compression());
52
376k
    let _unused = black_box(file.compressed_size());
53
376k
    let _unused = black_box(file.size());
54
376k
    let _unused = black_box(file.last_modified());
55
376k
    let _unused = black_box(file.is_dir());
56
376k
    let _unused = black_box(file.is_file());
57
376k
    let _unused = black_box(file.unix_mode());
58
376k
    let _unused = black_box(file.crc32());
59
376k
    let _unused = black_box(file.data_start());
60
376k
    let _unused = black_box(file.header_start());
61
376k
    let _unused = black_box(file.central_header_start());
62
376k
    let mut s: String = String::new();
63
376k
    let _unused = black_box(file.read_to_string(&mut s));
64
376k
    return Ok(());
65
376k
}
66
67
23.3k
fn fuzzed_extract(driver: Driver) -> Result<(), zip::result::ZipError> {
68
23.3k
    match zip::ZipArchive::new(Cursor::new(driver.zip_file)) {
69
21.2k
        Ok(mut archive) => {
70
322k
            for operation in driver.read_operations.iter() {
71
322k
                match operation {
72
21.7k
                    ReadOperations::ReadByName(name) => {
73
21.7k
                        let mut file = archive.by_name(name)?;
74
21.1k
                        let _unused = black_box(read_file_attributes(&mut file));
75
                    }
76
                    // TODO: This could probably use a custom mutator, or a specialised seed corpus.
77
                    // The probability that the fuzzer guesses the correct password is exceedingly low.
78
1.38k
                    ReadOperations::ReadByNameDecrypt { name, password } => {
79
1.38k
                        match archive.by_name_decrypt(name, password)? {
80
854
                            Ok(mut file) => {
81
854
                                let _unused = black_box(&read_file_attributes(&mut file));
82
854
                            }
83
419
                            Err(e) => {
84
419
                                let _unused = black_box(format!("{e:?}"));
85
419
                                let _unused = black_box(format!("{e:#?}"));
86
419
                            }
87
                        }
88
                    }
89
                    ReadOperations::ReadByIterableNames => {
90
190k
                        let names: Vec<String> =
91
235k
                            archive.file_names().map(|x| x.to_string()).collect();
92
235k
                        for name in names.iter() {
93
235k
                            let mut file = archive.by_name(&name)?;
94
235k
                            let _unused = black_box(read_file_attributes(&mut file));
95
                        }
96
                    }
97
238
                    ReadOperations::ReadByIndex(index) => {
98
238
                        let mut file = archive.by_index(*index)?;
99
132
                        let _unused = black_box(read_file_attributes(&mut file));
100
                    }
101
222
                    ReadOperations::ReadByIndexDecrypt { index, password } => {
102
222
                        match archive.by_index_decrypt(*index, password)? {
103
98
                            Ok(mut file) => {
104
98
                                let _unused = black_box(read_file_attributes(&mut file));
105
98
                            }
106
4
                            Err(e) => {
107
4
                                let _unused = black_box(format!("{e:?}"));
108
4
                                let _unused = black_box(format!("{e:#?}"));
109
4
                            }
110
                        }
111
                    }
112
                    ReadOperations::ReadByIterableIndicies => {
113
118k
                        for i in 0..archive.len() {
114
118k
                            let mut file = archive.by_index(i)?;
115
118k
                            let _unused = black_box(read_file_attributes(&mut file));
116
                        }
117
                    }
118
1.91k
                    ReadOperations::ReadComment => {
119
1.91k
                        let _unused = black_box(archive.comment());
120
1.91k
                    }
121
                }
122
            }
123
        }
124
2.07k
        Err(e) => return Err(e),
125
    }
126
20.2k
    return Ok(());
127
23.3k
}
128
129
fuzz_target!(|driver: Driver| {
130
    if let Err(e) = fuzzed_extract(driver) {
131
        let _unused = black_box(format!("{e:?}"));
132
        let _unused = black_box(format!("{e:#?}"));
133
    }
134
});