Skip to content

Commit

Permalink
Include fuzz testing setup (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
carllerche committed May 10, 2018
1 parent 571bb14 commit 173f9a6
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -1,5 +1,10 @@
target
Cargo.lock
h2spec

# These are backup files generated by rustfmt
**/*.rs.bk

# Files generated by honggfuzz
hfuzz_target
hfuzz_workspace
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -29,6 +29,7 @@ unstable = []

[workspace]
members = [
"tests/h2-fuzz",
"tests/h2-tests",
"tests/h2-support",
"util/genfixture",
Expand Down
14 changes: 14 additions & 0 deletions tests/h2-fuzz/Cargo.toml
@@ -0,0 +1,14 @@
[package]
name = "h2-fuzz"
version = "0.0.0"
publish = false
license = "MIT"

[dependencies]
h2 = { path = "../.." }

env_logger = { version = "0.5.3", default-features = false }
futures = "0.1.21"
honggfuzz = "0.5"
http = "0.1.3"
tokio-io = "0.1.4"
Empty file added tests/h2-fuzz/README.md
Empty file.
159 changes: 159 additions & 0 deletions tests/h2-fuzz/src/main.rs
@@ -0,0 +1,159 @@
#[macro_use]
extern crate futures;
extern crate tokio_io;
#[macro_use]
extern crate honggfuzz;
extern crate env_logger;
extern crate h2;
extern crate http;

use futures::prelude::*;
use futures::{executor, future, task};
use http::{Method, Request};
use std::cell::Cell;
use std::io::{self, Read, Write};
use std::sync::Arc;
use tokio_io::{AsyncRead, AsyncWrite};

struct MockIo<'a> {
input: &'a [u8],
}

impl<'a> MockIo<'a> {
fn next_byte(&mut self) -> Option<u8> {
if let Some(&c) = self.input.first() {
self.input = &self.input[1..];
Some(c)
} else {
None
}
}

fn next_u32(&mut self) -> u32 {
(self.next_byte().unwrap_or(0) as u32) << 8 | self.next_byte().unwrap_or(0) as u32
}
}

impl<'a> Read for MockIo<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
let mut len = self.next_u32() as usize;
if self.input.is_empty() {
Ok(0)
} else if len == 0 {
task::current().notify();
Err(io::ErrorKind::WouldBlock.into())
} else {
if len > self.input.len() {
len = self.input.len();
}

if len > buf.len() {
len = buf.len();
}
buf[0..len].copy_from_slice(&self.input[0..len]);
self.input = &self.input[len..];
Ok(len)
}
}
}

impl<'a> AsyncRead for MockIo<'a> {
unsafe fn prepare_uninitialized_buffer(&self, _buf: &mut [u8]) -> bool {
false
}
}

impl<'a> Write for MockIo<'a> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
let len = std::cmp::min(self.next_u32() as usize, buf.len());
if len == 0 {
if self.input.is_empty() {
Err(io::ErrorKind::BrokenPipe.into())
} else {
task::current().notify();
Err(io::ErrorKind::WouldBlock.into())
}
} else {
Ok(len)
}
}

fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}

impl<'a> AsyncWrite for MockIo<'a> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(Async::Ready(()))
}
}

struct MockNotify {
notified: Cell<bool>,
}

unsafe impl Sync for MockNotify {}

impl executor::Notify for MockNotify {
fn notify(&self, _id: usize) {
self.notified.set(true);
}
}

impl MockNotify {
fn take_notify(&self) -> bool {
self.notified.replace(false)
}
}

fn run(script: &[u8]) -> Result<(), h2::Error> {
let notify = Arc::new(MockNotify {
notified: Cell::new(false),
});
let notify_handle: executor::NotifyHandle = notify.clone().into();
let io = MockIo { input: script };
let (mut h2, mut connection) = h2::client::handshake(io).wait()?;
let mut in_progress = None;
let future = future::poll_fn(|| {
if let Async::Ready(()) = connection.poll()? {
return Ok(Async::Ready(()));
}
if in_progress.is_none() {
try_ready!(h2.poll_ready());
let request = Request::builder()
.method(Method::POST)
.uri("https://example.com/")
.body(())
.unwrap();
let (resp, mut send) = h2.send_request(request, false)?;
send.send_data(vec![0u8; 32769].into(), true).unwrap();
drop(send);
in_progress = Some(resp);
}
match in_progress.as_mut().unwrap().poll() {
r @ Ok(Async::Ready(_)) | r @ Err(_) => {
eprintln!("{:?}", r);
in_progress = None;
}
Ok(Async::NotReady) => (),
}
Ok::<_, h2::Error>(Async::NotReady)
});
let mut spawn = executor::spawn(future);
loop {
if let Async::Ready(()) = spawn.poll_future_notify(&notify_handle, 0)? {
return Ok(());
}
assert!(notify.take_notify());
}
}

fn main() {
env_logger::init();
loop {
fuzz!(|data: &[u8]| {
eprintln!("{:?}", run(data));
});
}
}
16 changes: 16 additions & 0 deletions tests/h2-tests/README.md
Expand Up @@ -5,3 +5,19 @@ crate because they transitively depend on the `unstable` feature flag via
`h2-support`. Due to a cargo limitation, if these tests existed as part of the
`h2` crate, it would require that `h2-support` be published to crates.io and
force the `unstable` feature flag to always be on.

## Setup

Install honggfuzz for cargo:

```rust
cargo install honggfuzz
```

## Running

From within this directory, run the following command:

```
HFUZZ_RUN_ARGS="-t 1" cargo hfuzz run h2-fuzz
```

0 comments on commit 173f9a6

Please sign in to comment.