-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OSS-Fuzz: OSS-Fuzz fuzzing integration (#385)
* OSS-Fuzz: OSS-Fuzz fuzzing integration Signed-off-by: Arthur Chan <[email protected]>
- Loading branch information
1 parent
2b1d374
commit 9189d3c
Showing
4 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
target | ||
corpus | ||
artifacts | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} | ||
}); |