/src/tar-rs/fuzz/fuzz_targets/tar.rs
Line | Count | Source |
1 | | // Copyright 2024 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 | | #![no_main] |
16 | | |
17 | | use arbitrary::{Arbitrary, Unstructured}; |
18 | | use libfuzzer_sys::fuzz_target; |
19 | | use std::io::{Cursor, Read, Seek, Write}; |
20 | | use tar::{Archive, Builder, EntryType, Header}; |
21 | | use tempfile::{tempdir, NamedTempFile}; |
22 | | |
23 | | // Define FuzzInput for arbitrary crate |
24 | | #[derive(Debug)] |
25 | | struct FuzzInput { |
26 | | data: Vec<u8>, |
27 | | file_name: String, |
28 | | link_path: String, |
29 | | target_path: String, |
30 | | entry_type: u8, |
31 | | metadata_size: u64, |
32 | | } |
33 | | |
34 | | // Implement Arbitrary for FuzzInput |
35 | | impl<'a> Arbitrary<'a> for FuzzInput { |
36 | 2.81k | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { |
37 | | Ok(FuzzInput { |
38 | 2.81k | data: u.arbitrary()?, |
39 | 2.81k | file_name: u.arbitrary::<&str>()?.to_string(), |
40 | 2.81k | link_path: u.arbitrary::<&str>()?.to_string(), |
41 | 2.81k | target_path: u.arbitrary::<&str>()?.to_string(), |
42 | 2.81k | entry_type: u.arbitrary()?, |
43 | 2.81k | metadata_size: u.int_in_range(0..=1000)?, |
44 | | }) |
45 | 2.81k | } |
46 | | } |
47 | | |
48 | | fuzz_target!(|data: &[u8]| { |
49 | | // Prepare FuzzInput by Arbitrary crate |
50 | | let mut unstructured = Unstructured::new(data); |
51 | | let input: FuzzInput = match FuzzInput::arbitrary(&mut unstructured) { |
52 | | Ok(val) => val, |
53 | | Err(_) => return, |
54 | | }; |
55 | | |
56 | | // Setup temporary directory and initialize builder |
57 | | let temp_dir = match tempdir() { |
58 | | Ok(dir) => dir, |
59 | | Err(_) => return, |
60 | | }; |
61 | | let archive_data = Cursor::new(&input.data); |
62 | | let mut builder = Builder::new(Cursor::new(Vec::new())); |
63 | | let mut header = Header::new_gnu(); |
64 | | |
65 | | // Set random header metadata |
66 | | header.set_size(input.metadata_size.min(input.data.len() as u64)); |
67 | | header.set_cksum(); |
68 | | let entry_type = match input.entry_type % 5 { |
69 | | 0 => EntryType::Regular, |
70 | | 1 => EntryType::Directory, |
71 | | 2 => EntryType::Symlink, |
72 | | 3 => EntryType::Link, |
73 | | _ => EntryType::Fifo, |
74 | | }; |
75 | | header.set_entry_type(entry_type); |
76 | | |
77 | | // Append data |
78 | | let _ = builder.append_data(&mut header, &input.file_name, archive_data); |
79 | | if let Ok(mut temp_file) = NamedTempFile::new() { |
80 | | let _ = temp_file.write_all(&input.data); |
81 | | let _ = builder.append_file("fuzzed/file2", temp_file.as_file_mut()).ok(); |
82 | | } |
83 | | |
84 | | #[cfg(unix)] |
85 | | let _ = builder.append_link(&mut header, &input.link_path, &input.target_path).ok(); |
86 | | let _ = builder.finish(); |
87 | | |
88 | | // Fuzzing Archive and Entry logic |
89 | | let mut archive = Archive::new(Cursor::new(&input.data)); |
90 | | if let Ok(mut entries) = archive.entries() { |
91 | | while let Some(Ok(mut entry)) = entries.next() { |
92 | 13.2k | let _ = entry.path().map(|p| p.to_owned()); |
93 | 13.2k | let _ = entry.link_name().map(|l| l.map(|ln| ln.to_owned())); |
94 | | let _ = entry.size(); |
95 | | let _ = entry.header(); |
96 | | let _ = entry.raw_header_position(); |
97 | | let _ = entry.raw_file_position(); |
98 | | |
99 | | // Randomly choose entry actions based on entry type |
100 | | match entry.header().entry_type() { |
101 | | EntryType::Regular => { /* Do nothing */ } |
102 | | EntryType::Directory | EntryType::Symlink | EntryType::Link => { |
103 | | let _ = entry.unpack_in(temp_dir.path()).ok(); |
104 | | } |
105 | | EntryType::Fifo => { /* Do nothing */ } |
106 | | _ => { /* Do nothing */ } |
107 | | } |
108 | | |
109 | | // Randomly read contents and adjust permissions and attributes |
110 | | let mut buffer = Vec::new(); |
111 | | let _ = entry.read_to_end(&mut buffer).ok(); |
112 | | entry.set_mask(0o755); |
113 | | entry.set_unpack_xattrs(true); |
114 | | entry.set_preserve_permissions(true); |
115 | | entry.set_preserve_mtime(true); |
116 | | |
117 | | // Fuzz unpack to randomized destination path |
118 | | let dst_path = temp_dir.path().join(&input.file_name); |
119 | | let _ = entry.unpack(&dst_path).ok(); |
120 | | let _ = entry.unpack_in(temp_dir.path()).ok(); |
121 | | |
122 | | // Fuzz PaxExtensions |
123 | | if let Ok(Some(pax_extensions)) = entry.pax_extensions() { |
124 | | for ext in pax_extensions { |
125 | | let _ = ext.ok(); |
126 | | } |
127 | | } |
128 | | |
129 | | // Randomized file search with tar entry position |
130 | | if entry.size() > 0 { |
131 | | let mut data_cursor = Cursor::new(&input.data); |
132 | | let _ = data_cursor.seek(std::io::SeekFrom::Start(entry.raw_file_position())).ok(); |
133 | | let _ = data_cursor.read(&mut buffer).ok(); |
134 | | } |
135 | | } |
136 | | } |
137 | | }); |