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

Add fuzzing support #1632

Merged
merged 2 commits into from
Sep 6, 2019
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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ rand = "0.7"
rand_chacha = "0.2"
semver = "0.9"

[target.'cfg(fuzzing)'.dependencies]
arbitrary = "0.2"
interpolate_name = "0.2.2"
rand = "0.7"
rand_chacha = "0.2"

[[bin]]
name = "rav1e"
required-features = ["binaries"]
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ Run regular benchmarks with:
cargo bench --features=bench
```

### Fuzzing

Install `cargo-fuzz` with `cargo install cargo-fuzz`. Running fuzz targets requires nightly Rust, so install that too with `rustup install nightly`.

* List the fuzz targets with `cargo fuzz list`.
* Run a fuzz target with `cargo +nightly fuzz run <target>`.
* Parallel fuzzing: `cargo +nightly fuzz run --jobs <n> <target> -- -workers=<n>`.
* Disable memory leak detection (seems to trigger always): `cargo +nightly fuzz run <target> -- -detect_leaks=0`.
* Bump the "slow unit" time limit: `cargo +nightly fuzz run <target> -- -report_slow_units=600`.
* Make the fuzzer generate long inputs right away (useful because fuzzing uses a ring buffer for data, so when the fuzzer generates big inputs it has a chance to affect different settings individually): `cargo +nightly fuzz run <target> -- -max_len=256 -len_control=0`.
* Release configuration (not really recommended because it disables debug assertions and integer overflow assertions): `RUSTFLAGS='-C codegen-units=1' cargo +nightly fuzz run --release <target>`
* `codegen-units=1` fixes https://github.com/rust-fuzz/cargo-fuzz/issues/161.
* Just give me the complete command line: `RUSTFLAGS='-C codegen-units=1' cargo +nightly fuzz run -j10 encode -- -workers=10 -detect_leaks=0 -timeout=600 -report_slow_units=600 -max_len=256 -len_control=0`.
* Run a single artifact with debug output: `RUST_LOG=debug <path/to/fuzz/target/executable> <path/to/artifact>`, for example, `RUST_LOG=debug fuzz/target/x86_64-unknown-linux-gnu/debug/encode fuzz/artifacts/encode/crash-2f5672cb76691b989bbd2022a5349939a2d7b952`.
* For adding new fuzz targets, see comment at the top of `src/fuzzing.rs`.

## Getting in Touch

Come chat with us on the IRC channel #daala on Freenode! If you don't have IRC set
Expand Down
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
35 changes: 35 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

[package]
name = "rav1e-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false

[package.metadata]
cargo-fuzz = true

[dependencies]
pretty_env_logger = "0.3"

[dependencies.rav1e]
path = ".."
features = ["decode_test_dav1d"]
[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "encode_decode"
path = "fuzz_targets/encode_decode.rs"
required-features = ["rav1e/decode_test_dav1d"]

[[bin]]
name = "encode"
path = "fuzz_targets/encode.rs"

[[bin]]
name = "construct_context"
path = "fuzz_targets/construct_context.rs"
10 changes: 10 additions & 0 deletions fuzz/fuzz_targets/construct_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate rav1e;
use rav1e::fuzzing::*;

fuzz_target!(|data| {
let _ = pretty_env_logger::try_init();

fuzz_construct_context(data)
});
10 changes: 10 additions & 0 deletions fuzz/fuzz_targets/encode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate rav1e;
use rav1e::fuzzing::*;

fuzz_target!(|data| {
let _ = pretty_env_logger::try_init();

fuzz_encode(data)
});
10 changes: 10 additions & 0 deletions fuzz/fuzz_targets/encode_decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate rav1e;
use rav1e::fuzzing::*;

fuzz_target!(|data| {
let _ = pretty_env_logger::try_init();

fuzz_encode_decode(data)
});
216 changes: 216 additions & 0 deletions src/fuzzing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
use std::sync::Arc;

use arbitrary::*;

use crate::prelude::*;

// Adding new fuzz targets
//
// 1. Add a function to this file which looks like this:
//
// pub fn fuzz_something(data: &[u8]) {
// let mut g = create_generator!();
//
// // Invoke everything you need.
// //
// // You should use g.g() to get an arbitrary value of any type that
// // implements Arbitrary [1]. This is how fuzzer affects the
// // execution—by feeding in different bytes, which result in different
// // arbitrary values being generated.
// // [1]: https://docs.rs/arbitrary/0.2.0/arbitrary/trait.Arbitrary.html
// //
// // Print out the structures you create with arbitrary data with
// // debug!().
// }
//
// 2. cargo fuzz add something
// 3. Copy the contents of any other .rs file from fuzz/fuzz_targets/ into the
// newly created fuzz/fuzz_targets/something.rs and change the function
// being called to fuzz_something.
//
// Now you can fuzz the new target with cargo fuzz.

// A helper for generating arbitrary data.
struct Generator<'a> {
buffer: RingBuffer<'a>,
}

impl<'a> Generator<'a> {
fn new(data: &'a [u8]) -> Result<Self, BufferError> {
Ok(Self { buffer: RingBuffer::new(data, data.len())? })
}

fn g<T: Arbitrary>(&mut self) -> T {
<T as Arbitrary>::arbitrary(&mut self.buffer).unwrap()
}
}

macro_rules! create_generator {
($data:expr) => {{
let g = Generator::new($data);
if g.is_err() {
return;
}
g.unwrap()
}};
}

pub fn fuzz_construct_context(data: &[u8]) {
let mut g = create_generator!(data);

let mut config = Config::default();
config.threads = 1;
config.enc.width = g.g();
config.enc.height = g.g();
config.enc.bit_depth = (g.g::<u8>() % 17) as usize;
config.enc.still_picture = g.g();
config.enc.time_base = Rational::new(g.g(), g.g());
config.enc.min_key_frame_interval = g.g();
config.enc.max_key_frame_interval = g.g();
config.enc.reservoir_frame_delay = g.g();
config.enc.low_latency = g.g();
config.enc.quantizer = g.g();
config.enc.min_quantizer = g.g();
config.enc.bitrate = g.g();
config.enc.tile_cols = g.g();
config.enc.tile_rows = g.g();
config.enc.tiles = g.g();
config.enc.rdo_lookahead_frames = g.g();
config.enc.speed_settings = SpeedSettings::from_preset(g.g());
config.enc.show_psnr = g.g();
config.enc.train_rdo = g.g();

debug!("config = {:#?}", config);

let _: Result<Context<u16>, _> = config.new_context();
}

fn encode_frames(
ctx: &mut Context<u8>, mut frames: impl Iterator<Item = Frame<u8>>,
) -> Result<(), EncoderStatus> {
loop {
let rv = ctx.receive_packet();
debug!("ctx.receive_packet() = {:#?}", rv);

match rv {
Ok(_packet) => {}
Err(EncoderStatus::Encoded) => {}
Err(EncoderStatus::LimitReached) => {
break;
}
Err(EncoderStatus::NeedMoreData) => {
ctx.send_frame(frames.next().map(Arc::new))?;
}
Err(EncoderStatus::EnoughData) => {
unreachable!();
}
Err(EncoderStatus::NotReady) => {
unreachable!();
}
Err(EncoderStatus::Failure) => {
return Err(EncoderStatus::Failure);
}
}
}

Ok(())
}

pub fn fuzz_encode(data: &[u8]) {
let mut g = create_generator!(data);

let mut config = Config::default();
config.threads = 1;
config.enc.width = g.g::<u8>() as usize + 1;
config.enc.height = g.g::<u8>() as usize + 1;
config.enc.still_picture = g.g();
config.enc.time_base = Rational::new(g.g(), g.g());
config.enc.min_key_frame_interval = (g.g::<u8>() % 4) as u64;
config.enc.max_key_frame_interval = (g.g::<u8>() % 4) as u64 + 1;
config.enc.low_latency = g.g();
config.enc.quantizer = g.g();
config.enc.min_quantizer = g.g();
config.enc.bitrate = g.g();
// config.enc.tile_cols = g.g();
// config.enc.tile_rows = g.g();
// config.enc.tiles = g.g();
config.enc.rdo_lookahead_frames = g.g();
config.enc.speed_settings = SpeedSettings::from_preset(10);

debug!("config = {:#?}", config);

let res = config.new_context();
if res.is_err() {
return;
}
let mut context: Context<u8> = res.unwrap();

let frame_count = g.g::<u8>() % 3 + 1;
let frames = (0..frame_count).map(|_| {
let mut frame = Frame::new(
config.enc.width,
config.enc.height,
config.enc.chroma_sampling,
);

for plane in &mut frame.planes {
let stride = plane.cfg.stride;
for row in plane.data_origin_mut().chunks_mut(stride) {
for pixel in row {
*pixel = g.g();
}
}
}

frame
});

let _ = encode_frames(&mut context, frames);
}

#[cfg(feature = "decode_test_dav1d")]
pub fn fuzz_encode_decode(data: &[u8]) {
use crate::test_encode_decode::*;

let mut g = create_generator!(data);

let w = g.g::<u8>() as usize + 1;
let h = g.g::<u8>() as usize + 1;
let speed = 10;
let q = g.g();
let limit = (g.g::<u8>() % 3) as usize + 1;
let min_keyint = g.g::<u64>() % 4;
let max_keyint = g.g::<u64>() % 4 + 1;
let low_latency = g.g();
let bitrate = g.g();

debug!(
"w = {:#?}\n\
h = {:#?}\n\
speed = {:#?}\n\
q = {:#?}\n\
limit = {:#?}\n\
min_keyint = {:#?}\n\
max_keyint = {:#?}\n\
low_latency = {:#?}\n\
bitrate = {:#?}",
w, h, speed, q, limit, min_keyint, max_keyint, low_latency, bitrate
);

let mut dec = get_decoder::<u8>("dav1d", w, h);
dec.encode_decode(
w,
h,
speed,
q,
limit,
8,
Default::default(),
min_keyint,
max_keyint,
low_latency,
bitrate,
1,
1,
);
}
8 changes: 7 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ pub mod version {
format!("{} ({})", short(), semver)
}
}
#[cfg(all(test, any(feature = "decode_test", feature = "decode_test_dav1d")))]
#[cfg(all(
any(test, fuzzing),
any(feature = "decode_test", feature = "decode_test_dav1d")
))]
mod test_encode_decode;

#[cfg(feature = "bench")]
Expand Down Expand Up @@ -255,3 +258,6 @@ pub mod bench {
pub use crate::util::*;
}
}

#[cfg(fuzzing)]
pub mod fuzzing;
10 changes: 5 additions & 5 deletions src/test_encode_decode/aom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ impl<T: Pixel> TestDecoder<T> for AomDecoder<T> {
packet.len(),
ptr::null_mut(),
);
println!("Decoded. -> {}", ret);
debug!("Decoded. -> {}", ret);
if ret != 0 {
let error_msg = aom_codec_error(&mut self.dec);
println!(
debug!(
" Decode codec_decode failed: {}",
CStr::from_ptr(error_msg).to_string_lossy()
);
let detail = aom_codec_error_detail(&mut self.dec);
if !detail.is_null() {
println!(
debug!(
" Decode codec_decode failed {}",
CStr::from_ptr(detail).to_string_lossy()
);
Expand All @@ -84,9 +84,9 @@ impl<T: Pixel> TestDecoder<T> for AomDecoder<T> {

if ret == 0 {
loop {
println!("Retrieving frame");
debug!("Retrieving frame");
let img = aom_codec_get_frame(&mut self.dec, &mut self.iter);
println!("Retrieved.");
debug!("Retrieved.");
if img.is_null() {
return DecodeResult::Done;
}
Expand Down
6 changes: 3 additions & 3 deletions src/test_encode_decode/dav1d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ impl<T: Pixel> TestDecoder<T> for Dav1dDecoder<T> {
let ptr = dav1d_data_create(&mut data, packet.len());
ptr::copy_nonoverlapping(packet.as_ptr(), ptr, packet.len());
let ret = dav1d_send_data(self.dec, &mut data);
println!("Decoded. -> {}", ret);
debug!("Decoded. -> {}", ret);
if ret != 0 {
corrupted_count += 1;
}

if ret == 0 {
loop {
let mut pic: Dav1dPicture = mem::zeroed();
println!("Retrieving frame");
debug!("Retrieving frame");
let ret = dav1d_get_picture(self.dec, &mut pic);
println!("Retrieved.");
debug!("Retrieved.");
if ret == -(EAGAIN as i32) {
return DecodeResult::Done;
}
Expand Down
Loading