Skip to content

Commit

Permalink
OSS-Fuzz: OSS-Fuzz fuzzing integration
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Chan <[email protected]>
  • Loading branch information
arthurscchan committed Oct 23, 2024
1 parent a1fd011 commit ff2b133
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 0 deletions.
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
36 changes: 36 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
139 changes: 139 additions & 0 deletions fuzz/fuzz_targets/archive.rs
Original file line number Diff line number Diff line change
@@ -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();
});
67 changes: 67 additions & 0 deletions fuzz/fuzz_targets/builder.rs
Original file line number Diff line number Diff line change
@@ -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());
}
_ => {}
}
}
});
99 changes: 99 additions & 0 deletions fuzz/fuzz_targets/tar.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
});

0 comments on commit ff2b133

Please sign in to comment.