Coverage Report

Created: 2025-09-27 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zip/fuzz/fuzz_targets/structured_fuzz_reader.rs
Line
Count
Source
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
0
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::try_size_hint::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::ReadOperations as arbitrary::Arbitrary>::arbitrary::{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
0
#[derive(Arbitrary, Debug)]
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::try_size_hint::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
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
60.6k
fn read_file_attributes(file: &mut zip::read::ZipFile) -> Result<(), zip::result::ZipError> {
47
    use std::io::Read;
48
60.6k
    let _unused = black_box(file.name());
49
60.6k
    let _unused = black_box(file.mangled_name());
50
60.6k
    let _unused = black_box(file.enclosed_name());
51
60.6k
    let _unused = black_box(file.compression());
52
60.6k
    let _unused = black_box(file.compressed_size());
53
60.6k
    let _unused = black_box(file.size());
54
60.6k
    let _unused = black_box(file.last_modified());
55
60.6k
    let _unused = black_box(file.is_dir());
56
60.6k
    let _unused = black_box(file.is_file());
57
60.6k
    let _unused = black_box(file.unix_mode());
58
60.6k
    let _unused = black_box(file.crc32());
59
60.6k
    let _unused = black_box(file.data_start());
60
60.6k
    let _unused = black_box(file.header_start());
61
60.6k
    let _unused = black_box(file.central_header_start());
62
60.6k
    let mut s: String = String::new();
63
60.6k
    let _unused = black_box(file.read_to_string(&mut s));
64
60.6k
    return Ok(());
65
60.6k
}
66
67
540
fn fuzzed_extract(driver: Driver) -> Result<(), zip::result::ZipError> {
68
540
    match zip::ZipArchive::new(Cursor::new(driver.zip_file)) {
69
357
        Ok(mut archive) => {
70
46.0k
            for operation in driver.read_operations.iter() {
71
46.0k
                match operation {
72
160
                    ReadOperations::ReadByName(name) => {
73
160
                        let mut file = archive.by_name(name)?;
74
70
                        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
32
                    ReadOperations::ReadByNameDecrypt { name, password } => {
79
32
                        match archive.by_name_decrypt(name, password)? {
80
9
                            Ok(mut file) => {
81
9
                                let _unused = black_box(&read_file_attributes(&mut file));
82
9
                            }
83
0
                            Err(e) => {
84
0
                                let _unused = black_box(format!("{e:?}"));
85
0
                                let _unused = black_box(format!("{e:#?}"));
86
0
                            }
87
                        }
88
                    }
89
                    ReadOperations::ReadByIterableNames => {
90
36.1k
                        let names: Vec<String> =
91
50.4k
                            archive.file_names().map(|x| x.to_string()).collect();
92
49.4k
                        for name in names.iter() {
93
49.4k
                            let mut file = archive.by_name(&name)?;
94
49.4k
                            let _unused = black_box(read_file_attributes(&mut file));
95
                        }
96
                    }
97
17
                    ReadOperations::ReadByIndex(index) => {
98
17
                        let mut file = archive.by_index(*index)?;
99
2
                        let _unused = black_box(read_file_attributes(&mut file));
100
                    }
101
33
                    ReadOperations::ReadByIndexDecrypt { index, password } => {
102
33
                        match archive.by_index_decrypt(*index, password)? {
103
12
                            Ok(mut file) => {
104
12
                                let _unused = black_box(read_file_attributes(&mut file));
105
12
                            }
106
0
                            Err(e) => {
107
0
                                let _unused = black_box(format!("{e:?}"));
108
0
                                let _unused = black_box(format!("{e:#?}"));
109
0
                            }
110
                        }
111
                    }
112
                    ReadOperations::ReadByIterableIndicies => {
113
11.0k
                        for i in 0..archive.len() {
114
11.0k
                            let mut file = archive.by_index(i)?;
115
11.0k
                            let _unused = black_box(read_file_attributes(&mut file));
116
                        }
117
                    }
118
278
                    ReadOperations::ReadComment => {
119
278
                        let _unused = black_box(archive.comment());
120
278
                    }
121
                }
122
            }
123
        }
124
183
        Err(e) => return Err(e),
125
    }
126
184
    return Ok(());
127
540
}
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
});