Skip to content

Commit

Permalink
OSS-Fuzz: OSS-Fuzz fuzzing integration (#385)
Browse files Browse the repository at this point in the history
* OSS-Fuzz: OSS-Fuzz fuzzing integration

Signed-off-by: Arthur Chan <[email protected]>
  • Loading branch information
arthurscchan authored Nov 4, 2024
1 parent 2b1d374 commit 9189d3c
Show file tree
Hide file tree
Showing 4 changed files with 297 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
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()) {
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();
}
}
}
});

0 comments on commit 9189d3c

Please sign in to comment.