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 all commits
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
32 changes: 32 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "tar-fuzz"
version = "0.0.0"
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
arbitrary = "1.3.2"
cap-std = "3.4.0"
derive_arbitrary = "1.3.2"
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 = "tar"
path = "fuzz_targets/tar.rs"
test = false
doc = false
bench = false
124 changes: 124 additions & 0 deletions fuzz/fuzz_targets/archive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 arbitrary::{Arbitrary, Unstructured};
use cap_std::fs::Dir;
use cap_std::ambient_authority;
use derive_arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use std::io::{Cursor, Write};
use tar::{Archive, Builder, EntryType, Header};
use tempfile::tempdir;

// Define ArchiveEntry for arbitrary crate
#[derive(Debug, Arbitrary)]
struct ArchiveEntry {
path: String,
entry_type: u8,
content: Vec<u8>,
}

// Define FuzzInput for arbitrary crate
#[derive(Debug, Arbitrary)]
struct FuzzInput {
entries: Vec<ArchiveEntry>,
}

fuzz_target!(|data: &[u8]| {
// Prepare FuzzInput with Arbitrary
let mut unstructured = Unstructured::new(data);
let input: FuzzInput = match FuzzInput::arbitrary(&mut unstructured) {
Ok(val) => val,
Err(_) => return,
};

// Create a sandbox directory with cap_std
let temp_dir = match tempdir() {
Ok(dir) => dir,
Err(_) => return,
};
let sandbox_dir = match Dir::open_ambient_dir(temp_dir.path(), ambient_authority()) {
arthurscchan marked this conversation as resolved.
Show resolved Hide resolved
Ok(dir) => dir,
Err(_) => return,
};
let temp_file_path = "archive_file.tar";
let mut builder = Builder::new(Vec::new());

// Iterate through the archive entries to build a tar structure
for entry in &input.entries {
let mut header = Header::new_gnu();

// Ensure content size is reasonable to avoid potential overflow issues
let file_size = entry.content.len() as u64;
if file_size > u32::MAX as u64 {
continue;
}
header.set_size(file_size);

// Determine the entry type from fuzzed data
let entry_type = match entry.entry_type % 5 {
0 => EntryType::Regular,
1 => EntryType::Directory,
2 => EntryType::Symlink,
3 => EntryType::hard_link(),
_ => EntryType::character_special(),
};
header.set_entry_type(entry_type);

// Process entry types using cap_std sandbox
match entry_type {
EntryType::Directory => {
if let Err(_) = sandbox_dir.create_dir_all(&entry.path) {
continue;
}
if builder.append_dir(&entry.path, &entry.path).is_err() {
continue;
}
}
EntryType::Regular => {
let mut cursor = Cursor::new(entry.content.clone());
if builder.append_data(&mut header, entry.path.as_str(), &mut cursor).is_err() {
continue;
}
}
_ => {
// Handle other types with appropriate mock content or skip unsupported
let mut cursor = Cursor::new(entry.content.clone());
if builder.append_data(&mut header, entry.path.as_str(), &mut cursor).is_err() {
continue;
}
}
}
}

// Write the builder content to the temporary tar file within the sandbox
if let Ok(mut temp_file) = sandbox_dir.create(temp_file_path) {
if temp_file.write_all(&builder.into_inner().unwrap_or_default()).is_ok() {
let mut archive = Archive::new(temp_file);
if let Ok(entries) = archive.entries() {
for entry in entries {
if entry.is_err() {
return;
}
}
}
}
}

// Cleanup temp directory and sandbox directory
drop(sandbox_dir);
drop(temp_dir);
});
137 changes: 137 additions & 0 deletions fuzz/fuzz_targets/tar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// 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 arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;
use std::io::{Cursor, Read, Seek, Write};
use tar::{Archive, Builder, EntryType, Header};
use tempfile::{tempdir, NamedTempFile};

// Define FuzzInput for arbitrary crate
#[derive(Debug)]
struct FuzzInput {
data: Vec<u8>,
file_name: String,
link_path: String,
target_path: String,
entry_type: u8,
metadata_size: u64,
}

// Implement Arbitrary for FuzzInput
impl<'a> Arbitrary<'a> for FuzzInput {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(FuzzInput {
data: u.arbitrary()?,
file_name: u.arbitrary::<&str>()?.to_string(),
link_path: u.arbitrary::<&str>()?.to_string(),
target_path: u.arbitrary::<&str>()?.to_string(),
entry_type: u.arbitrary()?,
metadata_size: u.int_in_range(0..=1000)?,
})
}
}

fuzz_target!(|data: &[u8]| {
// Prepare FuzzInput by Arbitrary crate
let mut unstructured = Unstructured::new(data);
let input: FuzzInput = match FuzzInput::arbitrary(&mut unstructured) {
Ok(val) => val,
Err(_) => return,
};

// Setup temporary directory and initialize builder
let temp_dir = match tempdir() {
Ok(dir) => dir,
Err(_) => return,
};
let archive_data = Cursor::new(&input.data);
let mut builder = Builder::new(Cursor::new(Vec::new()));
let mut header = Header::new_gnu();

// Set random header metadata
header.set_size(input.metadata_size.min(input.data.len() as u64));
header.set_cksum();
let entry_type = match input.entry_type % 5 {
0 => EntryType::Regular,
1 => EntryType::Directory,
2 => EntryType::Symlink,
3 => EntryType::Link,
_ => EntryType::Fifo,
};
header.set_entry_type(entry_type);

// Append data
let _ = builder.append_data(&mut header, &input.file_name, archive_data);
if let Ok(mut temp_file) = NamedTempFile::new() {
let _ = temp_file.write_all(&input.data);
let _ = builder.append_file("fuzzed/file2", temp_file.as_file_mut()).ok();
}

#[cfg(unix)]
let _ = builder.append_link(&mut header, &input.link_path, &input.target_path).ok();
let _ = builder.finish();

// Fuzzing Archive and Entry logic
let mut archive = Archive::new(Cursor::new(&input.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();

// Randomly choose entry actions based on entry type
match entry.header().entry_type() {
EntryType::Regular => { /* Do nothing */ }
EntryType::Directory | EntryType::Symlink | EntryType::Link => {
let _ = entry.unpack_in(temp_dir.path()).ok();
}
EntryType::Fifo => { /* Do nothing */ }
_ => { /* Do nothing */ }
}

// Randomly read contents and adjust permissions and attributes
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 to randomized destination path
let dst_path = temp_dir.path().join(&input.file_name);
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();
}
}

// Randomized file search with tar entry position
if entry.size() > 0 {
let mut data_cursor = Cursor::new(&input.data);
let _ = data_cursor.seek(std::io::SeekFrom::Start(entry.raw_file_position())).ok();
let _ = data_cursor.read(&mut buffer).ok();
}
}
}
});