Initial oss-fuzz integration. (#529)
Signed-off-by: davkor <david@adalogics.com>
This commit is contained in:
4
fuzz/.gitignore
vendored
Normal file
4
fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
43
fuzz/Cargo.toml
Normal file
43
fuzz/Cargo.toml
Normal 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
|
||||||
31
fuzz/fuzz_targets/fuzz_client.rs
Normal file
31
fuzz/fuzz_targets/fuzz_client.rs
Normal 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));
|
||||||
|
});
|
||||||
129
fuzz/fuzz_targets/fuzz_e2e.rs
Normal file
129
fuzz/fuzz_targets/fuzz_e2e.rs
Normal 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));
|
||||||
|
});
|
||||||
|
|
||||||
6
fuzz/fuzz_targets/fuzz_hpack.rs
Normal file
6
fuzz/fuzz_targets/fuzz_hpack.rs
Normal 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
28
src/fuzz_bridge.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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};
|
||||||
|
|
||||||
|
|||||||
@@ -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" ] }
|
||||||
|
|||||||
@@ -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)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user