Coverage Report

Created: 2025-11-24 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zip/fuzz/fuzz_targets/roundtrip.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
#![no_main]
17
18
use arbitrary::{self, Arbitrary, Unstructured};
19
use libfuzzer_sys::fuzz_target;
20
use std::hint::black_box;
21
use zip::{self, result::ZipError};
22
23
76.2k
fn arbitrary_unix_permissions(u: &mut Unstructured) -> arbitrary::Result<u32> {
24
76.2k
    return Ok(u.int_in_range(0o1..=0o777)?);
25
76.2k
}
26
27
// Generate a valid arbitrary path.
28
117k
fn arbitrary_path(u: &mut Unstructured) -> arbitrary::Result<String> {
29
117k
    let path_len: usize = u.int_in_range(1..=512).unwrap_or(1);
30
117k
    Ok((0..=path_len)
31
36.1M
        .map(|_| {
32
36.1M
            let valid_chars: Vec<char> = ('a'..='z')
33
36.1M
                .chain('A'..='Z')
34
36.1M
                .chain("/-_.@".chars())
35
36.1M
                .collect();
36
36.1M
            return valid_chars[u.choose_index(valid_chars.len()).unwrap_or(0)];
37
36.1M
        })
38
117k
        .collect())
39
117k
}
40
41
#[derive(Debug, Clone, PartialEq)]
42
struct Compression {
43
    method: zip::CompressionMethod,
44
    level: Option<i32>,
45
}
46
47
impl<'a> Arbitrary<'a> for Compression {
48
76.2k
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
49
        use zip::CompressionMethod::*;
50
76.2k
        Ok(match u8::arbitrary(u) {
51
652
            Ok(1) => Compression {
52
652
                method: Stored,
53
652
                level: u.int_in_range(0..=9).ok(),
54
652
            },
55
22.8k
            Ok(2) => Compression {
56
22.8k
                method: Deflated,
57
22.8k
                level: u.int_in_range(0..=9).ok(),
58
22.8k
            },
59
4.85k
            Ok(3) => Compression {
60
4.85k
                method: Bzip2,
61
4.85k
                level: u.int_in_range(1..=9).ok(),
62
4.85k
            },
63
47.8k
            _ => Compression {
64
47.8k
                method: Zstd,
65
47.8k
                level: u.int_in_range(-7..=22).ok(),
66
47.8k
            },
67
        })
68
76.2k
    }
69
}
70
71
76.3k
fn arbitrary_file(u: &mut Unstructured) -> arbitrary::Result<Vec<u8>> {
72
76.3k
    let file = Vec::<u8>::arbitrary(u)?;
73
76.3k
    if file.len() == 0 {
74
102
        return Err(arbitrary::Error::IncorrectFormat);
75
76.2k
    }
76
76.2k
    return Ok(file);
77
76.3k
}
78
79
0
#[derive(Arbitrary, Clone, Debug, PartialEq)]
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::arbitrary::{closure#0}
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::try_size_hint::{closure#0}
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
80
struct File {
81
    #[arbitrary(with = arbitrary_path)]
82
    zip_path: String,
83
    #[arbitrary(with = arbitrary_file)]
84
    contents: Vec<u8>,
85
    compression: Compression,
86
    alignment: Option<u16>,
87
    large_file: bool,
88
    #[arbitrary(with = arbitrary_unix_permissions)]
89
    unix_permissions: u32,
90
}
91
92
102
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary::{closure#0}
Line
Count
Source
92
102
#[derive(Arbitrary, Clone, Debug, PartialEq)]
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::try_size_hint::{closure#0}
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
93
enum ZipEntry<'a> {
94
    File(File),
95
    RawComment(&'a [u8]),
96
    Comment(String),
97
    Symlink {
98
        #[arbitrary(with = arbitrary_path)]
99
        src: String,
100
        #[arbitrary(with = arbitrary_path)]
101
        dst: String,
102
    },
103
    Directory {
104
        #[arbitrary(with = arbitrary_path)]
105
        path: String,
106
    },
107
}
108
109
impl<'a> ZipEntry<'a> {
110
204k
    fn path(&self) -> Option<String> {
111
204k
        match self {
112
75.3k
            ZipEntry::File(file) => Some(file.zip_path.clone()),
113
4.10k
            ZipEntry::Symlink { dst, .. } => Some(dst.clone()),
114
32.8k
            ZipEntry::Directory { path } => Some(path.clone()),
115
92.2k
            _ => None,
116
        }
117
204k
    }
118
}
119
120
#[derive(Debug, PartialEq)]
121
struct Operations<'a> {
122
    zip_entries: Vec<ZipEntry<'a>>,
123
}
124
125
impl<'a> Arbitrary<'a> for Operations<'a> {
126
11.8k
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
127
11.8k
        let zip_entries = Vec::<ZipEntry>::arbitrary(u)?;
128
11.7k
        let mut paths = std::collections::HashSet::new();
129
11.7k
        let mut result = Vec::new();
130
204k
        for zip_entry in zip_entries.iter() {
131
204k
            if let Some(path) = zip_entry.path() {
132
112k
                if paths.contains(&path) {
133
28.7k
                    continue;
134
83.5k
                } else {
135
83.5k
                    result.push(zip_entry.clone());
136
83.5k
                    paths.insert(path.clone());
137
83.5k
                }
138
92.2k
            } else {
139
92.2k
                result.push(zip_entry.clone());
140
92.2k
            }
141
        }
142
11.7k
        return Ok(Operations {
143
11.7k
            zip_entries: result,
144
11.7k
        });
145
11.8k
    }
146
}
147
148
11.7k
fn build_zip(zip_entries: &Vec<ZipEntry>) -> Result<Vec<u8>, ZipError> {
149
    use std::io::Write;
150
    use zip::write::FileOptions;
151
152
    // 1mB
153
11.7k
    let max_zip_size = 1024 * 1024 * 1;
154
11.7k
    let mut buf = vec![0; max_zip_size];
155
11.7k
    let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
156
157
11.7k
    let mut created_paths = std::collections::HashSet::new();
158
174k
    for entry in zip_entries.iter() {
159
        use ZipEntry::*;
160
174k
        match entry {
161
55.8k
            File(file) => {
162
55.8k
                if created_paths.contains(&file.zip_path) {
163
0
                    continue;
164
55.8k
                } else {
165
55.8k
                    created_paths.insert(file.zip_path.clone());
166
55.8k
                }
167
55.8k
                let options = FileOptions::default()
168
55.8k
                    .compression_method(file.compression.method)
169
55.8k
                    .compression_level(file.compression.level)
170
55.8k
                    .large_file(file.large_file)
171
55.8k
                    .unix_permissions(file.unix_permissions);
172
55.8k
                if let Some(alignment) = file.alignment {
173
44.0k
                    zip.start_file_aligned(file.zip_path.clone(), options, alignment)?;
174
                } else {
175
11.7k
                    zip.start_file(file.zip_path.clone(), options)?;
176
                }
177
178
55.7k
                zip.write(&file.contents)?;
179
            }
180
2.01k
            RawComment(comment) => zip.set_raw_comment(comment.to_vec()),
181
90.0k
            Comment(comment) => zip.set_comment(comment),
182
2.82k
            Symlink { src, dst } => {
183
2.82k
                if created_paths.contains(dst) {
184
0
                    continue;
185
2.82k
                } else {
186
2.82k
                    created_paths.insert(dst.clone());
187
2.82k
                }
188
2.82k
                let options = FileOptions::default();
189
2.82k
                zip.add_symlink(dst, src, options)?;
190
            }
191
24.2k
            Directory { path } => {
192
24.2k
                if created_paths.contains(path) {
193
0
                    continue;
194
24.2k
                } else {
195
24.2k
                    created_paths.insert(path.clone());
196
24.2k
                }
197
24.2k
                let options = FileOptions::default();
198
24.2k
                zip.add_directory(path, options)?;
199
            }
200
        }
201
    }
202
203
11.7k
    return Ok(zip.finish()?.get_ref().to_vec());
204
11.7k
}
205
206
fuzz_target!(|operations: Operations| {
207
    match build_zip(&operations.zip_entries) {
208
        Ok(compressed) => match zip::ZipArchive::new(std::io::Cursor::new(compressed)) {
209
            Ok(mut archive) => {
210
                for i in 0..archive.len() {
211
                    let mut file = archive
212
                        .by_index(i)
213
                        .expect("This was a generated zip it should be valid.");
214
                    let _ = std::io::copy(&mut file, &mut std::io::sink());
215
                }
216
            }
217
            Err(e) => {
218
                let _unused = black_box(format!("{e:?}"));
219
                let _unused = black_box(format!("{e:#?}"));
220
            }
221
        },
222
        Err(e) => {
223
            let _unused = black_box(format!("{e:?}"));
224
            let _unused = black_box(format!("{e:#?}"));
225
        }
226
    }
227
});