From ff2b133fa2c308c977486161d8bdd2044bbaaf86 Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Wed, 23 Oct 2024 19:42:41 +0000 Subject: [PATCH] OSS-Fuzz: OSS-Fuzz fuzzing integration Signed-off-by: Arthur Chan --- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 36 +++++++++ fuzz/fuzz_targets/archive.rs | 139 +++++++++++++++++++++++++++++++++++ fuzz/fuzz_targets/builder.rs | 67 +++++++++++++++++ fuzz/fuzz_targets/tar.rs | 99 +++++++++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/archive.rs create mode 100644 fuzz/fuzz_targets/builder.rs create mode 100644 fuzz/fuzz_targets/tar.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..1a45eee7 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..3552e0dc --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "tar-fuzz" +version = "0.0.0" +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tempfile = "3.3" + +[dependencies.tar] +path = ".." + +[[bin]] +name = "archive" +path = "fuzz_targets/archive.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "builder" +path = "fuzz_targets/builder.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "tar" +path = "fuzz_targets/tar.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/archive.rs b/fuzz/fuzz_targets/archive.rs new file mode 100644 index 00000000..b3fe721f --- /dev/null +++ b/fuzz/fuzz_targets/archive.rs @@ -0,0 +1,139 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use std::fs::{File, OpenOptions}; +use std::io::{Cursor, Write, Read}; +use tar::{Archive, Builder, EntryType, Header}; +use tempfile::tempdir; +use std::convert::TryInto; +use std::str; + +fuzz_target!(|data: &[u8]| { + // Skip this iteration when data is not enough + if data.len() < 10 { + return; + } + + // Create temp file and dir + let temp_dir = tempdir().unwrap(); + let file_name = match str::from_utf8(&data[0..data.len().min(10)]) { + Ok(name) => name.to_string(), + Err(_) => "default_file_name".to_string(), + }; + let dir_name = match str::from_utf8(&data[data.len().min(10)..data.len().min(20)]) { + Ok(name) => name.to_string(), + Err(_) => "default_dir_name".to_string(), + }; + let temp_file_path = temp_dir.path().join(format!("{}_file.tar", file_name)); + + // Initialise builder and cursor + let mut builder = Builder::new(Vec::new()); + let mut cursor = Cursor::new(data.to_vec()); + + // Choose an etnry type + let entry_type_byte = data[0]; + let entry_type = match entry_type_byte % 5 { + 0 => EntryType::Regular, + 1 => EntryType::Directory, + 2 => EntryType::Symlink, + 3 => EntryType::hard_link(), + _ => EntryType::character_special(), + }; + + // Initilaise header + let mut header = Header::new_gnu(); + let file_size = u64::from_le_bytes( + data.get(1..9) + .unwrap_or(&[0; 8]) + .try_into() + .unwrap_or([0; 8]), + ); + header.set_size(file_size); + header.set_entry_type(entry_type); + header.set_cksum(); + + // Prepare sample tar file + let tar_file_path = format!("{}/{}", dir_name, file_name); + let _ = builder.append_data(&mut header, tar_file_path.clone(), &mut cursor).ok(); + cursor.set_position(0); + for i in 1..5 { + let start = i * 10 % data.len(); + let end = std::cmp::min(start + 10, data.len()); + let entry_data = &data[start..end]; + let entry_name = match str::from_utf8(&entry_data) { + Ok(name) => name.to_string(), + Err(_) => format!("entry_{}", i), + }; + + let mut entry_header = Header::new_gnu(); + entry_header.set_size(entry_data.len() as u64); + entry_header.set_entry_type(entry_type); + entry_header.set_cksum(); + + let mut entry_cursor = Cursor::new(entry_data.to_vec()); + let _ = builder.append_data(&mut entry_header, entry_name, &mut entry_cursor).ok(); + } + + // Prepare malformed tar header + if data.len() > 512 { + let corrupt_header_data = &data[data.len() - 512..]; + let corrupt_header = Header::from_byte_slice(corrupt_header_data); + let mut corrupt_cursor = Cursor::new(data.to_vec()); + let corrupt_entry_name = "corrupt_entry.txt"; + let _ = builder.append_data(&mut corrupt_header.clone(), corrupt_entry_name, &mut corrupt_cursor).ok(); + } + + if let Ok(mut tar_file) = File::create(&temp_file_path) { + if let Ok(tar_data) = builder.into_inner() { + let _ = tar_file.write_all(&tar_data); + } + } + + // Fuzz archive and builder unpack with malformed tar archvie + if let Ok(mut tar_file) = OpenOptions::new().read(true).open(&temp_file_path) { + let mut tar_data = Vec::new(); + let _ = tar_file.read_to_end(&mut tar_data); + let mut tar_cursor = Cursor::new(tar_data); + let mut archive = Archive::new(&mut tar_cursor); + let _ = archive.unpack(temp_dir.path()).ok(); + } + + // Fuzz archive and builder + for i in 0..3 { + let name_data = &data[i * 5 % data.len()..(i * 5 + 5) % data.len()]; + let name = match str::from_utf8(name_data) { + Ok(n) => n.to_string(), + Err(_) => format!("random_name_{}", i), + }; + let path = temp_dir.path().join(name); + if i % 2 == 0 { + // Create a file + if let Ok(mut file) = File::create(&path) { + let _ = file.write_all(data); + } + } else { + // Create a directory + let _ = std::fs::create_dir(&path); + } + } + + // Fuzz unpacking + let mut data_cursor = Cursor::new(data.to_vec()); + let mut data_archive = Archive::new(&mut data_cursor); + let _ = data_archive.unpack(temp_dir.path()).ok(); +}); diff --git a/fuzz/fuzz_targets/builder.rs b/fuzz/fuzz_targets/builder.rs new file mode 100644 index 00000000..96678e95 --- /dev/null +++ b/fuzz/fuzz_targets/builder.rs @@ -0,0 +1,67 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use std::io::Cursor; +use tar::Builder; +use tempfile::{tempdir, tempfile}; + +fuzz_target!(|data: &[u8]| { + // Initialization + let random_bool = data.first().map(|&b| b % 2 == 0).unwrap_or(false); + let temp_dir = tempdir().expect(""); + + // Create a temporary file for testing + if let Ok(temp_file) = tempfile() { + let mut builder = Builder::new(temp_file); + + // Randomly choose a function target from builder to fuzz + match data.first().map(|&b| b % 8) { + Some(0) => { + builder.mode(if random_bool { tar::HeaderMode::Deterministic } else { tar::HeaderMode::Complete }); + } + Some(1) => { + if let Ok(mut file) = tempfile() { + let _ = builder.append_file("testfile.txt", &mut file); + } + } + Some(2) => { + let _ = builder.append_data(&mut tar::Header::new_old(), "randomfile", Cursor::new(data)); + } + Some(3) => { + if let Ok(mut file) = tempfile() { + let _ = builder.append_data(&mut tar::Header::new_old(), "testwrite.txt", &mut file); + } + } + Some(4) => { + let link_path = temp_dir.path().join("testlink"); + let _ = builder.append_link(&mut tar::Header::new_old(), "testlink.txt", &link_path); + } + Some(5) => { + let _ = builder.append_path(temp_dir.path()); + } + Some(6) => { + let link_path = temp_dir.path().join("testlink_with_path"); + let _ = builder.append_link(&mut tar::Header::new_old(), temp_dir.path(), &link_path); + } + Some(7) => { + let _ = builder.append_dir_all("testdir", temp_dir.path()); + } + _ => {} + } + } +}); diff --git a/fuzz/fuzz_targets/tar.rs b/fuzz/fuzz_targets/tar.rs new file mode 100644 index 00000000..77fb33e8 --- /dev/null +++ b/fuzz/fuzz_targets/tar.rs @@ -0,0 +1,99 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use tar::{Builder, Header, Archive, EntryType}; +use std::io::{Cursor, Read, Write, Seek}; +use tempfile::{tempdir, NamedTempFile}; + +fuzz_target!(|data: &[u8]| { + // Setup temporary directory and path + let temp_dir = tempdir().unwrap(); + let archive_data = Cursor::new(data); + let mut builder = Builder::new(Cursor::new(Vec::new())); + let mut header = Header::new_gnu(); + + // Set header metadata + header.set_size(data.len() as u64); + header.set_cksum(); + header.set_entry_type(EntryType::file()); + + // Append data and a temp file to tar + let _ = builder.append_data(&mut header, "fuzzed/file", archive_data); + let mut temp_file = NamedTempFile::new().unwrap(); + let _ = temp_file.write_all(data); + let _ = builder.append_file("fuzzed/file2", temp_file.as_file_mut()).ok(); + + #[cfg(unix)] + let _ = builder.append_link(&mut header, "symlink/path", "target/path").ok(); + + let _ = builder.finish(); + + // Fuzzing Archive and Entry logic + let mut archive = Archive::new(Cursor::new(data)); + if let Ok(mut entries) = archive.entries() { + while let Some(Ok(mut entry)) = entries.next() { + let _ = entry.path().map(|p| p.to_owned()); + let _ = entry.link_name().map(|l| l.map(|ln| ln.to_owned())); + let _ = entry.size(); + let _ = entry.header(); + let _ = entry.raw_header_position(); + let _ = entry.raw_file_position(); + + match entry.header().entry_type() { + EntryType::Regular => { /* Do nothing */ } + EntryType::Directory => { + let _ = entry.unpack_in(temp_dir.path()).ok(); + } + EntryType::Symlink => { + let _ = entry.unpack_in(temp_dir.path()).ok(); + } + EntryType::Link => { + let _ = entry.unpack_in(temp_dir.path()).ok(); + } + EntryType::Fifo => { /* Do nothing */ } + _ => { /* Do nothing */ } + } + + let mut buffer = Vec::new(); + let _ = entry.read_to_end(&mut buffer).ok(); + entry.set_mask(0o755); + entry.set_unpack_xattrs(true); + entry.set_preserve_permissions(true); + entry.set_preserve_mtime(true); + + // Fuzz unpack + let dst_path = temp_dir.path().join("unpacked_file"); + let _ = entry.unpack(&dst_path).ok(); + let _ = entry.unpack_in(temp_dir.path()).ok(); + + // Fuzz PaxExtensions + if let Ok(Some(pax_extensions)) = entry.pax_extensions() { + for ext in pax_extensions { + let _ = ext.ok(); + } + } + + // Fuzzing file search with tar entry position + if entry.size() > 0 { + let mut data_cursor = Cursor::new(data); + let _ = data_cursor.seek(std::io::SeekFrom::Start(entry.raw_file_position())).ok(); + let _ = data_cursor.read(&mut buffer).ok(); + } + } + } +});