Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSS-Fuzz: OSS-Fuzz fuzzing integration #385

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// Create temp file and dir
let temp_dir = tempdir().unwrap();
let file_name = match str::from_utf8(&data[0..data.len().min(10)]) {
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
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)]) {
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
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)
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
.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()];
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
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() {
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
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();
}
}
}
});