Initial oss-fuzz integration. (#529)

Signed-off-by: davkor <david@adalogics.com>
This commit is contained in:
DavidKorczynski
2021-04-16 22:58:07 +01:00
committed by GitHub
parent 2c53d60098
commit 9c7f47af95
9 changed files with 374 additions and 133 deletions

4
fuzz/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
target
corpus
artifacts

43
fuzz/Cargo.toml Normal file
View File

@@ -0,0 +1,43 @@
[package]
name = "h2-oss-fuzz"
version = "0.0.0"
authors = [ "David Korczynski <david@adalogics.com>" ]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
libfuzzer-sys = { version = "0.4.0", features = ["arbitrary-derive"] }
tokio = { version = "1", features = [ "full" ] }
bytes = "0.5.2"
h2 = { path = "../", features = [ "unstable" ] }
h2-support = { path = "../tests/h2-support" }
futures = { version = "0.3", default-features = false, features = ["std"] }
http = "0.2"
env_logger = { version = "0.5.3", default-features = false }
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzz_client"
path = "fuzz_targets/fuzz_client.rs"
test = false
doc = false
[[bin]]
name = "fuzz_hpack"
path = "fuzz_targets/fuzz_hpack.rs"
test = false
doc = false
[[bin]]
name = "fuzz_e2e"
path = "fuzz_targets/fuzz_e2e.rs"
test = false
doc = false

View File

@@ -0,0 +1,31 @@
#![no_main]
use h2_support::prelude::*;
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target};
#[derive(Debug, Arbitrary)]
struct HttpSpec {
uri: Vec<u8>,
header_name: Vec<u8>,
header_value: Vec<u8>,
}
async fn fuzz_entry(inp: HttpSpec) {
if let Ok(req) = Request::builder()
.uri(&inp.uri[..])
.header(&inp.header_name[..], &inp.header_value[..])
.body(())
{
let (io, mut _srv) = mock::new();
let (mut client, _h2) = client::Builder::new()
.handshake::<_, Bytes>(io)
.await
.unwrap();
let (_, _) = client.send_request(req, true).unwrap();
}
}
fuzz_target!(|inp: HttpSpec| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(fuzz_entry(inp));
});

View File

@@ -0,0 +1,129 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use futures::future;
use futures::stream::FuturesUnordered;
use futures::Stream;
use http::{Method, Request};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
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> AsyncRead for MockIo<'a> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf,
) -> Poll<io::Result<()>> {
let mut len = self.next_u32() as usize;
if self.input.is_empty() {
Poll::Ready(Ok(()))
} else if len == 0 {
cx.waker().clone().wake();
Poll::Pending
} else {
if len > self.input.len() {
len = self.input.len();
}
if len > buf.remaining() {
len = buf.remaining();
}
buf.put_slice(&self.input[len..]);
self.input = &self.input[len..];
Poll::Ready(Ok(()))
}
}
}
impl<'a> AsyncWrite for MockIo<'a> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let len = std::cmp::min(self.next_u32() as usize, buf.len());
if len == 0 {
if self.input.is_empty() {
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
} else {
cx.waker().clone().wake();
Poll::Pending
}
} else {
Poll::Ready(Ok(len))
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
async fn run(script: &[u8]) -> Result<(), h2::Error> {
let io = MockIo { input: script };
let (mut h2, mut connection) = h2::client::handshake(io).await?;
let mut futs = FuturesUnordered::new();
let future = future::poll_fn(|cx| {
if let Poll::Ready(()) = Pin::new(&mut connection).poll(cx)? {
return Poll::Ready(Ok::<_, h2::Error>(()));
}
while futs.len() < 128 {
if !h2.poll_ready(cx)?.is_ready() {
break;
}
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);
futs.push(resp);
}
loop {
match Pin::new(&mut futs).poll_next(cx) {
Poll::Pending | Poll::Ready(None) => break,
r @ Poll::Ready(Some(Ok(_))) | r @ Poll::Ready(Some(Err(_))) => {
eprintln!("{:?}", r);
}
}
}
Poll::Pending
});
future.await?;
Ok(())
}
fuzz_target!(|data: &[u8]| {
let rt = tokio::runtime::Runtime::new().unwrap();
let _res = rt.block_on(run(data));
});

View File

@@ -0,0 +1,6 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data_: &[u8]| {
let _decoder_ = h2::fuzz_bridge::fuzz_logic::fuzz_hpack(data_);
});

28
src/fuzz_bridge.rs Normal file
View File

@@ -0,0 +1,28 @@
#[cfg(fuzzing)]
pub mod fuzz_logic {
use crate::hpack;
use bytes::{BufMut, BytesMut};
use http::header::HeaderName;
use std::io::Cursor;
pub fn fuzz_hpack(data_: &[u8]) {
let mut decoder_ = hpack::Decoder::new(0);
let mut buf = BytesMut::new();
buf.extend(data_);
let _dec_res = decoder_.decode(&mut Cursor::new(&mut buf), |_h| {});
if let Ok(s) = std::str::from_utf8(data_) {
if let Ok(h) = http::Method::from_bytes(s.as_bytes()) {
let m_ = hpack::Header::Method(h);
let mut encoder = hpack::Encoder::new(0, 0);
let _res = encode(&mut encoder, vec![m_]);
}
}
}
fn encode(e: &mut hpack::Encoder, hdrs: Vec<hpack::Header<Option<HeaderName>>>) -> BytesMut {
let mut dst = BytesMut::with_capacity(1024);
e.encode(None, &mut hdrs.into_iter(), &mut (&mut dst).limit(1024));
dst
}
}

View File

@@ -117,6 +117,10 @@ pub mod client;
pub mod server; pub mod server;
mod share; mod share;
#[cfg(fuzzing)]
#[cfg_attr(feature = "unstable", allow(missing_docs))]
pub mod fuzz_bridge;
pub use crate::error::{Error, Reason}; pub use crate::error::{Error, Reason};
pub use crate::share::{FlowControl, Ping, PingPong, Pong, RecvStream, SendStream, StreamId}; pub use crate::share::{FlowControl, Ping, PingPong, Pong, RecvStream, SendStream, StreamId};

View File

@@ -12,4 +12,4 @@ env_logger = { version = "0.5.3", default-features = false }
futures = { version = "0.3", default-features = false, features = ["std"] } futures = { version = "0.3", default-features = false, features = ["std"] }
honggfuzz = "0.5" honggfuzz = "0.5"
http = "0.2" http = "0.2"
tokio = "1" tokio = { version = "1", features = [ "full" ] }

View File

@@ -1,132 +1,128 @@
use futures::future; use futures::future;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::Stream; use futures::Stream;
use http::{Method, Request}; use http::{Method, Request};
use std::future::Future; use std::future::Future;
use std::io; use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
struct MockIo<'a> { struct MockIo<'a> {
input: &'a [u8], input: &'a [u8],
} }
impl<'a> MockIo<'a> { impl<'a> MockIo<'a> {
fn next_byte(&mut self) -> Option<u8> { fn next_byte(&mut self) -> Option<u8> {
if let Some(&c) = self.input.first() { if let Some(&c) = self.input.first() {
self.input = &self.input[1..]; self.input = &self.input[1..];
Some(c) Some(c)
} else { } else {
None None
} }
} }
fn next_u32(&mut self) -> u32 { fn next_u32(&mut self) -> u32 {
(self.next_byte().unwrap_or(0) as u32) << 8 | self.next_byte().unwrap_or(0) as u32 (self.next_byte().unwrap_or(0) as u32) << 8 | self.next_byte().unwrap_or(0) as u32
} }
} }
impl<'a> AsyncRead for MockIo<'a> { impl<'a> AsyncRead for MockIo<'a> {
unsafe fn prepare_uninitialized_buffer(&self, _buf: &mut [std::mem::MaybeUninit<u8>]) -> bool { fn poll_read(
false mut self: Pin<&mut Self>,
} cx: &mut Context<'_>,
buf: &mut ReadBuf,
fn poll_read( ) -> Poll<io::Result<()>> {
mut self: Pin<&mut Self>, let mut len = self.next_u32() as usize;
cx: &mut Context<'_>, if self.input.is_empty() {
buf: &mut [u8], Poll::Ready(Ok(()))
) -> Poll<io::Result<usize>> { } else if len == 0 {
let mut len = self.next_u32() as usize; cx.waker().clone().wake();
if self.input.is_empty() { Poll::Pending
Poll::Ready(Ok(0)) } else {
} else if len == 0 { if len > self.input.len() {
cx.waker().clone().wake(); len = self.input.len();
Poll::Pending }
} else {
if len > self.input.len() { if len > buf.remaining() {
len = self.input.len(); len = buf.remaining();
} }
buf.put_slice(&self.input[len..]);
if len > buf.len() { self.input = &self.input[len..];
len = buf.len(); Poll::Ready(Ok(()))
} }
buf[0..len].copy_from_slice(&self.input[0..len]); }
self.input = &self.input[len..]; }
Poll::Ready(Ok(len))
} impl<'a> AsyncWrite for MockIo<'a> {
} fn poll_write(
} mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
impl<'a> AsyncWrite for MockIo<'a> { buf: &[u8],
fn poll_write( ) -> Poll<io::Result<usize>> {
mut self: Pin<&mut Self>, let len = std::cmp::min(self.next_u32() as usize, buf.len());
cx: &mut Context<'_>, if len == 0 {
buf: &[u8], if self.input.is_empty() {
) -> Poll<io::Result<usize>> { Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
let len = std::cmp::min(self.next_u32() as usize, buf.len()); } else {
if len == 0 { cx.waker().clone().wake();
if self.input.is_empty() { Poll::Pending
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) }
} else { } else {
cx.waker().clone().wake(); Poll::Ready(Ok(len))
Poll::Pending }
} }
} else {
Poll::Ready(Ok(len)) fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
} Poll::Ready(Ok(()))
} }
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { Poll::Ready(Ok(()))
Poll::Ready(Ok(())) }
} }
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(())) async fn run(script: &[u8]) -> Result<(), h2::Error> {
} let io = MockIo { input: script };
} let (mut h2, mut connection) = h2::client::handshake(io).await?;
let mut futs = FuturesUnordered::new();
async fn run(script: &[u8]) -> Result<(), h2::Error> { let future = future::poll_fn(|cx| {
let io = MockIo { input: script }; if let Poll::Ready(()) = Pin::new(&mut connection).poll(cx)? {
let (mut h2, mut connection) = h2::client::handshake(io).await?; return Poll::Ready(Ok::<_, h2::Error>(()));
let mut futs = FuturesUnordered::new(); }
let future = future::poll_fn(|cx| { while futs.len() < 128 {
if let Poll::Ready(()) = Pin::new(&mut connection).poll(cx)? { if !h2.poll_ready(cx)?.is_ready() {
return Poll::Ready(Ok::<_, h2::Error>(())); break;
} }
while futs.len() < 128 { let request = Request::builder()
if !h2.poll_ready(cx)?.is_ready() { .method(Method::POST)
break; .uri("https://example.com/")
} .body(())
let request = Request::builder() .unwrap();
.method(Method::POST) let (resp, mut send) = h2.send_request(request, false)?;
.uri("https://example.com/") send.send_data(vec![0u8; 32769].into(), true).unwrap();
.body(()) drop(send);
.unwrap(); futs.push(resp);
let (resp, mut send) = h2.send_request(request, false)?; }
send.send_data(vec![0u8; 32769].into(), true).unwrap(); loop {
drop(send); match Pin::new(&mut futs).poll_next(cx) {
futs.push(resp); Poll::Pending | Poll::Ready(None) => break,
} r @ Poll::Ready(Some(Ok(_))) | r @ Poll::Ready(Some(Err(_))) => {
loop { eprintln!("{:?}", r);
match Pin::new(&mut futs).poll_next(cx) { }
Poll::Pending | Poll::Ready(None) => break, }
r @ Poll::Ready(Some(Ok(_))) | r @ Poll::Ready(Some(Err(_))) => { }
eprintln!("{:?}", r); Poll::Pending
} });
} future.await?;
} Ok(())
Poll::Pending }
});
future.await?; fn main() {
Ok(()) env_logger::init();
} let mut rt = tokio::runtime::Runtime::new().unwrap();
loop {
fn main() { honggfuzz::fuzz!(|data: &[u8]| {
env_logger::init(); eprintln!("{:?}", rt.block_on(run(data)));
let mut rt = tokio::runtime::Runtime::new().unwrap(); });
loop { }
honggfuzz::fuzz!(|data: &[u8]| { }
eprintln!("{:?}", rt.block_on(run(data)));
});
}
}