Coverage Report

Created: 2025-06-04 06:23

/src/zip/fuzz/fuzz_targets/roundtrip.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
#![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
80.7k
fn arbitrary_unix_permissions(u: &mut Unstructured) -> arbitrary::Result<u32> {
24
80.7k
    return Ok(u.int_in_range(0o1..=0o777)?);
25
80.7k
}
26
27
// Generate a valid arbitrary path.
28
129k
fn arbitrary_path(u: &mut Unstructured) -> arbitrary::Result<String> {
29
129k
    let path_len: usize = u.int_in_range(1..=512).unwrap_or(1);
30
129k
    Ok((0..=path_len)
31
40.8M
        .map(|_| {
32
40.8M
            let valid_chars: Vec<char> = ('a'..='z')
33
40.8M
                .chain('A'..='Z')
34
40.8M
                .chain("/-_.@".chars())
35
40.8M
                .collect();
36
40.8M
            return valid_chars[u.choose_index(valid_chars.len()).unwrap_or(0)];
37
40.8M
        })
38
129k
        .collect())
39
129k
}
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
80.7k
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
49
        use zip::CompressionMethod::*;
50
80.7k
        Ok(match u8::arbitrary(u) {
51
814
            Ok(1) => Compression {
52
814
                method: Stored,
53
814
                level: u.int_in_range(0..=9).ok(),
54
814
            },
55
25.0k
            Ok(2) => Compression {
56
25.0k
                method: Deflated,
57
25.0k
                level: u.int_in_range(0..=9).ok(),
58
25.0k
            },
59
5.18k
            Ok(3) => Compression {
60
5.18k
                method: Bzip2,
61
5.18k
                level: u.int_in_range(1..=9).ok(),
62
5.18k
            },
63
49.6k
            _ => Compression {
64
49.6k
                method: Zstd,
65
49.6k
                level: u.int_in_range(-7..=22).ok(),
66
49.6k
            },
67
        })
68
80.7k
    }
69
}
70
71
80.8k
fn arbitrary_file(u: &mut Unstructured) -> arbitrary::Result<Vec<u8>> {
72
80.8k
    let file = Vec::<u8>::arbitrary(u)?;
73
80.8k
    if file.len() == 0 {
74
85
        return Err(arbitrary::Error::IncorrectFormat);
75
80.7k
    }
76
80.7k
    return Ok(file);
77
80.8k
}
78
79
80.9k
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::File as arbitrary::Arbitrary>::arbitrary::{closure#0}
Line
Count
Source
79
48
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::File as arbitrary::Arbitrary>::arbitrary::{closure#1}
Line
Count
Source
79
80.8k
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::File as arbitrary::Arbitrary>::arbitrary::{closure#2}
Line
Count
Source
79
48
#[derive(Arbitrary, Clone, Debug, PartialEq)]
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#2}
Unexecuted instantiation: <roundtrip::File as arbitrary::Arbitrary>::try_size_hint::{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
342k
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary::{closure#0}
Line
Count
Source
92
22
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary::{closure#1}
Line
Count
Source
92
342k
#[derive(Arbitrary, Clone, Debug, PartialEq)]
<roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary::{closure#2}
Line
Count
Source
92
22
#[derive(Arbitrary, Clone, Debug, PartialEq)]
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#0}
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#2}
Unexecuted instantiation: <roundtrip::ZipEntry as arbitrary::Arbitrary>::try_size_hint::{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
335k
    fn path(&self) -> Option<String> {
111
335k
        match self {
112
79.8k
            ZipEntry::File(file) => Some(file.zip_path.clone()),
113
5.87k
            ZipEntry::Symlink { dst, .. } => Some(dst.clone()),
114
36.7k
            ZipEntry::Directory { path } => Some(path.clone()),
115
212k
            _ => None,
116
        }
117
335k
    }
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
10.4k
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
127
10.4k
        let zip_entries = Vec::<ZipEntry>::arbitrary(u)?;
128
10.3k
        let mut paths = std::collections::HashSet::new();
129
10.3k
        let mut result = Vec::new();
130
335k
        for zip_entry in zip_entries.iter() {
131
335k
            if let Some(path) = zip_entry.path() {
132
122k
                if paths.contains(&path) {
133
29.7k
                    continue;
134
92.7k
                } else {
135
92.7k
                    result.push(zip_entry.clone());
136
92.7k
                    paths.insert(path.clone());
137
92.7k
                }
138
212k
            } else {
139
212k
                result.push(zip_entry.clone());
140
212k
            }
141
        }
142
10.3k
        return Ok(Operations {
143
10.3k
            zip_entries: result,
144
10.3k
        });
145
10.4k
    }
146
}
147
148
10.3k
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
10.3k
    let max_zip_size = 1024 * 1024 * 1;
154
10.3k
    let mut buf = vec![0; max_zip_size];
155
10.3k
    let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
156
10.3k
157
10.3k
    let mut created_paths = std::collections::HashSet::new();
158
304k
    for entry in zip_entries.iter() {
159
        use ZipEntry::*;
160
304k
        match entry {
161
61.5k
            File(file) => {
162
61.5k
                if created_paths.contains(&file.zip_path) {
163
0
                    continue;
164
61.5k
                } else {
165
61.5k
                    created_paths.insert(file.zip_path.clone());
166
61.5k
                }
167
61.5k
                let options = FileOptions::default()
168
61.5k
                    .compression_method(file.compression.method)
169
61.5k
                    .compression_level(file.compression.level)
170
61.5k
                    .large_file(file.large_file)
171
61.5k
                    .unix_permissions(file.unix_permissions);
172
61.5k
                if let Some(alignment) = file.alignment {
173
50.5k
                    zip.start_file_aligned(file.zip_path.clone(), options, alignment)?;
174
                } else {
175
11.0k
                    zip.start_file(file.zip_path.clone(), options)?;
176
                }
177
178
61.5k
                zip.write(&file.contents)?;
179
            }
180
2.19k
            RawComment(comment) => zip.set_raw_comment(comment.to_vec()),
181
210k
            Comment(comment) => zip.set_comment(comment),
182
3.75k
            Symlink { src, dst } => {
183
3.75k
                if created_paths.contains(dst) {
184
0
                    continue;
185
3.75k
                } else {
186
3.75k
                    created_paths.insert(dst.clone());
187
3.75k
                }
188
3.75k
                let options = FileOptions::default();
189
3.75k
                zip.add_symlink(dst, src, options)?;
190
            }
191
26.6k
            Directory { path } => {
192
26.6k
                if created_paths.contains(path) {
193
0
                    continue;
194
26.6k
                } else {
195
26.6k
                    created_paths.insert(path.clone());
196
26.6k
                }
197
26.6k
                let options = FileOptions::default();
198
26.6k
                zip.add_directory(path, options)?;
199
            }
200
        }
201
    }
202
203
10.2k
    return Ok(zip.finish()?.get_ref().to_vec());
204
10.3k
}
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
});