Coverage Report

Created: 2025-10-10 07:05

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>::arbitrary::{closure#0}
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}
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>::try_size_hint::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
Unexecuted instantiation: <structured_fuzz_reader::Driver as arbitrary::Arbitrary>::arbitrary::{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
48.9k
fn read_file_attributes(file: &mut zip::read::ZipFile) -> Result<(), zip::result::ZipError> {
47
    use std::io::Read;
48
48.9k
    let _unused = black_box(file.name());
49
48.9k
    let _unused = black_box(file.mangled_name());
50
48.9k
    let _unused = black_box(file.enclosed_name());
51
48.9k
    let _unused = black_box(file.compression());
52
48.9k
    let _unused = black_box(file.compressed_size());
53
48.9k
    let _unused = black_box(file.size());
54
48.9k
    let _unused = black_box(file.last_modified());
55
48.9k
    let _unused = black_box(file.is_dir());
56
48.9k
    let _unused = black_box(file.is_file());
57
48.9k
    let _unused = black_box(file.unix_mode());
58
48.9k
    let _unused = black_box(file.crc32());
59
48.9k
    let _unused = black_box(file.data_start());
60
48.9k
    let _unused = black_box(file.header_start());
61
48.9k
    let _unused = black_box(file.central_header_start());
62
48.9k
    let mut s: String = String::new();
63
48.9k
    let _unused = black_box(file.read_to_string(&mut s));
64
48.9k
    return Ok(());
65
48.9k
}
66
67
483
fn fuzzed_extract(driver: Driver) -> Result<(), zip::result::ZipError> {
68
483
    match zip::ZipArchive::new(Cursor::new(driver.zip_file)) {
69
327
        Ok(mut archive) => {
70
45.4k
            for operation in driver.read_operations.iter() {
71
45.4k
                match operation {
72
146
                    ReadOperations::ReadByName(name) => {
73
146
                        let mut file = archive.by_name(name)?;
74
62
                        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
28
                    ReadOperations::ReadByNameDecrypt { name, password } => {
79
28
                        match archive.by_name_decrypt(name, password)? {
80
4
                            Ok(mut file) => {
81
4
                                let _unused = black_box(&read_file_attributes(&mut file));
82
4
                            }
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
38.2k
                        let names: Vec<String> =
91
43.0k
                            archive.file_names().map(|x| x.to_string()).collect();
92
41.9k
                        for name in names.iter() {
93
41.9k
                            let mut file = archive.by_name(&name)?;
94
41.9k
                            let _unused = black_box(read_file_attributes(&mut file));
95
                        }
96
                    }
97
23
                    ReadOperations::ReadByIndex(index) => {
98
23
                        let mut file = archive.by_index(*index)?;
99
2
                        let _unused = black_box(read_file_attributes(&mut file));
100
                    }
101
27
                    ReadOperations::ReadByIndexDecrypt { index, password } => {
102
27
                        match archive.by_index_decrypt(*index, password)? {
103
10
                            Ok(mut file) => {
104
10
                                let _unused = black_box(read_file_attributes(&mut file));
105
10
                            }
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
6.93k
                        for i in 0..archive.len() {
114
6.93k
                            let mut file = archive.by_index(i)?;
115
6.93k
                            let _unused = black_box(read_file_attributes(&mut file));
116
                        }
117
                    }
118
214
                    ReadOperations::ReadComment => {
119
214
                        let _unused = black_box(archive.comment());
120
214
                    }
121
                }
122
            }
123
        }
124
156
        Err(e) => return Err(e),
125
    }
126
163
    return Ok(());
127
483
}
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
});