Skip to content

Commit 173f9a6

Browse files
authored
Include fuzz testing setup (#274)
1 parent 571bb14 commit 173f9a6

File tree

6 files changed

+195
-0
lines changed

6 files changed

+195
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
target
22
Cargo.lock
33
h2spec
4+
45
# These are backup files generated by rustfmt
56
**/*.rs.bk
7+
8+
# Files generated by honggfuzz
9+
hfuzz_target
10+
hfuzz_workspace

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ unstable = []
2929

3030
[workspace]
3131
members = [
32+
"tests/h2-fuzz",
3233
"tests/h2-tests",
3334
"tests/h2-support",
3435
"util/genfixture",

tests/h2-fuzz/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "h2-fuzz"
3+
version = "0.0.0"
4+
publish = false
5+
license = "MIT"
6+
7+
[dependencies]
8+
h2 = { path = "../.." }
9+
10+
env_logger = { version = "0.5.3", default-features = false }
11+
futures = "0.1.21"
12+
honggfuzz = "0.5"
13+
http = "0.1.3"
14+
tokio-io = "0.1.4"

tests/h2-fuzz/README.md

Whitespace-only changes.

tests/h2-fuzz/src/main.rs

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#[macro_use]
2+
extern crate futures;
3+
extern crate tokio_io;
4+
#[macro_use]
5+
extern crate honggfuzz;
6+
extern crate env_logger;
7+
extern crate h2;
8+
extern crate http;
9+
10+
use futures::prelude::*;
11+
use futures::{executor, future, task};
12+
use http::{Method, Request};
13+
use std::cell::Cell;
14+
use std::io::{self, Read, Write};
15+
use std::sync::Arc;
16+
use tokio_io::{AsyncRead, AsyncWrite};
17+
18+
struct MockIo<'a> {
19+
input: &'a [u8],
20+
}
21+
22+
impl<'a> MockIo<'a> {
23+
fn next_byte(&mut self) -> Option<u8> {
24+
if let Some(&c) = self.input.first() {
25+
self.input = &self.input[1..];
26+
Some(c)
27+
} else {
28+
None
29+
}
30+
}
31+
32+
fn next_u32(&mut self) -> u32 {
33+
(self.next_byte().unwrap_or(0) as u32) << 8 | self.next_byte().unwrap_or(0) as u32
34+
}
35+
}
36+
37+
impl<'a> Read for MockIo<'a> {
38+
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
39+
let mut len = self.next_u32() as usize;
40+
if self.input.is_empty() {
41+
Ok(0)
42+
} else if len == 0 {
43+
task::current().notify();
44+
Err(io::ErrorKind::WouldBlock.into())
45+
} else {
46+
if len > self.input.len() {
47+
len = self.input.len();
48+
}
49+
50+
if len > buf.len() {
51+
len = buf.len();
52+
}
53+
buf[0..len].copy_from_slice(&self.input[0..len]);
54+
self.input = &self.input[len..];
55+
Ok(len)
56+
}
57+
}
58+
}
59+
60+
impl<'a> AsyncRead for MockIo<'a> {
61+
unsafe fn prepare_uninitialized_buffer(&self, _buf: &mut [u8]) -> bool {
62+
false
63+
}
64+
}
65+
66+
impl<'a> Write for MockIo<'a> {
67+
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
68+
let len = std::cmp::min(self.next_u32() as usize, buf.len());
69+
if len == 0 {
70+
if self.input.is_empty() {
71+
Err(io::ErrorKind::BrokenPipe.into())
72+
} else {
73+
task::current().notify();
74+
Err(io::ErrorKind::WouldBlock.into())
75+
}
76+
} else {
77+
Ok(len)
78+
}
79+
}
80+
81+
fn flush(&mut self) -> Result<(), io::Error> {
82+
Ok(())
83+
}
84+
}
85+
86+
impl<'a> AsyncWrite for MockIo<'a> {
87+
fn shutdown(&mut self) -> Poll<(), io::Error> {
88+
Ok(Async::Ready(()))
89+
}
90+
}
91+
92+
struct MockNotify {
93+
notified: Cell<bool>,
94+
}
95+
96+
unsafe impl Sync for MockNotify {}
97+
98+
impl executor::Notify for MockNotify {
99+
fn notify(&self, _id: usize) {
100+
self.notified.set(true);
101+
}
102+
}
103+
104+
impl MockNotify {
105+
fn take_notify(&self) -> bool {
106+
self.notified.replace(false)
107+
}
108+
}
109+
110+
fn run(script: &[u8]) -> Result<(), h2::Error> {
111+
let notify = Arc::new(MockNotify {
112+
notified: Cell::new(false),
113+
});
114+
let notify_handle: executor::NotifyHandle = notify.clone().into();
115+
let io = MockIo { input: script };
116+
let (mut h2, mut connection) = h2::client::handshake(io).wait()?;
117+
let mut in_progress = None;
118+
let future = future::poll_fn(|| {
119+
if let Async::Ready(()) = connection.poll()? {
120+
return Ok(Async::Ready(()));
121+
}
122+
if in_progress.is_none() {
123+
try_ready!(h2.poll_ready());
124+
let request = Request::builder()
125+
.method(Method::POST)
126+
.uri("https://example.com/")
127+
.body(())
128+
.unwrap();
129+
let (resp, mut send) = h2.send_request(request, false)?;
130+
send.send_data(vec![0u8; 32769].into(), true).unwrap();
131+
drop(send);
132+
in_progress = Some(resp);
133+
}
134+
match in_progress.as_mut().unwrap().poll() {
135+
r @ Ok(Async::Ready(_)) | r @ Err(_) => {
136+
eprintln!("{:?}", r);
137+
in_progress = None;
138+
}
139+
Ok(Async::NotReady) => (),
140+
}
141+
Ok::<_, h2::Error>(Async::NotReady)
142+
});
143+
let mut spawn = executor::spawn(future);
144+
loop {
145+
if let Async::Ready(()) = spawn.poll_future_notify(&notify_handle, 0)? {
146+
return Ok(());
147+
}
148+
assert!(notify.take_notify());
149+
}
150+
}
151+
152+
fn main() {
153+
env_logger::init();
154+
loop {
155+
fuzz!(|data: &[u8]| {
156+
eprintln!("{:?}", run(data));
157+
});
158+
}
159+
}

tests/h2-tests/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,19 @@ crate because they transitively depend on the `unstable` feature flag via
55
`h2-support`. Due to a cargo limitation, if these tests existed as part of the
66
`h2` crate, it would require that `h2-support` be published to crates.io and
77
force the `unstable` feature flag to always be on.
8+
9+
## Setup
10+
11+
Install honggfuzz for cargo:
12+
13+
```rust
14+
cargo install honggfuzz
15+
```
16+
17+
## Running
18+
19+
From within this directory, run the following command:
20+
21+
```
22+
HFUZZ_RUN_ARGS="-t 1" cargo hfuzz run h2-fuzz
23+
```

0 commit comments

Comments
 (0)